diff --git a/src/main/java/com/hfstudio/guidenh/ClientProxy.java b/src/main/java/com/hfstudio/guidenh/ClientProxy.java index 2d7ad07d..38708da0 100644 --- a/src/main/java/com/hfstudio/guidenh/ClientProxy.java +++ b/src/main/java/com/hfstudio/guidenh/ClientProxy.java @@ -7,6 +7,7 @@ import com.hfstudio.guidenh.bridge.GuideNhRuntimeBridge; import com.hfstudio.guidenh.bridge.GuideNhRuntimeBridgeSettings; +import com.hfstudio.guidenh.client.GuideNhClientTaskScheduler; import com.hfstudio.guidenh.client.RegionWandRenderer; import com.hfstudio.guidenh.client.command.GuideNhClientBridgeController; import com.hfstudio.guidenh.client.command.GuideNhClientCommand; @@ -111,6 +112,7 @@ public static CompileWorker getWorker() { @Override public void preInit(FMLPreInitializationEvent event) { super.preInit(event); + GuideNhClientTaskScheduler.initialize(); GuidebookLevel.setPreviewWorldFactory(GuidebookFakeWorld::new); GuideNhClientIntegrationBootstrap.preInitClient(); GuideME.initClientProxy(); diff --git a/src/main/java/com/hfstudio/guidenh/bridge/preview/ItemPreviewService.java b/src/main/java/com/hfstudio/guidenh/bridge/preview/ItemPreviewService.java index 03df98f5..ee44e871 100644 --- a/src/main/java/com/hfstudio/guidenh/bridge/preview/ItemPreviewService.java +++ b/src/main/java/com/hfstudio/guidenh/bridge/preview/ItemPreviewService.java @@ -23,6 +23,7 @@ import org.lwjgl.opengl.GL11; import com.hfstudio.guidenh.bridge.protocol.BridgeProtocolLimits; +import com.hfstudio.guidenh.client.GuideNhClientTaskScheduler; import com.hfstudio.guidenh.guide.compiler.IdUtils; import com.hfstudio.guidenh.guide.document.interaction.ItemTooltip; import com.hfstudio.guidenh.guide.internal.tooltip.GuideItemTooltipLines; @@ -98,7 +99,7 @@ private ItemPreviewPayload renderOnClientThread(ItemPreviewCacheKey cacheKey, It } CompletableFuture future = new CompletableFuture<>(); - minecraft.func_152344_a(() -> { + GuideNhClientTaskScheduler.execute(() -> { try { future.complete(createPayload(cacheKey, stack, Minecraft.getMinecraft())); } catch (Throwable error) { diff --git a/src/main/java/com/hfstudio/guidenh/client/GuideNhClientTaskScheduler.java b/src/main/java/com/hfstudio/guidenh/client/GuideNhClientTaskScheduler.java new file mode 100644 index 00000000..4f0a0c6d --- /dev/null +++ b/src/main/java/com/hfstudio/guidenh/client/GuideNhClientTaskScheduler.java @@ -0,0 +1,67 @@ +package com.hfstudio.guidenh.client; + +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import net.minecraft.client.Minecraft; + +import com.hfstudio.guidenh.guide.scene.support.GuideDebugLog; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.TickEvent; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class GuideNhClientTaskScheduler { + + private static final Queue PENDING_TASKS = new ConcurrentLinkedQueue<>(); + private static final Runnable TICK_BOUNDARY = () -> {}; + private static boolean initialized; + + public static void initialize() { + if (initialized) { + return; + } + FMLCommonHandler.instance() + .bus() + .register(new GuideNhClientTaskScheduler()); + initialized = true; + } + + public static boolean isOnClientThread() { + Minecraft minecraft = Minecraft.getMinecraft(); + return minecraft != null && minecraft.func_152345_ab(); + } + + public static void execute(Runnable task) { + Objects.requireNonNull(task, "task"); + if (isOnClientThread()) { + runTask(task); + return; + } + PENDING_TASKS.add(task); + } + + @SubscribeEvent + public void onClientTick(TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.Phase.END) { + return; + } + PENDING_TASKS.add(TICK_BOUNDARY); + Runnable task; + while ((task = PENDING_TASKS.poll()) != null && task != TICK_BOUNDARY) { + runTask(task); + } + } + + private static void runTask(Runnable task) { + try { + task.run(); + } catch (Throwable error) { + GuideDebugLog.error("Client task execution failed", error); + } + } +} diff --git a/src/main/java/com/hfstudio/guidenh/guide/compiler/GuideItemReferenceResolver.java b/src/main/java/com/hfstudio/guidenh/guide/compiler/GuideItemReferenceResolver.java index e3a5ffae..ff9c9780 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/compiler/GuideItemReferenceResolver.java +++ b/src/main/java/com/hfstudio/guidenh/guide/compiler/GuideItemReferenceResolver.java @@ -104,19 +104,23 @@ public static ItemStack resolveOreDictionaryStack(@Nullable String oreName) { } List oreStacks = OreDictionary.getOres(trimmedOreName); - if (oreStacks == null || oreStacks.isEmpty()) { + if (oreStacks.isEmpty()) { return null; } - ItemStack firstMatch = oreStacks.getFirst(); - if (firstMatch == null || firstMatch.getItem() == null) { - return null; + for (ItemStack stack : oreStacks) { + if (stack == null || stack.getItem() == null) { + continue; + } + + ItemStack copiedStack = stack.copy(); + ItemStack normalizedStack = GuideNhIntegrationRegistry.global() + .normalizeItemStack(copiedStack); + + return normalizedStack != null && normalizedStack.getItem() != null ? normalizedStack : copiedStack; } - ItemStack copiedStack = firstMatch.copy(); - ItemStack normalizedStack = GuideNhIntegrationRegistry.global() - .normalizeItemStack(copiedStack); - return normalizedStack != null && normalizedStack.getItem() != null ? normalizedStack : copiedStack; + return null; } @Nullable diff --git a/src/main/java/com/hfstudio/guidenh/guide/compiler/IdUtils.java b/src/main/java/com/hfstudio/guidenh/guide/compiler/IdUtils.java index bb20f028..aba622ad 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/compiler/IdUtils.java +++ b/src/main/java/com/hfstudio/guidenh/guide/compiler/IdUtils.java @@ -106,7 +106,7 @@ public static ParsedItemRef parseItemRef(String idText, String defaultNamespace) nbt = tc; } } catch (Throwable t) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [IdUtils] Failed to parse SNBT tail '{}' for id '{}'; ignoring NBT", snbt, idText, diff --git a/src/main/java/com/hfstudio/guidenh/guide/compiler/PageCompiler.java b/src/main/java/com/hfstudio/guidenh/guide/compiler/PageCompiler.java index 23694a7d..df51d16d 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/compiler/PageCompiler.java +++ b/src/main/java/com/hfstudio/guidenh/guide/compiler/PageCompiler.java @@ -993,9 +993,9 @@ public LytFlowContent createErrorFlowContent(String text, UnistNode child) { span.appendText(tildes + "^"); span.appendBreak(); - GuideDebugLog.warnAlways("[GuideNH] [PageCompiler] {}\n{}\n{}\n", text, line, tildes + "^"); + GuideDebugLog.warn("[GuideNH] [PageCompiler] {}\n{}\n{}\n", text, line, tildes + "^"); } else { - GuideDebugLog.warnAlways("[GuideNH] [PageCompiler] {}\n", text); + GuideDebugLog.warn("[GuideNH] [PageCompiler] {}\n", text); } return span; diff --git a/src/main/java/com/hfstudio/guidenh/guide/compiler/tags/BlockquoteCompiler.java b/src/main/java/com/hfstudio/guidenh/guide/compiler/tags/BlockquoteCompiler.java index 16381ece..06b6bada 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/compiler/tags/BlockquoteCompiler.java +++ b/src/main/java/com/hfstudio/guidenh/guide/compiler/tags/BlockquoteCompiler.java @@ -72,19 +72,21 @@ protected void compile(PageCompiler compiler, LytBlockContainer parent, MdxJsxEl } private void compileDirectiveBody(PageCompiler compiler, BlockquoteDirective directive, LytBlockContainer parent) { + MdxJsxFlowElement firstParagraph = directive.firstParagraph() instanceof MdxJsxFlowElement paragraph ? paragraph + : null; // When there's a remainingText override and the first paragraph is still present // at the head of the children list, replace its leading text. // Otherwise — just compile children normally. if (!directive.children() - .isEmpty() && directive.firstParagraph() != null + .isEmpty() && firstParagraph != null && directive.children() - .getFirst() == directive.firstParagraph() + .getFirst() == firstParagraph && directive.remainingText() != null && !directive.remainingText() .isEmpty()) { // Strip directive prefix from the first paragraph's leading text - stripLeadingText(directive.firstParagraph(), directive.remainingText()); - compiler.compileBlockContext(Collections.singletonList(directive.firstParagraph()), parent); + stripLeadingText(firstParagraph, directive.remainingText()); + compiler.compileBlockContext(Collections.singletonList(firstParagraph), parent); for (int i = 1; i < directive.children() .size(); i++) { compiler.compileBlockContext( diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytCodeBlockToolbar.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytCodeBlockToolbar.java index 705a9734..9ee2c4f9 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytCodeBlockToolbar.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytCodeBlockToolbar.java @@ -25,7 +25,7 @@ public class LytCodeBlockToolbar extends LytBox implements InteractiveElement { - static final GuiSprite COPY_SPRITE = new GuiSprite( + public static final GuiSprite COPY_SPRITE = new GuiSprite( GuideIconButton.TEX, 0, 48, @@ -33,6 +33,14 @@ public class LytCodeBlockToolbar extends LytBox implements InteractiveElement { 16, GuideIconButton.TEXTURE_SIZE, GuideIconButton.TEXTURE_SIZE); + public static final GuiSprite RESET_VIEW_SPRITE = new GuiSprite( + GuideIconButton.TEX, + 0, + 32, + 16, + 16, + GuideIconButton.TEXTURE_SIZE, + GuideIconButton.TEXTURE_SIZE); private static final long COPY_TOOLTIP_RESET_DELAY_MILLIS = 1500L; private static final int TEXT_CENTERING_OFFSET_Y = 1; private static final CodeHighlightTheme CODE_THEME = CodeHighlightTheme.GITHUB_DARK_DEFAULT; @@ -89,6 +97,7 @@ public void setCopyText(String copyText) { } public void addButton(LytButton button) { + button.setColor(toolbarText); extraButtons.add(button); append(button); if (getDocument() != null) getDocument().invalidateLayout(); diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidCanvas.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidCanvas.java index 55eac157..ec0f84f3 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidCanvas.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidCanvas.java @@ -23,6 +23,8 @@ import com.hfstudio.guidenh.guide.document.interaction.FlowInteractionPath; import com.hfstudio.guidenh.guide.document.interaction.GuideTooltip; import com.hfstudio.guidenh.guide.document.interaction.InteractiveElement; +import com.hfstudio.guidenh.guide.internal.debug.DebugComponent; +import com.hfstudio.guidenh.guide.internal.debug.DebugFlowContainer; import com.hfstudio.guidenh.guide.internal.recipe.LytNeiRecipeBox; import com.hfstudio.guidenh.guide.internal.util.SmoothFloatState; import com.hfstudio.guidenh.guide.render.GuiSprite; @@ -55,6 +57,14 @@ public abstract class LytMermaidCanvas> extends Ly private final Map scaledStyleCache = new IdentityHashMap<>(); private float lastScaledStyleZoom = Float.NaN; + @Nullable + private Object debugComponentLayout; + @Nullable + private LytRect debugComponentViewport; + private int debugComponentOffsetX; + private int debugComponentOffsetY; + private float debugComponentZoom; + private List cachedDebugComponents = List.of(); // Common interaction state protected Map nodeContentBlocks; @@ -256,6 +266,12 @@ public void centerDiagram(int viewportWidth, int viewportHeight, int diagramWidt zoom); } + public void resetView() { + zoom = 1f; + centerDiagram(contentWidth(), contentHeight()); + clampOffsets(); + } + @Override public boolean beginDrag(int documentX, int documentY, int button) { if (!diagramReady()) return false; @@ -360,11 +376,37 @@ protected ResolvedTextStyle getOrScaleStyle(ResolvedTextStyle base, float zoom) return MermaidNodeRenderer.getOrScaleStyle(scaledStyleCache, base, zoom); } - public static int clampAxis(int offset, int viewportSize, int contentSize) { - if (contentSize <= viewportSize) { - return (viewportSize - contentSize) / 2; + @Nullable + protected List getCachedDebugComponents(Object layout) { + LytRect viewport = getInnerViewport(); + int offsetX = getVisualOffsetX(); + int offsetY = getVisualOffsetY(); + float activeZoom = getActiveZoom(); + if (layout != debugComponentLayout || !viewport.equals(debugComponentViewport) + || offsetX != debugComponentOffsetX + || offsetY != debugComponentOffsetY + || Float.compare(activeZoom, debugComponentZoom) != 0) { + return null; } - return Math.clamp(offset, viewportSize - contentSize, 0); + return cachedDebugComponents; + } + + protected List cacheDebugComponents(Object layout, + List components) { + debugComponentLayout = layout; + debugComponentViewport = getInnerViewport(); + debugComponentOffsetX = getVisualOffsetX(); + debugComponentOffsetY = getVisualOffsetY(); + debugComponentZoom = getActiveZoom(); + cachedDebugComponents = List.copyOf(components); + return cachedDebugComponents; + } + + public static int clampAxis(int offset, int viewportSize, int contentSize) { + int minimumVisible = Math.max(1, Math.min(contentSize, viewportSize) / 2); + int minOffset = minimumVisible - contentSize; + int maxOffset = viewportSize - minimumVisible; + return Math.clamp(offset, minOffset, maxOffset); } public static int scaled(int base, int value, float activeZoom) { @@ -519,6 +561,81 @@ protected static LytRect resolveNodeContentRect(NodeContentLayout contentLayout, .height() * activeZoom))); } + protected void collectNodeContentDebugComponents(NodeContentLayout contentLayout, LytRect contentViewport, + float activeZoom, String nodeName, int priority, List components) { + if (contentLayout == null || contentViewport == null + || contentLayout.visualBounds() + .isEmpty()) { + return; + } + int originX = contentViewport.x() - Math.round( + contentLayout.visualBounds() + .x() * activeZoom); + int originY = contentViewport.y() - Math.round( + contentLayout.visualBounds() + .y() * activeZoom); + collectNodeContentDebugComponents( + contentLayout.block(), + originX, + originY, + activeZoom, + nodeName, + priority, + components, + 0); + } + + private void collectNodeContentDebugComponents(LytNode node, int originX, int originY, float activeZoom, + String nodeName, int priority, List components, int depth) { + LytRect localBounds = node.getBounds(); + if (localBounds != null && !localBounds.isEmpty()) { + LytRect screenBounds = new LytRect( + originX + Math.round(localBounds.x() * activeZoom), + originY + Math.round(localBounds.y() * activeZoom), + Math.max(1, Math.round(localBounds.width() * activeZoom)), + Math.max(1, Math.round(localBounds.height() * activeZoom))); + components.add( + new DebugComponent.SimpleComponentEntry( + "NodeContent:" + nodeName + + ":" + + node.getClass() + .getSimpleName(), + screenBounds, + null, + priority + depth)); + if (node instanceof DebugFlowContainer flowContainer) { + for (DebugFlowContainer.FlowContentEntry entry : flowContainer.getAllFlowContent()) { + LytRect flowBounds = entry.bounds(); + components.add( + new DebugComponent.SimpleComponentEntry( + "NodeContent:" + nodeName + + ":" + + entry.content() + .getClass() + .getSimpleName(), + new LytRect( + originX + Math.round(flowBounds.x() * activeZoom), + originY + Math.round(flowBounds.y() * activeZoom), + Math.max(1, Math.round(flowBounds.width() * activeZoom)), + Math.max(1, Math.round(flowBounds.height() * activeZoom))), + null, + priority + depth + 10)); + } + } + } + for (LytNode child : node.getChildren()) { + collectNodeContentDebugComponents( + child, + originX, + originY, + activeZoom, + nodeName, + priority, + components, + depth + 1); + } + } + public record NodeHit(LytNode node, FlowInteractionPath flowPath, int localX, int localY) { public NodeHit(LytNode node, @Nullable FlowInteractionPath flowPath, int localX, int localY) { diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchart.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchart.java index 55f40618..c85b1035 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchart.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchart.java @@ -55,11 +55,20 @@ public LytMermaidFlowchart(FlowchartDocument flowchart, String sourceText, Map canvas.resetView()); + button.setTooltipText(GuidebookText.ResetView.text()); + button.setHoverColor(SymbolicColor.ICON_BUTTON_HOVER); + return button; + } + public void setPreferredSize(int width, int height) { canvas.setPreferredSize(width, height); toolbar.setPreferredWidth(width); diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchartCanvas.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchartCanvas.java index 2097c2c1..be4e3e2d 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchartCanvas.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchartCanvas.java @@ -11,6 +11,7 @@ import com.hfstudio.guidenh.guide.document.LytRect; import com.hfstudio.guidenh.guide.document.block.shapes.FlowchartShapes; import com.hfstudio.guidenh.guide.document.interaction.DocumentInteractionSnapshot; +import com.hfstudio.guidenh.guide.internal.debug.DebugComponent; import com.hfstudio.guidenh.guide.internal.mermaid.MermaidArrowHead; import com.hfstudio.guidenh.guide.internal.mermaid.MermaidEdgeStyle; import com.hfstudio.guidenh.guide.internal.mermaid.MermaidNodeShape; @@ -29,7 +30,7 @@ import com.hfstudio.guidenh.guide.style.TextAlignment; import com.hfstudio.guidenh.guide.style.WhiteSpaceMode; -public class LytMermaidFlowchartCanvas extends LytMermaidCanvas { +public class LytMermaidFlowchartCanvas extends LytMermaidCanvas implements DebugComponent { private static final int CANVAS_PADDING = 10; private static final int MIN_WIDTH = 96; @@ -956,4 +957,63 @@ private static int parseHexColor(String hex) { } catch (NumberFormatException ignored) {} return 0; } + + @Override + public List getDebugComponents() { + List cachedComponents = getCachedDebugComponents(layout); + if (cachedComponents != null) { + return cachedComponents; + } + List components = new ArrayList<>(); + if (layout == null || bounds == null) { + return components; + } + LytRect viewport = getInnerViewport(); + float zoom = getActiveZoom(); + int baseX = viewport.x() + getVisualOffsetX() - getScaledOriginX(); + int baseY = viewport.y() + getVisualOffsetY() - getScaledOriginY(); + + for (EdgePath edge : layout.getEdgePaths()) { + List points = edge.getPoints(); + for (int index = 1; index < points.size(); index++) { + var from = points.get(index - 1); + var to = points.get(index); + components.add( + new LineComponentEntry( + "Edge:" + edge.getFromId() + "->" + edge.getToId(), + scaled(baseX, from.getX(), zoom), + scaled(baseY, from.getY(), zoom), + scaled(baseX, to.getX(), zoom), + scaled(baseY, to.getY(), zoom), + Math.max(3, Math.round(CONNECTOR_THICKNESS * zoom) + 2), + null, + 10)); + } + } + + for (var entry : layout.getNodePositions() + .entrySet()) { + NodePosition position = entry.getValue(); + int x = scaled(baseX, position.getX(), zoom); + int y = scaled(baseY, position.getY(), zoom); + int width = Math.max(1, Math.round(position.getWidth() * zoom)); + int height = Math.max(1, Math.round(position.getHeight() * zoom)); + LytRect nodeBounds = new LytRect(x, y, width, height); + FlowchartNode node = document.getNodes() + .get(entry.getKey()); + String label = node != null && node.getLabel() != null ? node.getLabel() : entry.getKey(); + components.add(new SimpleComponentEntry("Node:" + label, nodeBounds, null, 20)); + LytRect contentBounds = nodeBounds.shrink( + Math.max(1, Math.round(NODE_PADDING_X * zoom)), + Math.max(1, Math.round(NODE_PADDING_Y * zoom)), + Math.max(1, Math.round(NODE_PADDING_X * zoom)), + Math.max(1, Math.round(NODE_PADDING_Y * zoom))); + components.add(new SimpleComponentEntry("Label:" + label, contentBounds, null, 25)); + NodeContentLayout contentLayout = nodeContentLayouts.get(entry.getKey()); + if (contentLayout != null) { + collectNodeContentDebugComponents(contentLayout, contentBounds, zoom, label, 30, components); + } + } + return cacheDebugComponents(layout, components); + } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmap.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmap.java index 59f18c92..91a224a2 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmap.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmap.java @@ -5,8 +5,10 @@ import java.util.Optional; import com.hfstudio.guidenh.guide.color.SymbolicColor; +import com.hfstudio.guidenh.guide.document.LytSize; import com.hfstudio.guidenh.guide.document.interaction.GuideTooltip; import com.hfstudio.guidenh.guide.document.interaction.InteractiveElement; +import com.hfstudio.guidenh.guide.internal.GuidebookText; import com.hfstudio.guidenh.guide.internal.mermaid.mindmap.MindmapDocument; import com.hfstudio.guidenh.guide.style.BorderStyle; import com.hfstudio.guidenh.guide.ui.GuideUiHost; @@ -41,11 +43,20 @@ public LytMermaidMindmap(MindmapDocument mindmap, String sourceText, Map canvas.resetView()); + button.setTooltipText(GuidebookText.ResetView.text()); + button.setHoverColor(SymbolicColor.ICON_BUTTON_HOVER); + return button; + } + public void setPreferredSize(int width, int height) { canvas.setPreferredSize(width, height); toolbar.setPreferredWidth(width); diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmapCanvas.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmapCanvas.java index 0750ccbb..6498a35d 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmapCanvas.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmapCanvas.java @@ -823,6 +823,10 @@ public int centerY() { @Override public List getDebugComponents() { + List cachedComponents = getCachedDebugComponents(layout); + if (cachedComponents != null) { + return cachedComponents; + } List components = new ArrayList<>(); if (layout == null || bounds == null) { @@ -837,7 +841,14 @@ public List getDebugComponents() { // Collect all nodes from the layout collectNodeComponents(layout.root(), components, baseX, baseY, activeZoom); - return components; + return cacheDebugComponents(layout, components); + } + + private void addConnectorSegments(List components, int x1, int y1, int x2, int y2, int x3, int y3, + int x4, int y4) { + components.add(new LineComponentEntry("Connector", x1, y1, x2, y2, CONNECTOR_THICKNESS + 2, null, 10)); + components.add(new LineComponentEntry("Connector", x2, y2, x3, y3, CONNECTOR_THICKNESS + 2, null, 10)); + components.add(new LineComponentEntry("Connector", x3, y3, x4, y4, CONNECTOR_THICKNESS + 2, null, 10)); } private void collectNodeComponents(NodeLayout node, List components, int baseX, int baseY, @@ -906,11 +917,43 @@ private void collectNodeComponents(NodeLayout node, List compone activeZoom); components.add( new SimpleComponentEntry("NodeContent", contentRect, "Block content for: " + nodeName, priority + 3)); + collectNodeContentDebugComponents( + node.contentLayout, + contentRect, + activeZoom, + nodeName, + priority + 4, + components); } // Recursively collect children for (NodeLayout child : node.children) { + collectConnectorComponents(node, child, components, baseX, baseY, activeZoom); collectNodeComponents(child, components, baseX, baseY, activeZoom); } } + + private void collectConnectorComponents(NodeLayout parent, NodeLayout child, List components, + int baseX, int baseY, float activeZoom) { + int startX; + int startY; + int endX; + int endY; + if (mindmap.getLayoutMode() == MindmapLayoutMode.TIDY_TREE) { + startX = scaled(baseX, parent.centerX(), activeZoom); + startY = scaled(baseY, parent.bottom(), activeZoom); + endX = scaled(baseX, child.centerX(), activeZoom); + endY = scaled(baseY, child.y, activeZoom); + int middleY = (startY + endY) / 2; + addConnectorSegments(components, startX, startY, startX, middleY, endX, middleY, endX, endY); + return; + } + boolean rightSide = child.centerX() >= parent.centerX(); + startX = scaled(baseX, rightSide ? parent.right() : parent.x, activeZoom); + startY = scaled(baseY, parent.centerY(), activeZoom); + endX = scaled(baseX, rightSide ? child.x : child.right(), activeZoom); + endY = scaled(baseY, child.centerY(), activeZoom); + int middleX = (startX + endX) / 2; + addConnectorSegments(components, startX, startY, middleX, startY, middleX, endY, endX, endY); + } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/functiongraph/LytFunctionGraph.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/functiongraph/LytFunctionGraph.java index 877711a3..57343c22 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/functiongraph/LytFunctionGraph.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/functiongraph/LytFunctionGraph.java @@ -14,6 +14,7 @@ import com.hfstudio.guidenh.guide.document.interaction.DocumentDragTarget; import com.hfstudio.guidenh.guide.document.interaction.GuideTooltip; import com.hfstudio.guidenh.guide.document.interaction.InteractiveElement; +import com.hfstudio.guidenh.guide.document.interaction.TextTooltip; import com.hfstudio.guidenh.guide.layout.LayoutContext; import com.hfstudio.guidenh.guide.render.RenderContext; import com.hfstudio.guidenh.guide.style.ResolvedTextStyle; @@ -25,14 +26,14 @@ /** * Function graph block. Plots one or more {@link FunctionPlot} curves on a Cartesian panel with - * interactive Desmos-style hovering: while the cursor is over a curve the segment is thickened, an - * accent point is drawn at the cursor's x value, and a custom tooltip anchored to the point is - * rendered. Pressing the mouse button latches the highlight onto that curve so the user can drag - * along it freely until the button is released, even when the cursor strays vertically. + * interactive Desmos-style hovering: while the cursor is over a curve the segment is thickened and an + * accent point is drawn at the cursor's x value. Pressing the mouse button latches the highlight + * onto that curve so the user can drag along it freely until the button is released, even when the + * cursor strays vertically. * *

- * Layout, sampling and the tooltip overlay are all handled inside this single block so the rest - * of the document does not need to coordinate with it. + * Layout, sampling and hover-state tracking are all handled inside this single block so the rest of + * the document does not need to coordinate with it. */ public class LytFunctionGraph extends LytBlock implements InteractiveElement, DocumentDragTarget { @@ -50,9 +51,6 @@ public class LytFunctionGraph extends LytBlock implements InteractiveElement, Do private static final float HIGHLIGHT_LINE_BONUS = 1.0f; private static final int POINT_RADIUS = 3; private static final float POINT_OUTER_RING = 1f; - private static final int TOOLTIP_PADDING_X = 5; - private static final int TOOLTIP_PADDING_Y = 4; - private static final int TOOLTIP_GAP = 8; private static final int LEGEND_GAP_ABOVE = 4; private static final int LEGEND_ROW_GAP = 2; private static final int LEGEND_ITEM_GAP = 10; @@ -67,7 +65,6 @@ public class LytFunctionGraph extends LytBlock implements InteractiveElement, Do private static final ResolvedTextStyle TITLE_STYLE = makeStyle(0xFFE6E6E6, true); private static final ResolvedTextStyle AXIS_LABEL_STYLE = makeStyle(0xFFB8C2CF, false); - private static final ResolvedTextStyle TOOLTIP_TITLE_STYLE = makeStyle(0xFFFFFFFF, false); private static final ResolvedTextStyle TOOLTIP_BODY_STYLE = makeStyle(0xFFD7DEE7, false); private static final ResolvedTextStyle LEGEND_LABEL_STYLE = makeStyle(0xFFD7DEE7, false); @@ -295,8 +292,7 @@ public Optional getTooltip(float x, float y) { if (!isDragging) { updateHover(x, y); } - // Tooltip is rendered manually anchored to the point; no built-in tooltip is returned. - return Optional.empty(); + return createActiveTooltip(); } @Override @@ -918,10 +914,6 @@ private void renderActiveOverlay(RenderContext context, LytRect plotRect) { context.fillCircle(sx, sy, POINT_RADIUS + POINT_OUTER_RING, 0xFFFFFFFF); context.fillCircle(sx, sy, POINT_RADIUS, plot.getColor()); - // Tooltip panel. - String line1 = !isEmpty(plot.getLabel()) ? plot.getLabel() : plot.getExpressionText(); - String line2 = "(" + formatValue(dataX) + ", " + formatValue(dataY) + ")"; - renderTooltipBox(context, sx, sy, line1, line2); } private void renderMarkedPointOverlay(RenderContext context, LytRect plotRect) { @@ -938,10 +930,6 @@ private void renderMarkedPointOverlay(RenderContext context, LytRect plotRect) { context.drawCircleOutline(sx, sy, POINT_RADIUS + 2f, 1f, 0xFF000000); context.fillCircle(sx, sy, POINT_RADIUS, color); - MarkedPoint point = points.get(activeMarkedIndex); - String line1 = !isEmpty(point.getLabel()) ? point.getLabel() : "Point"; - String line2 = "(" + formatValue(dataX) + ", " + formatValue(dataY) + ")"; - renderTooltipBox(context, sx, sy, line1, line2); } private void renderAutoPointOverlay(RenderContext context, LytRect plotRect) { @@ -957,31 +945,44 @@ private void renderAutoPointOverlay(RenderContext context, LytRect plotRect) { context.drawCircleOutline(sx, sy, POINT_RADIUS + 2f, 1f, 0xFF000000); context.fillCircle(sx, sy, POINT_RADIUS, color); - FunctionPlot plot = plots.get(activeAutoPlotIndex); - String line1 = !isEmpty(plot.getLabel()) ? plot.getLabel() : plot.getExpressionText(); - String line2 = "(" + formatValue(dataX) + ", " + formatValue(dataY) + ")"; - renderTooltipBox(context, sx, sy, line1, line2); } - private void renderTooltipBox(RenderContext context, float sx, float sy, String line1, String line2) { - int lineH = context.getLineHeight(TOOLTIP_BODY_STYLE); - int textWidth = Math - .max(context.getStringWidth(line1, TOOLTIP_TITLE_STYLE), context.getStringWidth(line2, TOOLTIP_BODY_STYLE)); - int boxWidth = textWidth + TOOLTIP_PADDING_X * 2; - int boxHeight = lineH * 2 + TOOLTIP_PADDING_Y * 2; - int boxX = (int) sx - boxWidth / 2; - int boxY = (int) sy - boxHeight - TOOLTIP_GAP; - if (boxY < bounds.y() + 2) { - boxY = (int) sy + TOOLTIP_GAP; - } - boxX = Math.clamp(boxX, bounds.x() + 2, bounds.right() - boxWidth - 2); - boxY = Math.clamp(boxY, bounds.y() + 2, bounds.bottom() - boxHeight - 2); - - LytRect box = new LytRect(boxX, boxY, boxWidth, boxHeight); - context.fillRect(box, 0xEE202428); - context.drawBorder(box, 0xFF555555, 1); - context.drawText(line1, boxX + TOOLTIP_PADDING_X, boxY + TOOLTIP_PADDING_Y, TOOLTIP_TITLE_STYLE); - context.drawText(line2, boxX + TOOLTIP_PADDING_X, boxY + TOOLTIP_PADDING_Y + lineH, TOOLTIP_BODY_STYLE); + private Optional createActiveTooltip() { + if (activeMarkedIndex >= 0 && activeMarkedIndex < points.size()) { + MarkedPoint point = points.get(activeMarkedIndex); + return coordinateTooltip( + !isEmpty(point.getLabel()) ? point.getLabel() : "Point", + activeMarkedDataX, + activeMarkedDataY); + } + if (activeAutoPlotIndex >= 0 && activeAutoPlotIndex < plots.size()) { + FunctionPlot plot = plots.get(activeAutoPlotIndex); + return coordinateTooltip( + !isEmpty(plot.getLabel()) ? plot.getLabel() : plot.getExpressionText(), + activeAutoDataX, + activeAutoDataY); + } + if (activePlotIndex < 0 || activePlotIndex >= plots.size()) { + return Optional.empty(); + } + FunctionPlot plot = plots.get(activePlotIndex); + double dataX; + double dataY; + if (plot.isInverse()) { + dataY = activeDataX; + dataX = plot.evaluate(dataY); + } else { + dataX = activeDataX; + dataY = plot.evaluate(dataX); + } + if (!Double.isFinite(dataX) || !Double.isFinite(dataY)) { + return Optional.empty(); + } + return coordinateTooltip(!isEmpty(plot.getLabel()) ? plot.getLabel() : plot.getExpressionText(), dataX, dataY); + } + + private Optional coordinateTooltip(String label, double dataX, double dataY) { + return Optional.of(new TextTooltip(label + "\n(" + formatValue(dataX) + ", " + formatValue(dataY) + ")")); } /** @@ -1363,11 +1364,4 @@ private static ResolvedTextStyle makeStyle(int argb, boolean bold) { null, false); } - - @SuppressWarnings("unused") - private int unusedDragButtonAccessor() { - // The drag button is captured for future use (e.g. distinguishing left/right behaviour) but - // is not consulted today; this accessor keeps it from being trimmed by static analysis. - return dragButton; - } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/indices/ItemIndex.java b/src/main/java/com/hfstudio/guidenh/guide/indices/ItemIndex.java index deb2cfd8..55b97f7d 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/indices/ItemIndex.java +++ b/src/main/java/com/hfstudio/guidenh/guide/indices/ItemIndex.java @@ -73,8 +73,7 @@ public static List> getItemAnchors(ParsedGuidePage page List itemIdList = normalizeItemIdEntries(page, itemIdsNode); if (itemIdList == null) { - GuideDebugLog - .warnAlways("[GuideNH] [ItemIndex] Page {} contains malformed item_ids frontmatter", page.getId()); + GuideDebugLog.warn("[GuideNH] [ItemIndex] Page {} contains malformed item_ids frontmatter", page.getId()); return List.of(); } @@ -97,7 +96,7 @@ public static List> getItemAnchors(ParsedGuidePage page page.getId() .getResourceDomain()); } catch (IllegalArgumentException e) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [ItemIndex] Page {} contains a malformed item_ids frontmatter entry: {}", page.getId(), listEntry); @@ -105,7 +104,7 @@ public static List> getItemAnchors(ParsedGuidePage page } if (itemId == null) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [ItemIndex] Page {} references an unknown item {} in its item_ids frontmatter", page.getId(), listEntry); @@ -114,7 +113,7 @@ public static List> getItemAnchors(ParsedGuidePage page itemAnchors.add(Pair.of(itemId, new PageAnchor(page.getId(), anchor))); } else { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [ItemIndex] Page {} contains a malformed item_ids frontmatter entry: {}", page.getId(), listEntry); @@ -132,9 +131,8 @@ private static List normalizeItemIdEntries(ParsedGuidePage page, Object itemI if (itemIdsNode instanceof String itemIdEntry) { String trimmed = itemIdEntry.trim(); if (trimmed.isEmpty()) { - GuideDebugLog.warnAlways( - "[GuideNH] [ItemIndex] Page {} contains an empty item_ids frontmatter entry", - page.getId()); + GuideDebugLog + .warn("[GuideNH] [ItemIndex] Page {} contains an empty item_ids frontmatter entry", page.getId()); return List.of(); } return List.of(trimmed); diff --git a/src/main/java/com/hfstudio/guidenh/guide/indices/OreIndex.java b/src/main/java/com/hfstudio/guidenh/guide/indices/OreIndex.java index 52d5b4e2..5f3c3e58 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/indices/OreIndex.java +++ b/src/main/java/com/hfstudio/guidenh/guide/indices/OreIndex.java @@ -115,8 +115,7 @@ public static List> getOreAnchors(ParsedGuidePage page) } if (!(oreIdsNode instanceof ListoreIdList)) { - GuideDebugLog - .warnAlways("[GuideNH] [OreIndex] Page {} contains malformed ore_ids frontmatter", page.getId()); + GuideDebugLog.warn("[GuideNH] [OreIndex] Page {} contains malformed ore_ids frontmatter", page.getId()); return List.of(); } @@ -126,14 +125,13 @@ public static List> getOreAnchors(ParsedGuidePage page) if (listEntry instanceof String oreName) { String trimmed = oreName.trim(); if (trimmed.isEmpty()) { - GuideDebugLog.warnAlways( - "[GuideNH] [OreIndex] Page {} contains an empty ore_ids frontmatter entry", - page.getId()); + GuideDebugLog + .warn("[GuideNH] [OreIndex] Page {} contains an empty ore_ids frontmatter entry", page.getId()); continue; } oreAnchors.add(Pair.of(trimmed, new PageAnchor(page.getId(), null))); } else { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [OreIndex] Page {} contains a malformed ore_ids frontmatter entry: {}", page.getId(), listEntry); diff --git a/src/main/java/com/hfstudio/guidenh/guide/indices/UniqueIndex.java b/src/main/java/com/hfstudio/guidenh/guide/indices/UniqueIndex.java index 3b98bda9..c9066391 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/indices/UniqueIndex.java +++ b/src/main/java/com/hfstudio/guidenh/guide/indices/UniqueIndex.java @@ -99,7 +99,7 @@ private void addToIndex(ParsedGuidePage page) { var value = entry.getValue(); var previousPage = index.putIfAbsent(key, new Record<>(page.getId(), value)); if (previousPage != null) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [UniqueIndex] Key conflict in index {}: {} is used by pages {} and {}; keeping {} and ignoring {}", name, key, diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideLightweightReloadService.java b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideLightweightReloadService.java index 761b8464..e7969533 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideLightweightReloadService.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideLightweightReloadService.java @@ -101,8 +101,7 @@ public static void reloadGuides(IResourceManager resourceManager) { GuideME.getSearch() .indexAll(); } catch (Throwable t) { - GuideDebugLog - .warnAlways("[GuideNH] [GuideLightweightReloadService] Failed to reindex search after reload", t); + GuideDebugLog.warn("[GuideNH] [GuideLightweightReloadService] Failed to reindex search after reload", t); } } @@ -222,11 +221,8 @@ private static ParsedGuidePage parsePageBytes(String sourcePack, String language return GuideLocalizedPageSourceResolver .parseFrontmatterOnly(sourcePack, language, contentRootFolder, pageId, bytes); } catch (Exception ex) { - GuideDebugLog.warnAlways( - "[GuideNH] [GuideLightweightReloadService] Error parsing page {} from {}", - pageId, - sourceId, - ex); + GuideDebugLog + .warn("[GuideNH] [GuideLightweightReloadService] Error parsing page {} from {}", pageId, sourceId, ex); return null; } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideMEClientProxy.java b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideMEClientProxy.java index b5dc041d..52beb7ea 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideMEClientProxy.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideMEClientProxy.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable; +import com.hfstudio.guidenh.client.GuideNhClientTaskScheduler; import com.hfstudio.guidenh.guide.PageAnchor; public class GuideMEClientProxy extends GuideMEServerProxy { @@ -20,6 +21,9 @@ public boolean openGuide(EntityPlayer player, ResourceLocation guideId, @Nullabl public boolean reloadResources() { var mc = Minecraft.getMinecraft(); if (mc == null) return false; - return GuideMEClientReloadDispatcher.dispatch(mc.func_152345_ab(), mc::func_152344_a, mc::refreshResources); + return GuideMEClientReloadDispatcher.dispatch( + GuideNhClientTaskScheduler.isOnClientThread(), + GuideNhClientTaskScheduler::execute, + mc::refreshResources); } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideScreen.java b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideScreen.java index c81a7347..63da4360 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideScreen.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideScreen.java @@ -117,6 +117,7 @@ import com.hfstudio.guidenh.guide.internal.screen.GuideIconButton; import com.hfstudio.guidenh.guide.internal.screen.GuideNavBar; import com.hfstudio.guidenh.guide.internal.screen.GuideNavBar.ContextTarget; +import com.hfstudio.guidenh.guide.internal.screen.GuideNavBar.ExpansionChange; import com.hfstudio.guidenh.guide.internal.screen.GuideNavBarState; import com.hfstudio.guidenh.guide.internal.search.GuideItemLinksPage; import com.hfstudio.guidenh.guide.internal.search.GuideSearchPage; @@ -307,6 +308,8 @@ private static LytDocument buildLoadingDocument() { private LytDocument searchDocument; @Nullable private String cachedSearchQuery; + private long cachedSearchIndexRevision = -1L; + private long searchDocumentRebuildAfterNanos; // Tracks the item stack whose tooltip was rendered last frame, for the G-key disambiguation hotkey. @Nullable @@ -388,6 +391,7 @@ private static LytDocument buildLoadingDocument() { public static final int SEARCH_RESULT_ICON_AND_GAP = 22; public static final int SEARCH_RESULT_TITLE_GAP = 8; public static final int SEARCH_PATH_MAX_CHARS = 20; + private static final long SEARCH_QUERY_DEBOUNCE_NANOS = 75_000_000L; public static final String ASCII_ELLIPSIS = "..."; public static final int SEARCH_TOOLBAR_FIELD_Y_OFFSET = 5; private static final int SPECIAL_SEARCH_BACKGROUND_PADDING_X = 4; @@ -540,17 +544,13 @@ private GuideScreen(GuideScreenRoute route, @Nullable GuideScreenViewState resto bookmarkState, route.isContent() && currentAnchor != null ? currentAnchor.pageId() : null, null); - navBar.setOnExpansionToggled((toggledGuideId, pageId, expanded) -> { - if (toggledGuideId != null) { - updateSavedExpansionState(toggledGuideId, pageId, expanded); - } + navBar.setOnExpansionChanged((changes, allCollapsed) -> { ResourceLocation currentGuideId = guide != null ? guide.getId() : null; - if (!Objects.equals(currentGuideId, toggledGuideId)) { + updateSavedExpansionStates(changes, currentGuideId); + if (allCollapsed || changes.stream() + .anyMatch(change -> !Objects.equals(currentGuideId, change.guideId()))) { rememberNavigationState(); } - if (toggledGuideId == null && currentGuideId != null) { - updateSavedExpansionState(null, pageId, expanded); - } }); } @@ -615,7 +615,7 @@ private static GuideScreenViewState contentState(ResourceLocation guideId, @Null private static GuideScreenRoute contentRoute(ResourceLocation guideId, @Nullable PageAnchor anchor) { MutableGuide guide = GuideRegistry.getById(guideId); if (guide == null) { - GuideDebugLog.warnAlways("GuideScreen.open: no guide registered with id {}", guideId); + GuideDebugLog.warn("GuideScreen.open: no guide registered with id {}", guideId); return null; } if (anchor == null) { @@ -764,6 +764,7 @@ private void restoreViewState(GuideScreenViewState state) { ensureLayout(); scrollToCurrentAnchor(); clampScroll(); + rebuildToolbar(); if (isGuideEditorActive()) { refreshGuideEditorDraft(true); } @@ -806,23 +807,40 @@ private void rememberNavigationState() { .rememberNavBarState(guide != null ? guide.getId() : null, navBar.captureState()); } - private void updateSavedExpansionState(@Nullable ResourceLocation guideId, ResourceLocation pageId, - boolean expanded) { - GuideNavBarState saved = ClientProxy.getLytHost() - .getNavigation() - .recallNavigationState(guideId); - LinkedHashSet updated = new LinkedHashSet<>( - saved.expandedPageIds() != null ? saved.expandedPageIds() : Collections.emptySet()); - if (expanded) { - updated.add(pageId); - } else { - updated.remove(pageId); - } - ClientProxy.getLytHost() - .getNavigation() - .rememberNavBarState( + private void updateSavedExpansionStates(List changes, @Nullable ResourceLocation currentGuideId) { + Map savedStates = new LinkedHashMap<>(); + Map> expandedPageIdsByGuide = new LinkedHashMap<>(); + for (ExpansionChange change : changes) { + ResourceLocation guideId = change.guideId(); + if (guideId == null && currentGuideId == null) { + continue; + } + GuideNavBarState saved = savedStates.computeIfAbsent( + guideId, + ignored -> ClientProxy.getLytHost() + .getNavigation() + .recallNavigationState(guideId)); + LinkedHashSet expandedPageIds = expandedPageIdsByGuide.computeIfAbsent( guideId, - GuideNavBarState.create(saved.bookmarkGroupExpanded(), updated, saved.scrollY())); + ignored -> new LinkedHashSet<>( + saved.expandedPageIds() != null ? saved.expandedPageIds() : Collections.emptySet())); + if (change.expanded()) { + expandedPageIds.add(change.pageId()); + } else { + expandedPageIds.remove(change.pageId()); + } + } + for (Map.Entry entry : savedStates.entrySet()) { + GuideNavBarState saved = entry.getValue(); + ClientProxy.getLytHost() + .getNavigation() + .rememberNavBarState( + entry.getKey(), + GuideNavBarState.create( + saved.bookmarkGroupExpanded(), + expandedPageIdsByGuide.get(entry.getKey()), + saved.scrollY())); + } } private boolean isNavigationNewPageButtonVisible() { @@ -1200,8 +1218,7 @@ private void createGuideEditorPageAtPath(MutableGuide activeGuide, ParsedGuidePa Path sourceRoot = guideEditorFileStore .findWritablePageResourcePackRoot(activeGuide, currentParsedPage.getId(), language); if (sourceRoot == null) { - GuideDebugLog - .warnAlways("Failed to create guide editor page because current page has no writable resource pack"); + GuideDebugLog.warn("Failed to create guide editor page because current page has no writable resource pack"); return; } @@ -1510,7 +1527,7 @@ private void rebuildGuideEditorPreview() { } cachedGuideEditorPreviewInteractionState = null; } catch (Throwable t) { - GuideDebugLog.warnAlways("Failed to compile guide editor preview for {}", currentAnchor.pageId(), t); + GuideDebugLog.warn("Failed to compile guide editor preview for {}", currentAnchor.pageId(), t); } } @@ -1761,7 +1778,7 @@ private void refreshGuideEditorPreviewState() { scheduleGuideEditorNavigationRefresh(); } } catch (Throwable t) { - GuideDebugLog.warnAlways("Failed to refresh guide editor draft state for {}", currentAnchor.pageId(), t); + GuideDebugLog.warn("Failed to refresh guide editor draft state for {}", currentAnchor.pageId(), t); } } @@ -1862,7 +1879,7 @@ private void updateGuideEditorNavigationRefresh() { GuideME.getSearch() .index(guide); } catch (Throwable t) { - GuideDebugLog.warnAlways("Guide editor navigation refresh failed", t); + GuideDebugLog.warn("Guide editor navigation refresh failed", t); } } @@ -2227,7 +2244,6 @@ protected void actionPerformed(GuiButton btn) { bookmarkState, null, null); - rebuildToolbar(); }); } else if (btn == btnGuideEditorToggle) { toggleGuideEditorEnabled(); @@ -2289,7 +2305,6 @@ private void navigateBackInHistory() { .pageId() : null, carryOver); } - rebuildToolbar(); } }); } @@ -2327,7 +2342,6 @@ private void navigateForwardInHistory() { .pageId() : null, carryOver); } - rebuildToolbar(); } }); } @@ -2856,18 +2870,19 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { } drawButtonTooltip(mouseX, mouseY); debugOverlay.onFrameStart(); + var activeDocument = getActiveDocument(); debugOverlay.render( width, height, mouseX, mouseY, contentX, - getDocumentViewportY(), + activeDocument != null ? getDocumentRenderY(activeDocument) : getDocumentViewportY(), contentW, getDocumentViewportHeight(), Math.round(visualScrollY), currentZoom, - layoutDocument, + activeDocument, fontRendererObj); } @@ -2922,12 +2937,12 @@ private static ResourceLocation getHomeLogoTexture() { try (InputStream inputStream = GuideScreen.class.getResourceAsStream(HOME_LOGO_RESOURCE_PATH)) { if (inputStream == null) { - GuideDebugLog.warnAlways("GuideScreen home logo resource not found at {}", HOME_LOGO_RESOURCE_PATH); + GuideDebugLog.warn("GuideScreen home logo resource not found at {}", HOME_LOGO_RESOURCE_PATH); return null; } BufferedImage image = ImageIO.read(inputStream); if (image == null) { - GuideDebugLog.warnAlways("GuideScreen home logo failed to decode at {}", HOME_LOGO_RESOURCE_PATH); + GuideDebugLog.warn("GuideScreen home logo failed to decode at {}", HOME_LOGO_RESOURCE_PATH); return null; } homeLogoWidth = image.getWidth(); @@ -2937,7 +2952,7 @@ private static ResourceLocation getHomeLogoTexture() { .getDynamicTextureLocation(HOME_LOGO_SOURCE.getResourcePath(), new DynamicTexture(image)); return homeLogoTexture; } catch (Exception e) { - GuideDebugLog.warnAlways("GuideScreen failed to load home logo from {}", HOME_LOGO_RESOURCE_PATH, e); + GuideDebugLog.warn("GuideScreen failed to load home logo from {}", HOME_LOGO_RESOURCE_PATH, e); return null; } } @@ -3160,7 +3175,7 @@ private void renderGuideEditorPreview(int x, int y, int width, int height) { try { previewDocument.render(reusableRenderCtx); } catch (Throwable t) { - GuideDebugLog.warnAlways("Failed to render guide editor preview", t); + GuideDebugLog.warn("Failed to render guide editor preview", t); } finally { GL11.glPopMatrix(); reusableRenderCtx.restoreExternalRenderState(); @@ -4262,7 +4277,7 @@ private void drawContentTooltip(ContentTooltip ct, int mouseX, int mouseY, ct.getContent() .render(ctx); } catch (Throwable t) { - GuideDebugLog.warnAlways("Error rendering ContentTooltip", t); + GuideDebugLog.warn("Error rendering ContentTooltip", t); } finally { GL11.glPopMatrix(); ctx.restoreExternalRenderState(); @@ -4650,7 +4665,7 @@ private boolean canSearchCurrentView() { private void drawTiledBackground() { drawRect(0, 0, this.width, this.height, BACKGROUND_DIM_COLOR); if (mc == null || mc.getTextureManager() == null) { - GuideDebugLog.warnAlways("[GuideNH] drawTiledBackground: mc or textureManager is null, skipping"); + GuideDebugLog.warn("[GuideNH] drawTiledBackground: mc or textureManager is null, skipping"); return; } mc.getTextureManager() @@ -4901,10 +4916,8 @@ protected void mouseClicked(int mouseX, int mouseY, int button) { } if (button == 1 && navBar.contains(mouseX, mouseY)) { ContextTarget contextTarget = navBar.getContextTarget(mouseX, mouseY); - if (contextTarget != null) { - openNavBarContextMenu(mouseX, mouseY, contextTarget); - return; - } + openNavBarContextMenu(mouseX, mouseY, contextTarget); + return; } if (GuideScreenNeiBridge.mouseClicked(this, mouseX, mouseY, button)) { return; @@ -4916,7 +4929,8 @@ protected void mouseClicked(int mouseX, int mouseY, int button) { guide != null ? guide.getId() : null, currentAnchor != null ? currentAnchor.pageId() : null, bookmarkState, - isNavigationNewPageButtonVisible()); + isNavigationNewPageButtonVisible(), + Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT)); if (result != null && result.pinToggle()) { toggleNavigationPinned(); mc.getSoundHandler() @@ -5119,7 +5133,7 @@ private void openHomePageContextMenu(int mouseX, int mouseY) { homePageContextMenu.open(mouseX, mouseY, width, height, fontRendererObj); } - private void openNavBarContextMenu(int mouseX, int mouseY, ContextTarget contextTarget) { + private void openNavBarContextMenu(int mouseX, int mouseY, @Nullable ContextTarget contextTarget) { closeHomePageContextMenu(); closeGuideEditorContextMenu(); closeNavBarContextMenu(); @@ -5164,6 +5178,14 @@ private void performHomePageContextMenuAction(GuideScreenContextMenu.ContextMenu private void performNavBarContextMenuAction(GuideScreenContextMenu.ContextMenuAction action) { ContextTarget contextTarget = navBarContextTarget; closeNavBarContextMenu(); + if (action == GuideScreenContextMenu.ContextMenuAction.EXPAND_ALL) { + navBar.expandAll(resolveNavigationTree(), bookmarkState); + return; + } + if (action == GuideScreenContextMenu.ContextMenuAction.COLLAPSE_ALL) { + navBar.collapseAll(resolveNavigationTree(), bookmarkState); + return; + } if (action == GuideScreenContextMenu.ContextMenuAction.OPEN_SPECIAL_PAGES) { openSpecialPagesFromContextMenu(contextTarget); return; @@ -5193,8 +5215,17 @@ private void openSpecialPagesFromContextMenu(@Nullable ContextTarget contextTarg .specialPageId(activeGuide.getDefaultNamespace(), MediaWikiSpecialPageIds.SPECIAL_PAGES))); } - private List buildNavBarContextMenuEntries(ContextTarget contextTarget) { + private List buildNavBarContextMenuEntries(@Nullable ContextTarget contextTarget) { List entries = new ArrayList<>(); + entries.add( + GuideScreenContextMenu.Entry + .action(GuidebookText.NavBarExpandAll.text(), GuideScreenContextMenu.ContextMenuAction.EXPAND_ALL)); + entries.add( + GuideScreenContextMenu.Entry + .action(GuidebookText.NavBarCollapseAll.text(), GuideScreenContextMenu.ContextMenuAction.COLLAPSE_ALL)); + if (contextTarget == null) { + return entries; + } entries.add( GuideScreenContextMenu.Entry.action( GuidebookText.NavBarSpecialPages.text(), @@ -5223,10 +5254,8 @@ private boolean handleHomePageContextMenuClick(int mouseX, int mouseY, int butto } if (button == 1 && navBar.contains(mouseX, mouseY)) { ContextTarget contextTarget = navBar.getContextTarget(mouseX, mouseY); - if (contextTarget != null) { - openNavBarContextMenu(mouseX, mouseY, contextTarget); - return true; - } + openNavBarContextMenu(mouseX, mouseY, contextTarget); + return true; } boolean handled = homePageContextMenu.mouseClicked( mouseX, @@ -5257,10 +5286,8 @@ private boolean handleNavBarContextMenuClick(int mouseX, int mouseY, int button) } if (button == 1 && navBar.contains(mouseX, mouseY)) { ContextTarget contextTarget = navBar.getContextTarget(mouseX, mouseY); - if (contextTarget != null) { - openNavBarContextMenu(mouseX, mouseY, contextTarget); - return true; - } + openNavBarContextMenu(mouseX, mouseY, contextTarget); + return true; } boolean handled = navBarContextMenu .mouseClicked(mouseX, mouseY, button, this::performNavBarContextMenuAction, fontRendererObj, width, height); @@ -5780,7 +5807,6 @@ protected void mouseMovedOrUp(int mouseX, int mouseY, int state) { draggingDocument = false; return; } - if (state == 0) {} } @Nullable @@ -6087,7 +6113,6 @@ public void navigateTo(PageAnchor anchor) { suppressGuideEditorTextFocusUntilGuideHotkeyRelease(); rememberCurrentContentStateIfEligible(); restoreViewState(GuideScreenViewState.of(GuideScreenRoute.content(guide.getId(), anchor), 0)); - rebuildToolbar(); }); } @@ -6118,7 +6143,6 @@ public void navigateTo(ResourceLocation guideId, PageAnchor anchor) { bookmarkState, anchor.pageId(), carryOver); - rebuildToolbar(); }); } @@ -6472,13 +6496,22 @@ private void rebuildSearchDocumentIfNeeded(boolean force) { } String query = GuideSearchPage.queryFromAnchor(currentAnchor); - if (!force && searchDocument != null && Objects.equals(cachedSearchQuery, query)) { + if (!force && System.nanoTime() < searchDocumentRebuildAfterNanos) { + return; + } + long searchIndexRevision = GuideME.getSearch() + .getIndexRevision(); + if (!force && searchDocument != null + && Objects.equals(cachedSearchQuery, query) + && cachedSearchIndexRevision == searchIndexRevision) { return; } clearInteractionState(); cachedSearchQuery = query; searchDocument = buildSearchDocument(query); + cachedSearchIndexRevision = searchIndexRevision; + searchDocumentRebuildAfterNanos = 0L; layoutDocument = null; lastLayoutWidth = -1; } @@ -6627,7 +6660,7 @@ private LytDocument buildSearchDocument(String query) { clipSnippetForWidth(result.text(), getSearchSnippetLineWidth(textColumnWidth)))); } } catch (Throwable t) { - GuideDebugLog.warnAlways("Search failed", t); + GuideDebugLog.warn("Search failed", t); } return GuideSearchResultDocumentBuilder @@ -6765,7 +6798,7 @@ private int getDocumentSearchInset() { + SPECIAL_SEARCH_DIVIDER_HEIGHT + 6; } - return isSearchPage() ? SEARCH_FIELD_H + 14 : 0; + return 0; } private boolean handleSearchFieldKey(char typedChar, int keyCode) { @@ -6835,7 +6868,8 @@ private void updateSearchQuery(String query) { currentPage = null; document = null; refreshCurrentPageTitle(); - rebuildSearchDocumentIfNeeded(true); + searchDocumentRebuildAfterNanos = query.isEmpty() ? 0L : System.nanoTime() + SEARCH_QUERY_DEBOUNCE_NANOS; + rebuildSearchDocumentIfNeeded(false); scrollY = 0; snapVisualScrollToTarget(); invalidateScrollbarOutline(); diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideScreenContextMenu.java b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideScreenContextMenu.java index a7618f6b..6472b981 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideScreenContextMenu.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideScreenContextMenu.java @@ -32,6 +32,8 @@ public interface Listener { } public enum ContextMenuAction { + EXPAND_ALL, + COLLAPSE_ALL, OPEN_SPECIAL_PAGES, CREATE_NEW_PAGE, OPEN_CONTAINING_FOLDER diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideSourceWatcher.java b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideSourceWatcher.java index 2a8e4c7a..bcb88776 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideSourceWatcher.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideSourceWatcher.java @@ -754,10 +754,8 @@ private PageSource resolveActivePageSource(ResourceLocation pageId, String curre Map entries = GuidePageLanguageIndex.readPageKeys(input); return entries.get(GuideLocalizedPageSourceResolver.buildLangKey(contentRootFolder, pageId)); } catch (IOException e) { - GuideDebugLog.warnAlways( - "[GuideNH] [GuideSourceWatcher] Failed to read localized page lang file {}", - langFilePath, - e); + GuideDebugLog + .warn("[GuideNH] [GuideSourceWatcher] Failed to read localized page lang file {}", langFilePath, e); return null; } } @@ -795,7 +793,7 @@ private Set resolveNamespacesForLocalizedSources(@Nullable String namesp child.getFileName() .toString())); } catch (IOException e) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [GuideSourceWatcher] Failed to scan localized source namespaces in {}", assetsPath, e); @@ -825,10 +823,8 @@ private Map loadLocalizedSourceOverridesForNamespace(String sour try (InputStream input = Files.newInputStream(langFilePath)) { return GuidePageLanguageIndex.readPageKeys(input); } catch (IOException e) { - GuideDebugLog.warnAlways( - "[GuideNH] [GuideSourceWatcher] Failed to read localized page lang file {}", - langFilePath, - e); + GuideDebugLog + .warn("[GuideNH] [GuideSourceWatcher] Failed to read localized page lang file {}", langFilePath, e); return Map.of(); } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/GuidebookText.java b/src/main/java/com/hfstudio/guidenh/guide/internal/GuidebookText.java index d42031d0..0b5033e1 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/GuidebookText.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/GuidebookText.java @@ -268,6 +268,8 @@ public enum GuidebookText implements LocalizationEnum { HomePage, HomePageSpecialPages, NavBarSpecialPages, + NavBarExpandAll, + NavBarCollapseAll, SpecialPageShowMore, SiteExportNoPages, SiteExportOpenGuide, diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/MutableGuide.java b/src/main/java/com/hfstudio/guidenh/guide/internal/MutableGuide.java index 1e649f31..a3cd6885 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/MutableGuide.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/MutableGuide.java @@ -175,7 +175,7 @@ public T getIndex(Class indexClass) { @Nullable public ParsedGuidePage getParsedPage(ResourceLocation id) { if (pages == null) { - GuideDebugLog.warnAlways("[GuideNH] [MutableGuide] Can't get page {}. Pages not loaded yet.", id); + GuideDebugLog.warn("[GuideNH] [MutableGuide] Can't get page {}. Pages not loaded yet.", id); return null; } @@ -732,7 +732,7 @@ private void requestMediaWikiDerivedCacheWarmup(long revision) { requestedMediaWikiWarmupRevision = Long.MIN_VALUE; } } - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [MutableGuide] Failed to warm MediaWiki caches asynchronously for guide {} revision {}", id, revision, diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/datadriven/DataDrivenGuideLoader.java b/src/main/java/com/hfstudio/guidenh/guide/internal/datadriven/DataDrivenGuideLoader.java index a97c50f5..371ec6e2 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/datadriven/DataDrivenGuideLoader.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/datadriven/DataDrivenGuideLoader.java @@ -233,7 +233,7 @@ private static void scanZipBuildIndex(File resourcePackFile, String folder, } } } catch (IOException e) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [DataDrivenGuideLoader] Failed to scan guide pages from resource pack {}", resourcePackFile.getAbsolutePath(), e); @@ -433,7 +433,7 @@ public static void scanZipPagePaths(File resourcePackFile, String prefix, Set reso var basePacks = accessor.guidenh$getResourcePackList(); if (basePacks != null) resourcePacks.addAll(basePacks); } catch (RuntimeException e) { - GuideDebugLog.warnAlways("[GuideNH] [DataDrivenGuideLoader] Failed to inspect base resource packs", e); + GuideDebugLog.warn("[GuideNH] [DataDrivenGuideLoader] Failed to inspect base resource packs", e); } var repository = Minecraft.getMinecraft() .getResourcePackRepository(); @@ -588,7 +588,7 @@ private static void addResourceManagerResourcePacks(IResourceManager resourceMan } } } catch (RuntimeException e) { - GuideDebugLog.warnAlways("[GuideNH] [DataDrivenGuideLoader] Failed to inspect resource manager packs", e); + GuideDebugLog.warn("[GuideNH] [DataDrivenGuideLoader] Failed to inspect resource manager packs", e); } } @@ -613,7 +613,7 @@ public static byte[] readBytes(IResourcePack resourcePack, ResourceLocation reso } catch (IOException e) { return null; } catch (RuntimeException e) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [DataDrivenGuideLoader] readBytes failed for {} from pack {}: {}", resourceLocation, resourcePack.getPackName(), diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/debug/DebugComponent.java b/src/main/java/com/hfstudio/guidenh/guide/internal/debug/DebugComponent.java index 43a3fb5f..8eef26fd 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/debug/DebugComponent.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/debug/DebugComponent.java @@ -99,4 +99,58 @@ public int getPriority() { return priority; } } + + record LineComponentEntry(String name, int x1, int y1, int x2, int y2, int tolerance, @Nullable String extraInfo, + int priority) implements ComponentEntry { + + @Override + public String getName() { + return name; + } + + @Override + public @Nullable String getExtraInfo() { + return extraInfo; + } + + @Override + public int getPriority() { + return priority; + } + + @Override + public LytRect getBounds() { + int padding = Math.max(1, tolerance); + return new LytRect( + Math.min(x1, x2) - padding, + Math.min(y1, y2) - padding, + Math.abs(x2 - x1) + padding * 2 + 1, + Math.abs(y2 - y1) + padding * 2 + 1); + } + + @Override + public boolean containsPoint(int x, int y) { + int padding = Math.max(1, tolerance); + int minX = Math.min(x1, x2) - padding; + int maxX = Math.max(x1, x2) + padding; + int minY = Math.min(y1, y2) - padding; + int maxY = Math.max(y1, y2) + padding; + if (x < minX || x > maxX || y < minY || y > maxY) { + return false; + } + float dx = x2 - x1; + float dy = y2 - y1; + float lengthSquared = dx * dx + dy * dy; + if (lengthSquared == 0) { + return Math.abs(x - x1) <= tolerance && Math.abs(y - y1) <= tolerance; + } + float progress = ((x - x1) * dx + (y - y1) * dy) / lengthSquared; + progress = Math.clamp(progress, 0F, 1F); + float nearestX = x1 + progress * dx; + float nearestY = y1 + progress * dy; + float offsetX = x - nearestX; + float offsetY = y - nearestY; + return offsetX * offsetX + offsetY * offsetY <= tolerance * tolerance; + } + } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/debug/ElementHoverDetector.java b/src/main/java/com/hfstudio/guidenh/guide/internal/debug/ElementHoverDetector.java index 358ade46..650e0ba9 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/debug/ElementHoverDetector.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/debug/ElementHoverDetector.java @@ -1,8 +1,5 @@ package com.hfstudio.guidenh.guide.internal.debug; -import java.util.ArrayList; -import java.util.List; - import org.jetbrains.annotations.Nullable; import com.hfstudio.guidenh.config.ModConfig; @@ -30,14 +27,13 @@ public HoveredElementInfo detectHoveredElement(LytDocument document, int mouseX, DebugInfoExtractorInit.init(); - List candidates = new ArrayList<>(); - collectHoveredNodes(document, mouseX, mouseY, null, candidates); - - return selectBestCandidate(candidates); + HoveredCandidate bestCandidate = new HoveredCandidate(); + collectHoveredNodes(document, mouseX, mouseY, null, 0, bestCandidate); + return bestCandidate.info; } private void collectHoveredNodes(LytNode node, int mouseX, int mouseY, @Nullable HoveredElementInfo parentInfo, - List candidates) { + int depth, HoveredCandidate bestCandidate) { LytRect bounds = node.getBounds(); @@ -51,15 +47,14 @@ private void collectHoveredNodes(LytNode node, int mouseX, int mouseY, @Nullable info.setCumulativeScrollOffset(cumulativeScrollX, cumulativeScrollY); - int depth = calculateDepth(node); - candidates.add(new HoveredCandidate(info, depth, bounds.width() * bounds.height())); + bestCandidate.consider(info, depth, bounds.width() * bounds.height()); if (node instanceof DebugComponent debugComponent) { - collectDebugComponents(debugComponent, mouseX, mouseY, info, depth, candidates); + collectDebugComponents(debugComponent, mouseX, mouseY, info, depth, bestCandidate); } if (node instanceof DebugFlowContainer flowContainer) { - collectFlowContent(flowContainer, mouseX, mouseY, info, depth, candidates); + collectFlowContent(flowContainer, mouseX, mouseY, info, depth, bestCandidate); } // Calculate cumulative scroll offset for children: parent's cumulative + this node's offset @@ -92,17 +87,17 @@ private void collectHoveredNodes(LytNode node, int mouseX, int mouseY, @Nullable } for (LytNode child : node.getChildren()) { - collectHoveredNodes(child, adjustedMouseX, adjustedMouseY, childParentInfo, candidates); + collectHoveredNodes(child, adjustedMouseX, adjustedMouseY, childParentInfo, depth + 1, bestCandidate); } } else if (bounds == null) { for (LytNode child : node.getChildren()) { - collectHoveredNodes(child, mouseX, mouseY, parentInfo, candidates); + collectHoveredNodes(child, mouseX, mouseY, parentInfo, depth + 1, bestCandidate); } } } private void collectDebugComponents(DebugComponent debugComponent, int mouseX, int mouseY, - HoveredElementInfo parentInfo, int parentDepth, List candidates) { + HoveredElementInfo parentInfo, int parentDepth, HoveredCandidate bestCandidate) { for (DebugComponent.ComponentEntry component : debugComponent.getDebugComponents()) { if (component.containsPoint(mouseX, mouseY)) { @@ -134,13 +129,13 @@ private void collectDebugComponents(DebugComponent debugComponent, int mouseX, i .width() * component.getBounds() .height(); - candidates.add(new HoveredCandidate(info, effectiveDepth, area)); + bestCandidate.consider(info, effectiveDepth, area); } } } private void collectFlowContent(DebugFlowContainer flowContainer, int mouseX, int mouseY, - HoveredElementInfo parentInfo, int parentDepth, List candidates) { + HoveredElementInfo parentInfo, int parentDepth, HoveredCandidate bestCandidate) { DebugFlowContainer.FlowContentEntry entry = flowContainer.pickFlowContent(mouseX, mouseY); if (entry != null && entry.bounds() @@ -171,36 +166,10 @@ private void collectFlowContent(DebugFlowContainer flowContainer, int mouseX, in .width() * entry.bounds() .height(); - candidates.add(new HoveredCandidate(info, parentDepth + 1, area)); + bestCandidate.consider(info, parentDepth + 1, area); } } - @Nullable - private HoveredElementInfo selectBestCandidate(List candidates) { - if (candidates.isEmpty()) { - return null; - } - - HoveredCandidate best = candidates.get(0); - for (HoveredCandidate candidate : candidates) { - if (candidate.depth > best.depth || (candidate.depth == best.depth && candidate.area < best.area)) { - best = candidate; - } - } - - return best.info; - } - - private int calculateDepth(LytNode node) { - int depth = 0; - LytNode current = node.getParent(); - while (current != null) { - depth++; - current = current.getParent(); - } - return depth; - } - private HoveredElementInfo createElementInfo(LytNode node, LytRect bounds, @Nullable HoveredElementInfo parentInfo) { String className = node.getClass() @@ -235,14 +204,17 @@ private void addBasicInfo(LytNode node, HoveredElementInfo info) { private static class HoveredCandidate { - final HoveredElementInfo info; - final int depth; - final float area; + @Nullable + private HoveredElementInfo info; + private int depth = Integer.MIN_VALUE; + private float area = Float.POSITIVE_INFINITY; - HoveredCandidate(HoveredElementInfo info, int depth, float area) { - this.info = info; - this.depth = depth; - this.area = area; + private void consider(HoveredElementInfo info, int depth, float area) { + if (depth > this.depth || depth == this.depth && area < this.area) { + this.info = info; + this.depth = depth; + this.area = area; + } } } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/editor/io/SceneEditorOffscreenFramebuffer.java b/src/main/java/com/hfstudio/guidenh/guide/internal/editor/io/SceneEditorOffscreenFramebuffer.java index e7bcc434..91b61ea7 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/editor/io/SceneEditorOffscreenFramebuffer.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/editor/io/SceneEditorOffscreenFramebuffer.java @@ -1,6 +1,7 @@ package com.hfstudio.guidenh.guide.internal.editor.io; import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; import java.nio.ByteBuffer; import net.minecraft.client.Minecraft; @@ -129,15 +130,18 @@ private BufferedImage readPixels() { GL11.glReadPixels(0, 0, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + int[] argbPixels = ((DataBufferInt) image.getRaster() + .getDataBuffer()).getData(); for (int y = 0; y < height; y++) { - int flippedY = height - 1 - y; + int sourceOffset = y * width * 4; + int targetOffset = (height - 1 - y) * width; for (int x = 0; x < width; x++) { - int index = (x + y * width) * 4; + int index = sourceOffset + x * 4; int r = buffer.get(index) & 0xFF; int g = buffer.get(index + 1) & 0xFF; int b = buffer.get(index + 2) & 0xFF; int a = buffer.get(index + 3) & 0xFF; - image.setRGB(x, flippedY, (a << 24) | (r << 16) | (g << 8) | b); + argbPixels[targetOffset + x] = a << 24 | r << 16 | g << 8 | b; } } return image; diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/host/scripts/SceneScript.java b/src/main/java/com/hfstudio/guidenh/guide/internal/host/scripts/SceneScript.java index 3ddf5ff4..344c5b55 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/host/scripts/SceneScript.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/host/scripts/SceneScript.java @@ -484,7 +484,7 @@ private static class ExceptionCollector implements LytErrorSink { @Override public void appendError(PageCompiler compiler, String text, UnistNode node) { - GuideDebugLog.warnAlways("[GuideNH] [SceneScript] {}", text); + GuideDebugLog.warn("[GuideNH] [SceneScript] {}", text); } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/item/GuideDisplayItemStacks.java b/src/main/java/com/hfstudio/guidenh/guide/internal/item/GuideDisplayItemStacks.java index a68e5039..76b20bc9 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/item/GuideDisplayItemStacks.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/item/GuideDisplayItemStacks.java @@ -174,9 +174,9 @@ private static String metaCacheKey(Item item, int meta) { private static void warnOnce(String key, String message, Object arg, @Nullable Throwable t) { if (WARNED_DISPLAY_FAILURES.add(key)) { if (t == null) { - GuideDebugLog.warnAlways(message, arg); + GuideDebugLog.warn(message, arg); } else { - GuideDebugLog.warnAlways(message, arg, t); + GuideDebugLog.warn(message, arg, t); } } } @@ -184,9 +184,9 @@ private static void warnOnce(String key, String message, Object arg, @Nullable T private static void warnOnce(String key, String message, Object arg1, Object arg2, @Nullable Throwable t) { if (WARNED_DISPLAY_FAILURES.add(key)) { if (t == null) { - GuideDebugLog.warnAlways(message, arg1, arg2); + GuideDebugLog.warn(message, arg1, arg2); } else { - GuideDebugLog.warnAlways(message, arg1, arg2, t); + GuideDebugLog.warn(message, arg1, arg2, t); } } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideLocalizedFrontmatterMerger.java b/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideLocalizedFrontmatterMerger.java index d928a9bf..3b382f20 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideLocalizedFrontmatterMerger.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideLocalizedFrontmatterMerger.java @@ -22,7 +22,7 @@ public static String merge(String fallbackSource, String localizedSource) { long t1 = System.nanoTime(); long totalUs = (t1 - t0) / 1000; if (totalUs > 1_000) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [FrontmatterMerger] merge() took {}us, fallbackLen={} localizedLen={}", totalUs, fallbackSource.length(), @@ -64,7 +64,7 @@ private static String mergeInternal(String fallbackSource, String localizedSourc long t5 = System.nanoTime(); long totalUs = (t5 - t0) / 1000; if (totalUs > 1_000) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [FrontmatterMerger] mergeInternal normalize={}us split={}us yamlLoad={}us mergeKeys={}us writeMap={}us total={}us", (t1 - t0) / 1000, (t2 - t1) / 1000, diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideLocalizedPageSourceResolver.java b/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideLocalizedPageSourceResolver.java index f6025a13..d830c1d5 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideLocalizedPageSourceResolver.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideLocalizedPageSourceResolver.java @@ -46,7 +46,7 @@ public static ParsedGuidePage parseFrontmatterOnly(String sourcePack, String lan long t2 = System.nanoTime(); long totalUs = (t2 - t0) / 1000; if (totalUs > 5_000) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [PageSourceResolver] parseFrontmatterOnly {} resolve={}us parse={}us total={}us", pageId, (t1 - t0) / 1000, @@ -82,7 +82,7 @@ public static ResolvedGuidePageSource resolve(String language, String contentRoo long t3 = System.nanoTime(); long totalUs = (t3 - t0) / 1000; if (totalUs > 2_000) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [PageSourceResolver] resolve {} i18nLookup={}us newString={}us merge={}us total={}us langKey={}", pageId, (t1 - t0) / 1000, diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuidePageLanguageIndex.java b/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuidePageLanguageIndex.java index 8bd0729c..b77cb9d3 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuidePageLanguageIndex.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuidePageLanguageIndex.java @@ -84,7 +84,7 @@ private static Map loadLanguage(String normalizedLanguage) { loadResourcePackLanguage(resourcePack, normalizedLanguage, merged); long packNs = System.nanoTime() - packStartedAt; if (packNs > 100_000_000) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [GuidePageLanguageIndex] Slow resource pack [#{}/{}] {} took {} ms", packIndex, activeResourcePacks.size(), @@ -94,7 +94,7 @@ private static Map loadLanguage(String normalizedLanguage) { packIndex++; } long totalNs = System.nanoTime() - startedAt; - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [GuidePageLanguageIndex] Loaded {} page language keys for language {} from {} resource packs in {} ms", merged.size(), normalizedLanguage, @@ -165,10 +165,8 @@ private static void loadDirectoryLanguageEntries(File directory, String normaliz try (InputStream input = new FileInputStream(child)) { mergePageKeys(input, target); } catch (IOException e) { - GuideDebugLog.warnAlways( - "[GuideNH] [GuidePageLanguageIndex] Failed to read lang file {}", - child.getAbsolutePath(), - e); + GuideDebugLog + .warn("[GuideNH] [GuidePageLanguageIndex] Failed to read lang file {}", child.getAbsolutePath(), e); } } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideResourceLanguageIndex.java b/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideResourceLanguageIndex.java index f8b25350..05f21b06 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideResourceLanguageIndex.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/localization/GuideResourceLanguageIndex.java @@ -47,7 +47,7 @@ private static Map load(String normalizedLanguage) { loadResourcePackLanguage(resourcePack, normalizedLanguage, merged); long packNs = System.nanoTime() - packStartedAt; if (packNs > 100_000_000) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [GuideResourceLanguageIndex] Slow resource pack [#{}/{}] {} took {} ms", packIndex, activeResourcePacks.size(), @@ -57,7 +57,7 @@ private static Map load(String normalizedLanguage) { packIndex++; } long totalNs = System.nanoTime() - startedAt; - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [GuideResourceLanguageIndex] Loaded {} lang entries for language {} from {} resource packs in {} ms", merged.size(), normalizedLanguage, @@ -128,7 +128,7 @@ private static void loadDirectoryLanguageEntries(File directory, String normaliz try (InputStream input = new FileInputStream(child)) { target.putAll(StringTranslate.parseLangFile(input)); } catch (IOException e) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [GuideResourceLanguageIndex] Failed to read lang file {}", child.getAbsolutePath(), e); diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/markdown/FileTreeCompiler.java b/src/main/java/com/hfstudio/guidenh/guide/internal/markdown/FileTreeCompiler.java index 283bf732..82ef58ea 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/markdown/FileTreeCompiler.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/markdown/FileTreeCompiler.java @@ -61,12 +61,12 @@ private static LytBlock buildIconBlock(PageCompiler compiler, FileTreeIcon icon) var imageId = IdUtils.resolveLink(value, compiler.getPageId()); var imageContent = compiler.loadAsset(imageId); if (imageContent == null) { - GuideDebugLog.warnAlways("[GuideNH] [FileTreeCompiler] File tree iconPng not found: {}", value); + GuideDebugLog.warn("[GuideNH] [FileTreeCompiler] File tree iconPng not found: {}", value); image.setTitle("Missing image: " + value); } image.setImage(imageId, imageContent); } catch (IllegalArgumentException e) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [FileTreeCompiler] File tree iconPng has invalid id '{}': {}", value, e.getMessage()); @@ -81,7 +81,7 @@ private static LytBlock buildIconBlock(PageCompiler compiler, FileTreeIcon icon) .getResourceDomain()); if (stack == null) { GuideDebugLog - .warnAlways("[GuideNH] [FileTreeCompiler] File tree iconItem could not be resolved: {}", value); + .warn("[GuideNH] [FileTreeCompiler] File tree iconItem could not be resolved: {}", value); LytParagraph fallback = new LytParagraph(); fallback.setMarginTop(0); fallback.setMarginBottom(0); diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/markdown/MarkdownRuntimeBlocks.java b/src/main/java/com/hfstudio/guidenh/guide/internal/markdown/MarkdownRuntimeBlocks.java index c1e4d7f0..2b738446 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/markdown/MarkdownRuntimeBlocks.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/markdown/MarkdownRuntimeBlocks.java @@ -11,6 +11,8 @@ import com.hfstudio.guidenh.libs.mdast.mdx.model.MdxJsxElementFields; import com.hfstudio.guidenh.libs.mdast.mdx.model.MdxJsxFlowElement; import com.hfstudio.guidenh.libs.mdast.model.MdAstAnyContent; +import com.hfstudio.guidenh.libs.mdast.model.MdAstBlockquote; +import com.hfstudio.guidenh.libs.mdast.model.MdAstParagraph; import com.hfstudio.guidenh.libs.mdast.model.MdAstText; public class MarkdownRuntimeBlocks { @@ -47,6 +49,16 @@ private MarkdownRuntimeBlocks() {} public static @Nullable BlockquoteDirective parseBlockquoteDirective(MdxJsxElementFields blockquote) { FirstParagraphText firstParagraph = findFirstParagraphText(blockquote); + return parseBlockquoteDirective(firstParagraph, blockquote.children()); + } + + public static @Nullable BlockquoteDirective parseBlockquoteDirective(MdAstBlockquote blockquote) { + FirstParagraphText firstParagraph = findFirstParagraphText(blockquote); + return parseBlockquoteDirective(firstParagraph, blockquote.children()); + } + + private static @Nullable BlockquoteDirective parseBlockquoteDirective(@Nullable FirstParagraphText firstParagraph, + List children) { if (firstParagraph == null) { return null; } @@ -67,7 +79,7 @@ private MarkdownRuntimeBlocks() {} new QuoteIconSpec(QuoteIconKind.TEXT, alertType.symbol()), trimLeadingDirectiveText(firstText, directiveEnd >= 0 ? directiveEnd + 1 : 0), firstParagraph.paragraph(), - blockquote.children()); + children); } String trimmed = firstText.trim(); @@ -118,14 +130,14 @@ private MarkdownRuntimeBlocks() {} icon, trimLeadingDirectiveText(trimmed, directiveEnd + 1), firstParagraph.paragraph(), - blockquote.children()); + children); } @Nullable private static FirstParagraphText findFirstParagraphText(MdxJsxElementFields blockquote) { for (Object child : blockquote.children()) { if (child instanceof MdxJsxFlowElement p && "p".equals(p.name())) { - String text = getLeadingParagraphText(p); + String text = getLeadingParagraphText(p.children()); if (text != null && !text.trim() .isEmpty()) { return new FirstParagraphText(p, text); @@ -141,8 +153,27 @@ private static FirstParagraphText findFirstParagraphText(MdxJsxElementFields blo } @Nullable - private static String getLeadingParagraphText(MdxJsxFlowElement paragraph) { - for (Object child : paragraph.children()) { + private static FirstParagraphText findFirstParagraphText(MdAstBlockquote blockquote) { + for (MdAstAnyContent child : blockquote.children()) { + if (child instanceof MdAstParagraph paragraph) { + String text = getLeadingParagraphText(paragraph.children()); + if (text != null && !text.trim() + .isEmpty()) { + return new FirstParagraphText(paragraph, text); + } + } else if (child instanceof MdAstText text) { + if (!text.value.trim() + .isEmpty()) { + return new FirstParagraphText(text, text.value); + } + } + } + return null; + } + + @Nullable + private static String getLeadingParagraphText(Iterable children) { + for (MdAstAnyContent child : children) { if (child instanceof MdAstText text) { if (!text.value.trim() .isEmpty()) { @@ -249,7 +280,7 @@ private static ColorValue parseColor(String value) { @Desugar public record BlockquoteDirective(@Nullable GithubAlertType alertType, ColorValue accentColor, @Nullable String title, @Nullable QuoteIconSpec icon, String remainingText, - @Nullable MdxJsxFlowElement firstParagraph, List children) {} + @Nullable MdAstAnyContent firstParagraph, List children) {} @Desugar public record QuoteIconSpec(QuoteIconKind kind, String value) {} @@ -261,7 +292,7 @@ public enum QuoteIconKind { } @Desugar - private record FirstParagraphText(@Nullable MdxJsxFlowElement paragraph, String text) {} + private record FirstParagraphText(@Nullable MdAstAnyContent paragraph, String text) {} @Desugar public record GithubAlertBlock(GithubAlertType type, List children, diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/recipe/LytNeiRecipeBox.java b/src/main/java/com/hfstudio/guidenh/guide/internal/recipe/LytNeiRecipeBox.java index 56900fbe..25d7ca9a 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/recipe/LytNeiRecipeBox.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/recipe/LytNeiRecipeBox.java @@ -231,7 +231,7 @@ private void warnRecipeRenderFailure(Throwable t) { + t.getClass() .getName(); if (WARNED_RECIPE_RENDER_FAILURES.add(key)) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [LytNeiRecipeBox] Failed to render embedded recipe {}#{}; keeping recipe frame", handler.getClass() .getName(), diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/recipe/RecipeLookup.java b/src/main/java/com/hfstudio/guidenh/guide/internal/recipe/RecipeLookup.java index c5fb6c52..cc095552 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/recipe/RecipeLookup.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/recipe/RecipeLookup.java @@ -121,9 +121,10 @@ public static ItemStack resolveOre(Object o) { if (o instanceof ItemStack stack) { return copy(stack); } - if (o instanceof Listlist) { - if (!list.isEmpty() && list.getFirst() instanceof ItemStack) { - return copy((ItemStack) list.getFirst()); + if (o instanceof Listlist && !list.isEmpty()) { + Object first = list.get(0); + if (first instanceof ItemStack stack) { + return copy(stack); } } return null; diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/scene/GuidebookPreviewPlayerSkinResolver.java b/src/main/java/com/hfstudio/guidenh/guide/internal/scene/GuidebookPreviewPlayerSkinResolver.java index 8bfab99e..2a3c1312 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/scene/GuidebookPreviewPlayerSkinResolver.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/scene/GuidebookPreviewPlayerSkinResolver.java @@ -33,6 +33,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import com.hfstudio.guidenh.client.GuideNhClientTaskScheduler; import com.hfstudio.guidenh.guide.scene.element.GuidebookSceneEntityLoader; import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.InsecureTextureException; @@ -104,8 +105,7 @@ static void queueSkinRefresh(GuidebookScenePreviewPlayerEntity entity) { public static void resolveSkinInBackground(String cacheKey, String playerName, GameProfile lookupProfile) { ResolvedPreviewPlayerSkin resolvedSkin = resolvePreviewPlayerSkinSafely(playerName, lookupProfile); - Minecraft minecraft = Minecraft.getMinecraft(); - minecraft.func_152344_a(() -> applyResolvedSkinOnMainThread(cacheKey, playerName, resolvedSkin)); + GuideNhClientTaskScheduler.execute(() -> applyResolvedSkinOnMainThread(cacheKey, playerName, resolvedSkin)); } public static void applyResolvedSkinOnMainThread(String cacheKey, String playerName, diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/scheduler/SearchIndexWorkItem.java b/src/main/java/com/hfstudio/guidenh/guide/internal/scheduler/SearchIndexWorkItem.java index 09fe424b..215f4f6b 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/scheduler/SearchIndexWorkItem.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/scheduler/SearchIndexWorkItem.java @@ -12,14 +12,17 @@ public Priority priority() { @Override public boolean shouldRun() { - return true; + return GuideME.getSearch() + .hasPendingWork(); } @Override public WorkResult tick(long deadlineNs) { - GuideME.getSearch() - .processWork(GuideSearch.BACKGROUND_TIME_PER_TICK); - return WorkResult.DONE; + GuideSearch search = GuideME.getSearch(); + long budget = search.isSearchPriorityActive() ? GuideSearch.SEARCH_TIME_PER_TICK + : GuideSearch.BACKGROUND_TIME_PER_TICK; + search.processWork(budget); + return WorkResult.YIELD; } @Override diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/screen/GuideNavBar.java b/src/main/java/com/hfstudio/guidenh/guide/internal/screen/GuideNavBar.java index e228af36..858ca625 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/screen/GuideNavBar.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/screen/GuideNavBar.java @@ -1,5 +1,6 @@ package com.hfstudio.guidenh.guide.internal.screen; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -26,6 +27,7 @@ import com.hfstudio.guidenh.guide.internal.GuidebookText; import com.hfstudio.guidenh.guide.internal.util.DisplayScale; import com.hfstudio.guidenh.guide.internal.util.SmoothFloatState; +import com.hfstudio.guidenh.guide.navigation.NavigationNode; import com.hfstudio.guidenh.guide.navigation.NavigationTree; import com.hfstudio.guidenh.guide.render.GuidePageTexture; @@ -155,9 +157,11 @@ public boolean shouldCreateNewPage() { public interface GuideExpansionListener { - void onExpansionToggled(@Nullable ResourceLocation guideId, ResourceLocation pageId, boolean expanded); + void onExpansionChanged(List changes, boolean allCollapsed); } + public record ExpansionChange(@Nullable ResourceLocation guideId, ResourceLocation pageId, boolean expanded) {} + private final List rows = new ArrayList<>(); private final Set expandedPageIds = new HashSet<>(); private final GuideNavProjection projection = new GuideNavProjection(); @@ -170,7 +174,7 @@ public interface GuideExpansionListener { private int lastExpandedStateHash; private boolean bookmarkGroupExpanded = true; @Nullable - private GuideExpansionListener onExpansionToggled; + private GuideExpansionListener onExpansionChanged; private int x; private int y; @@ -191,6 +195,7 @@ public void setBounds(int x, int y, int height) { this.x = x; this.y = y; this.height = height; + clampScrollToRows(); } public void setOpenWidth(int openWidth) { @@ -219,8 +224,8 @@ public void setContextMenuOpen(boolean contextMenuOpen) { } } - public void setOnExpansionToggled(@Nullable GuideExpansionListener listener) { - this.onExpansionToggled = listener; + public void setOnExpansionChanged(@Nullable GuideExpansionListener listener) { + this.onExpansionChanged = listener; } public void update(int mouseX, int mouseY, @Nullable NavigationTree tree, GuideBookmarkState bookmarkState) { @@ -330,6 +335,14 @@ public void expandParentsTo(@Nullable NavigationTree tree, @Nullable ResourceLoc } } + public void expandAll(@Nullable NavigationTree tree, GuideBookmarkState bookmarkState) { + updateExpansionState(collectExpandableNodes(tree), true, bookmarkState); + } + + public void collapseAll(@Nullable NavigationTree tree, GuideBookmarkState bookmarkState) { + updateExpansionState(collectExpandableNodes(tree), false, bookmarkState, true); + } + private boolean shouldRebuildRows(@Nullable NavigationTree tree, GuideBookmarkState bookmarkState) { return tree != lastTree || lastBookmarkStateVersion != bookmarkState.version() || lastExpandedStateHash != expandedPageIds.hashCode(); @@ -342,6 +355,7 @@ private void rebuildRows(@Nullable NavigationTree tree, GuideBookmarkState bookm if (tree == null) { lastBookmarkStateVersion = bookmarkState.version(); lastExpandedStateHash = expandedPageIds.hashCode(); + clampScrollToRows(); return; } GuideNavProjection.ProjectionResult projected = projection @@ -351,6 +365,7 @@ private void rebuildRows(@Nullable NavigationTree tree, GuideBookmarkState bookm } lastBookmarkStateVersion = bookmarkState.version(); lastExpandedStateHash = expandedPageIds.hashCode(); + clampScrollToRows(); } public void render(Minecraft mc, @Nullable ResourceLocation currentGuideId, @@ -640,7 +655,8 @@ private boolean renderRowTitle(Minecraft mc, FontRenderer fr, Row row, int textX @Nullable public ClickResult mouseClicked(int mouseX, int mouseY, @Nullable ResourceLocation currentGuideId, - @Nullable ResourceLocation currentPageId, GuideBookmarkState bookmarkState, boolean showNewPageButton) { + @Nullable ResourceLocation currentPageId, GuideBookmarkState bookmarkState, boolean showNewPageButton, + boolean shiftDown) { if (!isOpen()) { return null; } @@ -672,7 +688,11 @@ public ClickResult mouseClicked(int mouseX, int mouseY, @Nullable ResourceLocati } if (row.hasChildren() && isInsideExpandArrow(mouseX, row)) { - toggleExpand(row, bookmarkState); + if (shiftDown && row.kind() == GuideNavProjection.RowKind.TREE_PAGE) { + toggleExpandedDescendants(row, bookmarkState); + } else { + toggleExpand(row, bookmarkState); + } return ClickResult.none(); } @@ -795,16 +815,93 @@ private boolean isExpanded(Row row) { private void toggleExpand(Row row, GuideBookmarkState bookmarkState) { if (row.kind() == GuideNavProjection.RowKind.BOOKMARK_GROUP) { bookmarkGroupExpanded = !bookmarkGroupExpanded; - } else if (row.pageId() != null) { - if (expandedPageIds.contains(row.pageId())) { - expandedPageIds.remove(row.pageId()); - } else { - expandedPageIds.add(row.pageId()); + rebuildRows(lastTree, bookmarkState); + return; + } + ResourceLocation pageId = row.pageId(); + if (lastTree == null || pageId == null) { + return; + } + NavigationNode node = lastTree.getNodeById(pageId); + if (node == null) { + return; + } + updateExpansionState(List.of(node), !expandedPageIds.contains(pageId), bookmarkState); + } + + private void toggleExpandedDescendants(Row row, GuideBookmarkState bookmarkState) { + ResourceLocation pageId = row.pageId(); + if (lastTree == null || pageId == null) { + return; + } + NavigationNode node = lastTree.getNodeById(pageId); + if (node == null) { + return; + } + List nodes = collectExpandableNodes(node); + boolean allExpanded = !nodes.isEmpty() && nodes.stream() + .allMatch(expandableNode -> expandedPageIds.contains(expandableNode.pageId())); + updateExpansionState(nodes, !allExpanded, bookmarkState); + } + + private List collectExpandableNodes(@Nullable NavigationTree tree) { + return tree == null ? List.of() : collectExpandableNodes(tree.getRootNodes()); + } + + private List collectExpandableNodes(NavigationNode root) { + return collectExpandableNodes(List.of(root)); + } + + private List collectExpandableNodes(List roots) { + if (roots.isEmpty()) { + return List.of(); + } + List nodes = new ArrayList<>(); + ArrayDeque pendingNodes = new ArrayDeque<>(roots.size()); + for (int index = roots.size() - 1; index >= 0; index--) { + pendingNodes.push(roots.get(index)); + } + while (!pendingNodes.isEmpty()) { + NavigationNode node = pendingNodes.pop(); + List children = node.children(); + if (node.pageId() != null && !children.isEmpty()) { + nodes.add(node); + } + for (int index = children.size() - 1; index >= 0; index--) { + pendingNodes.push(children.get(index)); + } + } + return nodes; + } + + private void updateExpansionState(List nodes, boolean expanded, GuideBookmarkState bookmarkState) { + updateExpansionState(nodes, expanded, bookmarkState, false); + } + + private void updateExpansionState(List nodes, boolean expanded, GuideBookmarkState bookmarkState, + boolean allCollapsed) { + List changes = new ArrayList<>(); + boolean changed = allCollapsed && !expandedPageIds.isEmpty(); + for (NavigationNode node : nodes) { + ResourceLocation pageId = node.pageId(); + if (pageId == null) { + continue; + } + boolean nodeChanged = expanded ? expandedPageIds.add(pageId) : expandedPageIds.remove(pageId); + changed |= nodeChanged; + if (nodeChanged) { + changes.add(new ExpansionChange(node.guideId(), pageId, expanded)); } } + if (allCollapsed) { + expandedPageIds.clear(); + } + if (!changed) { + return; + } rebuildRows(lastTree, bookmarkState); - if (onExpansionToggled != null && row.pageId() != null) { - onExpansionToggled.onExpansionToggled(row.guideId(), row.pageId(), expandedPageIds.contains(row.pageId())); + if (onExpansionChanged != null) { + onExpansionChanged.onExpansionChanged(changes, allCollapsed); } } @@ -814,17 +911,23 @@ public boolean contains(int mouseX, int mouseY) { } public void scroll(int dwheel) { - int contentH = rows.size() * ROW_H + CONTENT_PADDING * 2; - int max = Math.max(0, contentH - Math.max(0, height - TITLE_H)); - scrollY -= Integer.signum(dwheel) * ROW_H * 2; - if (scrollY < 0) { - scrollY = 0; - } - if (scrollY > max) { - scrollY = max; + scrollY = Math.clamp(scrollY - Integer.signum(dwheel) * ROW_H * 2, 0, getMaxScrollY()); + } + + private void clampScrollToRows() { + int maxScrollY = getMaxScrollY(); + int clampedScrollY = Math.clamp(scrollY, 0, maxScrollY); + if (clampedScrollY != scrollY || visualScrollY.rounded() > maxScrollY) { + scrollY = clampedScrollY; + visualScrollY.snapTo(scrollY); } } + private int getMaxScrollY() { + int contentHeight = rows.size() * ROW_H + CONTENT_PADDING * 2; + return Math.max(0, contentHeight - getBodyHeight()); + } + private int getFirstVisibleRowIndex() { int firstVisibleRow = Math.floorDiv(visualScrollY.rounded() - CONTENT_PADDING - 1, ROW_H); return Math.clamp(firstVisibleRow, 0, rows.size()); diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/search/GuideSearch.java b/src/main/java/com/hfstudio/guidenh/guide/internal/search/GuideSearch.java index 3089fb5f..e477ad57 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/search/GuideSearch.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/search/GuideSearch.java @@ -2,14 +2,29 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.time.Instant; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; import java.util.HashSet; +import java.util.HexFormat; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import net.minecraft.util.ResourceLocation; @@ -35,8 +50,10 @@ import org.apache.lucene.search.highlight.Highlighter; import org.apache.lucene.search.highlight.InvalidTokenOffsetsException; import org.apache.lucene.search.highlight.QueryScorer; -import org.apache.lucene.store.ByteBuffersDirectory; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; import com.github.bsideup.jabel.Desugar; import com.hfstudio.guidenh.guide.Guide; @@ -50,52 +67,62 @@ import com.hfstudio.guidenh.guide.scene.support.GuideDebugLog; import com.hfstudio.guidenh.libs.unist.UnistNode; +import cpw.mods.fml.common.Loader; +import lombok.Getter; + /** - * Manages the in-memory Lucene index for guide search. + * Manages the persistent Lucene index for guide search. */ public class GuideSearch implements AutoCloseable { - /** Small background budget to avoid guide indexing from competing with gameplay-critical work. */ public static final long BACKGROUND_TIME_PER_TICK = TimeUnit.MILLISECONDS.toNanos(1); - /** Default budget used when indexing can make stronger forward progress. */ - public static final long DEFAULT_TIME_PER_TICK = TimeUnit.MILLISECONDS.toNanos(5); - - private final ByteBuffersDirectory directory = new ByteBuffersDirectory(); - + public static final long SEARCH_TIME_PER_TICK = TimeUnit.MILLISECONDS.toNanos(8); + private static final int INDEX_SCHEMA_VERSION = 1; + private static final String COMMIT_SCHEMA_VERSION = "guidenh.search.schema"; + private static final String COMMIT_FINGERPRINT = "guidenh.search.fingerprint"; + private static final long PUBLISH_INTERVAL_NANOS = TimeUnit.MILLISECONDS.toNanos(250); + private static final long SEARCH_PRIORITY_NANOS = TimeUnit.SECONDS.toNanos(1); + private static final int MAX_INDEX_WORKERS = 4; + + private Directory directory; private final Analyzer analyzer; - private final IndexWriter indexWriter; + private IndexWriter indexWriter; private IndexReader indexReader; private IndexSearcher indexSearcher; private final List pendingTasks = new ArrayList<>(); + private final ExecutorService indexingExecutor; + private final CompletionService completedDocuments; private Instant indexingStarted; private int pagesIndexed; + private int inFlightDocuments; + private long lastPublishedNanos; + private long searchPriorityUntilNanos; + @Getter + private long indexRevision; + private long buildGeneration; private final Set warnedAboutLanguage = Collections.synchronizedSet(new HashSet<>()); private final Set indexedLanguages = Collections.synchronizedSet(new HashSet<>()); public GuideSearch() { analyzer = new LanguageSpecificAnalyzerWrapper(); - ClassLoader prevCCL = Thread.currentThread() - .getContextClassLoader(); - Thread.currentThread() - .setContextClassLoader(GuideSearch.class.getClassLoader()); - try { - var config = new IndexWriterConfig(analyzer); - indexWriter = new IndexWriter(directory, config); - // Commit once so DirectoryReader can open the in-memory index immediately. - indexWriter.flush(); - indexWriter.commit(); - indexReader = DirectoryReader.open(directory); - indexSearcher = new IndexSearcher(indexReader); - } catch (IOException e) { - // ByteBuffersDirectory keeps this in memory, so initialization failures are unexpected. - throw new UncheckedIOException("Failed to create index writer.", e); - } finally { - Thread.currentThread() - .setContextClassLoader(prevCCL); - } + int workerCount = Math.clamp( + Runtime.getRuntime() + .availableProcessors() - 1, + 1, + MAX_INDEX_WORKERS); + indexingExecutor = Executors.newFixedThreadPool(workerCount, runnable -> { + Thread thread = new Thread(runnable, "GuideNH Search Indexer"); + thread.setDaemon(true); + return thread; + }); + completedDocuments = new ExecutorCompletionService<>(indexingExecutor); } public void index(Guide guide) { + if (indexWriter == null) { + indexAll(); + return; + } try { indexWriter.deleteDocuments( new Term( @@ -113,19 +140,38 @@ public void index(Guide guide) { pendingTasks.removeIf( t -> t.guide.getId() .equals(guide.getId())); - pendingTasks.add(new GuideIndexingTask(guide, new ArrayList<>(guide.getPages()))); + pendingTasks.add(new GuideIndexingTask(guide, new ArrayDeque<>(guide.getPages()))); } public void indexAll() { - pendingTasks.clear(); + String fingerprint = fingerprint(Guides.getAll()); + cancelPendingWork(); indexedLanguages.clear(); warnedAboutLanguage.clear(); try { - indexWriter.deleteAll(); - indexWriter.flush(); + closeIndex(); + directory = openDirectory(fingerprint); + if (DirectoryReader.indexExists(directory)) { + indexReader = DirectoryReader.open(directory); + if (matchesFingerprint(indexReader, fingerprint)) { + indexWriter = openIndexWriter(IndexWriterConfig.OpenMode.APPEND); + indexSearcher = new IndexSearcher(indexReader); + indexedLanguages.addAll(Analyzers.MINECRAFT_TO_LUCENE_LANG.values()); + indexRevision++; + GuideDebugLog.info("[GuideNH] [GuideSearch] Loaded persistent search index {}", fingerprint); + return; + } + indexReader.close(); + indexReader = null; + } + + indexWriter = openIndexWriter(IndexWriterConfig.OpenMode.CREATE); + indexWriter.setLiveCommitData(commitData(fingerprint).entrySet()); indexWriter.commit(); - refreshIndexReader(); + indexReader = DirectoryReader.open(directory); + indexSearcher = new IndexSearcher(indexReader); + indexRevision++; } catch (IOException e) { throw new UncheckedIOException("Failed to reset the guide search index.", e); } @@ -136,75 +182,129 @@ public void indexAll() { } public void processWork() { - processWork(DEFAULT_TIME_PER_TICK); + processWork(BACKGROUND_TIME_PER_TICK); } public void processWork(long budgetNanos) { - if (pendingTasks.isEmpty()) { + if (indexWriter == null || !hasPendingWork()) { return; } long start = System.nanoTime(); - - var guideTaskIt = pendingTasks.iterator(); - while (guideTaskIt.hasNext()) { - if (isTimeElapsed(start, budgetNanos)) { - return; + schedulePendingPages(); + boolean wroteDocuments = false; + while (!isTimeElapsed(start, budgetNanos)) { + Future completed = completedDocuments.poll(); + if (completed == null) { + break; + } + PageIndexDocument pageDocument = receiveCompletedDocument(completed); + if (pageDocument == null || pageDocument.generation() != buildGeneration) { + continue; } + inFlightDocuments--; + writeDocument(pageDocument); + wroteDocuments = true; + schedulePendingPages(); + } - var guideTask = guideTaskIt.next(); - var guide = guideTask.guide(); + boolean finished = !hasPendingWork(); + if (wroteDocuments && (finished || isPublishDue())) { + publishIndex(); + } + if (finished) { + GuideDebugLog.info( + "[GuideNH] [GuideSearch] Indexing of {} pages finished in {}", + pagesIndexed, + Duration.between(indexingStarted, Instant.now())); + } + } - var pageIt = guideTask.pendingPages.iterator(); - while (pageIt.hasNext()) { - if (isTimeElapsed(start, budgetNanos)) { - return; - } + private boolean isTimeElapsed(long start, long budgetNanos) { + return System.nanoTime() - start >= budgetNanos; + } - var page = pageIt.next(); + public boolean hasPendingWork() { + return !pendingTasks.isEmpty() || inFlightDocuments > 0; + } - var pageDoc = createPageDocument(guideTask.guide(), page); - if (pageDoc != null) { - try { - indexWriter.addDocument(pageDoc); - } catch (IOException e) { - GuideDebugLog.error("[GuideNH] [GuideSearch] Failed to index document {}{}", guide, page, e); - } + public boolean isSearchPriorityActive() { + return System.nanoTime() < searchPriorityUntilNanos; + } - var searchLang = pageDoc.get(IndexSchema.FIELD_SEARCH_LANG); - if (searchLang != null) { - indexedLanguages.add(searchLang); - } - } - pagesIndexed++; - pageIt.remove(); + private void schedulePendingPages() { + int maximumInFlight = MAX_INDEX_WORKERS * 2; + while (inFlightDocuments < maximumInFlight && !pendingTasks.isEmpty()) { + GuideIndexingTask task = pendingTasks.getFirst(); + var page = task.pendingPages() + .pollFirst(); + if (page == null) { + pendingTasks.removeFirst(); + continue; } - - guideTaskIt.remove(); + long generation = buildGeneration; + completedDocuments.submit(() -> createPageIndexDocument(task.guide(), page, generation)); + inFlightDocuments++; } + } + @Nullable + private PageIndexDocument receiveCompletedDocument(Future completed) { try { - indexWriter.flush(); - indexWriter.commit(); - refreshIndexReader(); - } catch (IOException e) { - throw new UncheckedIOException(e); + return completed.get(); + } catch (Exception exception) { + GuideDebugLog.error("[GuideNH] [GuideSearch] Failed to prepare a search document", exception); + return null; } + } - GuideDebugLog.info( - "[GuideNH] [GuideSearch] Indexing of {} pages finished in {}", - pagesIndexed, - Duration.between(indexingStarted, Instant.now())); + @NonNull + private PageIndexDocument createPageIndexDocument(Guide guide, ParsedGuidePage page, long generation) { + try { + Document document = createPageDocument(guide, page); + return new PageIndexDocument(guide, page, document, generation); + } catch (Throwable throwable) { + GuideDebugLog + .error("[GuideNH] [GuideSearch] Failed to prepare index document {}{}", guide, page, throwable); + return new PageIndexDocument(guide, page, null, generation); + } } - public void processAllWork() { - while (!pendingTasks.isEmpty()) { - processWork(DEFAULT_TIME_PER_TICK); + private void writeDocument(PageIndexDocument pageDocument) { + try { + Document document = pageDocument.document(); + if (document == null) { + return; + } + indexWriter.addDocument(document); + String searchLanguage = document.get(IndexSchema.FIELD_SEARCH_LANG); + if (searchLanguage != null) { + indexedLanguages.add(searchLanguage); + } + } catch (IOException exception) { + GuideDebugLog.error( + "[GuideNH] [GuideSearch] Failed to index document {}{}", + pageDocument.guide(), + pageDocument.page(), + exception); + } finally { + pagesIndexed++; } } - private boolean isTimeElapsed(long start, long budgetNanos) { - return System.nanoTime() - start >= budgetNanos; + private boolean isPublishDue() { + return System.nanoTime() - lastPublishedNanos >= PUBLISH_INTERVAL_NANOS; + } + + private void publishIndex() { + try { + indexWriter.commit(); + refreshIndexReader(); + lastPublishedNanos = System.nanoTime(); + indexRevision++; + } catch (IOException exception) { + throw new UncheckedIOException("Failed to publish the guide search index.", exception); + } } private void refreshIndexReader() throws IOException { @@ -212,16 +312,18 @@ private void refreshIndexReader() throws IOException { var oldReader = indexReader; indexReader = newReader; indexSearcher = new IndexSearcher(newReader); - oldReader.close(); + if (oldReader != null) { + oldReader.close(); + } } public List searchGuide(String queryText, @Nullable Guide onlyFromGuide) { if (queryText.isEmpty()) { return List.of(); } - - if (!pendingTasks.isEmpty()) { - processAllWork(); + searchPriorityUntilNanos = System.nanoTime() + SEARCH_PRIORITY_NANOS; + if (indexSearcher == null) { + return List.of(); } var searchLanguage = getLuceneLanguageFromMinecraft(LangUtil.getCurrentLanguage()); @@ -268,7 +370,7 @@ public List searchGuide(String queryText, @Nullable Guide onlyFrom var guide = Guides.getById(guideId); if (guide == null) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [GuideSearch] Search index produced guide id {} which couldn't be found.", guideId); continue; @@ -276,7 +378,7 @@ public List searchGuide(String queryText, @Nullable Guide onlyFrom var page = guide.getParsedPage(pageId); if (page == null) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [GuideSearch] Search index produced page {} in guide {}, which couldn't be found.", pageId, guideId); @@ -373,7 +475,7 @@ private String getLuceneLanguageFromMinecraft(String language) { var luceneLang = Analyzers.MINECRAFT_TO_LUCENE_LANG.get(language); if (luceneLang == null) { if (warnedAboutLanguage.add(language)) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [GuideSearch] Minecraft language '{}' is unknown, so search falls back to english.", language); } @@ -405,38 +507,153 @@ public void appendBreak() { return searchableText.toString(); } - @Override - public void close() throws IOException { - IOException suppressed = null; + private Directory openDirectory(String fingerprint) throws IOException { + Path indexDirectory = Loader.instance() + .getConfigDir() + .toPath() + .resolve("guidenh") + .resolve("search-index") + .resolve("v" + INDEX_SCHEMA_VERSION) + .resolve(fingerprint); + Files.createDirectories(indexDirectory); + return FSDirectory.open(indexDirectory); + } + + private IndexWriter openIndexWriter(IndexWriterConfig.OpenMode openMode) throws IOException { + ClassLoader previousClassLoader = Thread.currentThread() + .getContextClassLoader(); + Thread.currentThread() + .setContextClassLoader(GuideSearch.class.getClassLoader()); try { - indexWriter.close(); - } catch (IOException e) { - suppressed = e; + return new IndexWriter(directory, new IndexWriterConfig(analyzer).setOpenMode(openMode)); + } finally { + Thread.currentThread() + .setContextClassLoader(previousClassLoader); } - try { - indexReader.close(); - } catch (IOException e) { - if (suppressed != null) { - suppressed.addSuppressed(e); - } else { - suppressed = e; + } + + private boolean matchesFingerprint(IndexReader reader, String fingerprint) throws IOException { + if (!(reader instanceof DirectoryReader directoryReader)) { + return false; + } + Map commitData = directoryReader.getIndexCommit() + .getUserData(); + return Integer.toString(INDEX_SCHEMA_VERSION) + .equals(commitData.get(COMMIT_SCHEMA_VERSION)) && fingerprint.equals(commitData.get(COMMIT_FINGERPRINT)); + } + + private Map commitData(String fingerprint) { + return Map.of(COMMIT_SCHEMA_VERSION, Integer.toString(INDEX_SCHEMA_VERSION), COMMIT_FINGERPRINT, fingerprint); + } + + private String fingerprint(Iterable guides) { + MessageDigest digest = createFingerprintDigest(); + updateFingerprint(digest, "schema=" + INDEX_SCHEMA_VERSION); + var sortedGuides = new ArrayList(); + for (Guide guide : guides) { + sortedGuides.add(guide); + } + sortedGuides.sort( + Comparator.comparing( + guide -> guide.getId() + .toString())); + for (Guide guide : sortedGuides) { + updateFingerprint( + digest, + guide.getId() + .toString()); + var pages = new ArrayList<>(guide.getPages()); + pages.sort( + Comparator.comparing( + page -> page.getId() + .toString())); + for (var page : pages) { + updateFingerprint( + digest, + page.getId() + .toString()); + updateFingerprint(digest, page.getLanguage()); + updateFingerprint(digest, page.getSource()); } } + return HexFormat.of() + .formatHex(digest.digest()); + } + + private MessageDigest createFingerprintDigest() { try { - directory.close(); - } catch (IOException e) { - if (suppressed != null) { - e.addSuppressed(suppressed); + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException exception) { + throw new IllegalStateException("SHA-256 is unavailable", exception); + } + } + + private void updateFingerprint(MessageDigest digest, String value) { + digest.update(value.getBytes(StandardCharsets.UTF_8)); + digest.update((byte) 0); + } + + private void cancelPendingWork() { + buildGeneration++; + pendingTasks.clear(); + inFlightDocuments = 0; + } + + private void closeIndex() throws IOException { + IOException failure = null; + if (indexWriter != null) { + try { + indexWriter.close(); + } catch (IOException exception) { + failure = exception; + } finally { + indexWriter = null; + } + } + if (indexReader != null) { + try { + indexReader.close(); + } catch (IOException exception) { + if (failure != null) { + failure.addSuppressed(exception); + } else { + failure = exception; + } + } finally { + indexReader = null; + indexSearcher = null; + } + } + if (directory != null) { + try { + directory.close(); + } catch (IOException exception) { + if (failure != null) { + failure.addSuppressed(exception); + } else { + failure = exception; + } + } finally { + directory = null; } - throw e; } - if (suppressed != null) { - throw suppressed; + if (failure != null) { + throw failure; } } + @Override + public void close() throws IOException { + cancelPendingWork(); + indexingExecutor.shutdownNow(); + closeIndex(); + } + + @Desugar + public record GuideIndexingTask(Guide guide, Deque pendingPages) {} + @Desugar - record GuideIndexingTask(Guide guide, List pendingPages) {} + public record PageIndexDocument(Guide guide, ParsedGuidePage page, Document document, long generation) {} @Desugar public record SearchResult(ResourceLocation guideId, ResourceLocation pageId, String pageTitle, LytFlowContent text, diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/search/GuideSearchSnippetFormatter.java b/src/main/java/com/hfstudio/guidenh/guide/internal/search/GuideSearchSnippetFormatter.java index fbd34e6c..568475d7 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/search/GuideSearchSnippetFormatter.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/search/GuideSearchSnippetFormatter.java @@ -1,8 +1,12 @@ package com.hfstudio.guidenh.guide.internal.search; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.List; +import org.jetbrains.annotations.Nullable; + import com.github.bsideup.jabel.Desugar; import com.hfstudio.guidenh.guide.document.DefaultStyles; import com.hfstudio.guidenh.guide.document.block.LytVisitor; @@ -22,7 +26,102 @@ public static LytFlowContent format(String fragmentMarkup) { var expanded = expandHighlightMarkup(fragmentMarkup == null ? "" : fragmentMarkup); var plain = new StringBuilder(); var ranges = parseRanges(expanded, plain); - return buildFlowContent(plain.toString(), ranges); + var sanitized = stripMarkdownDelimiters(plain.toString(), ranges); + return buildFlowContent(sanitized.text(), sanitized.ranges()); + } + + private static SanitizedSnippet stripMarkdownDelimiters(String text, List ranges) { + var delimiters = findMarkdownDelimiters(text); + if (delimiters.isEmpty()) { + return new SanitizedSnippet(text, ranges); + } + + int[] offsets = new int[text.length() + 1]; + var sanitized = new StringBuilder(text.length()); + int sourceIndex = 0; + while (sourceIndex < text.length()) { + offsets[sourceIndex] = sanitized.length(); + IntRange delimiter = delimiterStartingAt(delimiters, sourceIndex); + if (delimiter != null) { + for (int index = delimiter.startInclusive() + 1; index <= delimiter.endExclusive(); index++) { + offsets[index] = sanitized.length(); + } + sourceIndex = delimiter.endExclusive(); + continue; + } + sanitized.append(text.charAt(sourceIndex)); + sourceIndex++; + offsets[sourceIndex] = sanitized.length(); + } + + var sanitizedRanges = new ArrayList(ranges.size()); + for (var range : ranges) { + int start = offsets[range.startInclusive()]; + int end = offsets[range.endExclusive()]; + if (start < end) { + sanitizedRanges.add(new IntRange(start, end)); + } + } + return new SanitizedSnippet(sanitized.toString(), sanitizedRanges); + } + + private static List findMarkdownDelimiters(String text) { + var delimiters = new ArrayList(); + Deque openDelimiters = new ArrayDeque<>(); + int index = 0; + while (index < text.length()) { + if (!isMarkdownDelimiter(text.charAt(index)) || isEscaped(text, index)) { + index++; + continue; + } + int end = index + 1; + while (end < text.length() && text.charAt(end) == text.charAt(index)) { + end++; + } + String token = text.substring(index, end); + MarkdownDelimiter opening = openDelimiters.peek(); + if (opening != null && opening.token() + .equals(token) && containsVisibleText(text, opening.endExclusive(), index)) { + delimiters.add(new IntRange(opening.startInclusive(), opening.endExclusive())); + delimiters.add(new IntRange(index, end)); + openDelimiters.pop(); + } else { + openDelimiters.push(new MarkdownDelimiter(token, index, end)); + } + index = end; + } + return delimiters; + } + + private static boolean isMarkdownDelimiter(char character) { + return !Character.isLetterOrDigit(character) && !Character.isWhitespace(character); + } + + private static boolean isEscaped(String text, int index) { + int slashCount = 0; + for (int cursor = index - 1; cursor >= 0 && text.charAt(cursor) == '\\'; cursor--) { + slashCount++; + } + return slashCount % 2 != 0; + } + + private static boolean containsVisibleText(String text, int start, int end) { + for (int index = start; index < end; index++) { + if (!Character.isWhitespace(text.charAt(index))) { + return true; + } + } + return false; + } + + @Nullable + private static IntRange delimiterStartingAt(List delimiters, int index) { + for (var delimiter : delimiters) { + if (delimiter.startInclusive() == index) { + return delimiter; + } + } + return null; } public static LytFlowContent clipToVisibleChars(LytFlowContent content, int maxVisibleChars) { @@ -227,4 +326,10 @@ public record IntRange(int startInclusive, int endExclusive) { } } } + + @Desugar + private record SanitizedSnippet(String text, List ranges) {} + + @Desugar + private record MarkdownDelimiter(String token, int startInclusive, int endExclusive) {} } diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/search/PageIndexer.java b/src/main/java/com/hfstudio/guidenh/guide/internal/search/PageIndexer.java index 065a5774..64b3fe1a 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/search/PageIndexer.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/search/PageIndexer.java @@ -18,6 +18,7 @@ import com.hfstudio.guidenh.libs.mdast.MdAstYamlFrontmatter; import com.hfstudio.guidenh.libs.mdast.mdx.model.MdxJsxElementFields; import com.hfstudio.guidenh.libs.mdast.model.MdAstAnyContent; +import com.hfstudio.guidenh.libs.mdast.model.MdAstBreak; import com.hfstudio.guidenh.libs.mdast.model.MdAstDefinition; import com.hfstudio.guidenh.libs.mdast.model.MdAstRoot; import com.hfstudio.guidenh.libs.mdast.model.MdAstText; @@ -60,13 +61,15 @@ public void index(MdAstRoot root, IndexingSink sink) { public void indexContent(MdAstAnyContent content, IndexingSink sink) { if (content instanceof MdAstText astText) { sink.appendText(astText, astText.value); + } else if (content instanceof MdAstBreak) { + sink.appendBreak(); } else if (content instanceof MdxJsxElementFields el) { + if ("br".equals(el.name())) { + sink.appendBreak(); + return; + } var compiler = tagCompilers.get(el.name()); if (compiler == null) { - GuideDebugLog.warnAlways( - "[GuideNH] [PageIndexer] Unhandled MDX element in guide search indexing: {}", - el.name()); - // Fallback: index children content indexContent(el.children(), sink); } else { compiler.index(this, el, sink); @@ -75,7 +78,7 @@ public void indexContent(MdAstAnyContent content, IndexingSink sink) { // Handled via conversion } else { GuideDebugLog - .warnAlways("[GuideNH] [PageIndexer] Unhandled node type in guide search indexing: {}", content.type()); + .warn("[GuideNH] [PageIndexer] Unhandled node type in guide search indexing: {}", content.type()); } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/latex/GuideLatexRenderer.java b/src/main/java/com/hfstudio/guidenh/guide/latex/GuideLatexRenderer.java index a0f8e8e7..7495fdc4 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/latex/GuideLatexRenderer.java +++ b/src/main/java/com/hfstudio/guidenh/guide/latex/GuideLatexRenderer.java @@ -55,8 +55,7 @@ public int calibrateRefHeight(float sourceScale) { int h = icon.getIconHeight(); return Math.max(1, h); } catch (ParseException e) { - GuideDebugLog - .warnAlways("[GuideNH/LaTeX] Failed to calibrate reference height for scale {}", sourceScale, e); + GuideDebugLog.warn("[GuideNH/LaTeX] Failed to calibrate reference height for scale {}", sourceScale, e); return 16; } }); @@ -120,11 +119,11 @@ public int[] measureSize(String formula, int fillColorArgb, float sourceScale) { GuideLatexTextureCache.INSTANCE.putSize(sizeKey, w, h, d); return new int[] { w, h, d }; } catch (ParseException e) { - GuideDebugLog.warnAlways("[GuideNH/LaTeX] Parse error measuring '{}': {}", formula, e.getMessage()); + GuideDebugLog.warn("[GuideNH/LaTeX] Parse error measuring '{}': {}", formula, e.getMessage()); GuideLatexTextureCache.INSTANCE.markFailed(formula, e.getMessage()); return null; } catch (Exception e) { - GuideDebugLog.warnAlways("[GuideNH/LaTeX] Unexpected error measuring '{}': {}", formula, e.getMessage(), e); + GuideDebugLog.warn("[GuideNH/LaTeX] Unexpected error measuring '{}': {}", formula, e.getMessage(), e); GuideLatexTextureCache.INSTANCE.markFailed(formula, e.getMessage()); return null; } @@ -175,11 +174,11 @@ public int[] getOrCreateTexture(String formula, int fillColorArgb, float sourceS return new int[] { textureId, w, h }; } catch (ParseException e) { - GuideDebugLog.warnAlways("[GuideNH/LaTeX] Parse error rendering '{}': {}", formula, e.getMessage()); + GuideDebugLog.warn("[GuideNH/LaTeX] Parse error rendering '{}': {}", formula, e.getMessage()); GuideLatexTextureCache.INSTANCE.markFailed(formula, e.getMessage()); return null; } catch (Exception e) { - GuideDebugLog.warnAlways("[GuideNH/LaTeX] Unexpected error rendering '{}': {}", formula, e.getMessage(), e); + GuideDebugLog.warn("[GuideNH/LaTeX] Unexpected error rendering '{}': {}", formula, e.getMessage(), e); GuideLatexTextureCache.INSTANCE.markFailed( formula, e.getMessage() == null ? e.getClass() diff --git a/src/main/java/com/hfstudio/guidenh/guide/mediawiki/MediaWikiCategoryParser.java b/src/main/java/com/hfstudio/guidenh/guide/mediawiki/MediaWikiCategoryParser.java index d1a1675f..a33970d6 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/mediawiki/MediaWikiCategoryParser.java +++ b/src/main/java/com/hfstudio/guidenh/guide/mediawiki/MediaWikiCategoryParser.java @@ -87,7 +87,7 @@ private static MediaWikiCategoryReference parseReference(ParsedGuidePage page, O } private static void warnMalformedCategories(ParsedGuidePage page, String message) { - GuideDebugLog.warnAlways("[GuideNH] [MediaWikiCategoryParser] Page {} {}", page.getId(), message); + GuideDebugLog.warn("[GuideNH] [MediaWikiCategoryParser] Page {} {}", page.getId(), message); } private static String normalizeCategoryKey(String categoryName) { diff --git a/src/main/java/com/hfstudio/guidenh/guide/navigation/NavigationTree.java b/src/main/java/com/hfstudio/guidenh/guide/navigation/NavigationTree.java index 1f1afe15..8c152043 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/navigation/NavigationTree.java +++ b/src/main/java/com/hfstudio/guidenh/guide/navigation/NavigationTree.java @@ -327,7 +327,13 @@ public static NavigationNode createNode(Map no public static final Comparator NODE_COMPARATOR = Comparator.comparingInt(NavigationNode::position) .reversed() - .thenComparing(NavigationNode::title); + .thenComparing(NavigationNode::title) + .thenComparing( + node -> node.guideId() != null ? node.guideId() + .toString() : "") + .thenComparing( + node -> node.pageId() != null ? node.pageId() + .toString() : ""); @Nullable private static NavigationNode createMergedNode(Map nodeIndex, diff --git a/src/main/java/com/hfstudio/guidenh/guide/render/GuidePageTexture.java b/src/main/java/com/hfstudio/guidenh/guide/render/GuidePageTexture.java index e62fa83c..1caf1d7f 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/render/GuidePageTexture.java +++ b/src/main/java/com/hfstudio/guidenh/guide/render/GuidePageTexture.java @@ -110,12 +110,12 @@ public static String sanitize(String raw) { private static LytSize readImageSize(ResourceLocation id, byte[] imageData) throws Exception { try (ImageInputStream input = ImageIO.createImageInputStream(new ByteArrayInputStream(imageData))) { if (input == null) { - GuideDebugLog.warnAlways("Failed to inspect image {} (ImageIO returned null stream)", id); + GuideDebugLog.warn("Failed to inspect image {} (ImageIO returned null stream)", id); return null; } Iterator readers = ImageIO.getImageReaders(input); if (!readers.hasNext()) { - GuideDebugLog.warnAlways("Failed to inspect image {} (no ImageIO reader found)", id); + GuideDebugLog.warn("Failed to inspect image {} (no ImageIO reader found)", id); return null; } ImageReader reader = readers.next(); @@ -151,7 +151,7 @@ public ResourceLocation getTexture() { BufferedImage img = ImageIO.read(new ByteArrayInputStream(data)); if (img == null) { GuideDebugLog - .warnAlways("Failed to decode image {} while creating dynamic texture (ImageIO returned null)", id); + .warn("Failed to decode image {} while creating dynamic texture (ImageIO returned null)", id); imageData = null; return null; } @@ -195,10 +195,8 @@ private static ITextureObject removeTextureObject(TextureManager textureManager, TEXTURE_OBJECTS_SRG_FIELD); return textureObjects.remove(location); } catch (Throwable t) { - GuideDebugLog.warnAlways( - "Failed to remove dynamic guide page texture {} from Minecraft texture manager", - location, - t); + GuideDebugLog + .warn("Failed to remove dynamic guide page texture {} from Minecraft texture manager", location, t); return null; } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteExportTask.java b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteExportTask.java index 53011980..13b9553f 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteExportTask.java +++ b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteExportTask.java @@ -14,6 +14,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.IResourceManager; @@ -33,11 +35,15 @@ import com.hfstudio.guidenh.guide.PageCollection; import com.hfstudio.guidenh.guide.compiler.PageCompiler; import com.hfstudio.guidenh.guide.compiler.ParsedGuidePage; +import com.hfstudio.guidenh.guide.document.block.LytDocument; +import com.hfstudio.guidenh.guide.document.block.LytNode; import com.hfstudio.guidenh.guide.indices.CategoryIndex; import com.hfstudio.guidenh.guide.indices.PageIndex; import com.hfstudio.guidenh.guide.internal.GuideRegistry; import com.hfstudio.guidenh.guide.internal.GuidebookText; import com.hfstudio.guidenh.guide.internal.MutableGuide; +import com.hfstudio.guidenh.guide.internal.host.LytHost; +import com.hfstudio.guidenh.guide.internal.host.scripts.SceneScript; import com.hfstudio.guidenh.guide.internal.resource.GuideResourceAccess; import com.hfstudio.guidenh.guide.internal.util.LangUtil; import com.hfstudio.guidenh.guide.mediawiki.MediaWikiListContext; @@ -49,6 +55,7 @@ import com.hfstudio.guidenh.guide.scene.LytGuidebookScene.PonderTimelineKeyframe; import com.hfstudio.guidenh.guide.scene.SceneBlockStatsEntry; import com.hfstudio.guidenh.guide.scene.SceneSoundCue; +import com.hfstudio.guidenh.guide.scene.SceneTagCompiler.ScenePlaceholder; import com.hfstudio.guidenh.guide.scene.StructureLibSceneBinding; import com.hfstudio.guidenh.guide.scene.support.GuideDebugLog; import com.hfstudio.guidenh.guide.sound.GuideSoundSpec; @@ -60,7 +67,12 @@ public class GuideSiteExportTask { public static final Gson GSON = new GsonBuilder().disableHtmlEscaping() .serializeNulls() .create(); - private static final int MAX_SCENE_STATE_VARIANTS = 256; + private static final int MAX_SCENE_STRUCTURE_TIER = 4; + private static final int MAX_SCENE_STRUCTURE_CHANNEL_VALUE = 4; + private static final int MAX_SCENE_STATE_VARIANTS = 125; + private static final long SCENE_MATERIALIZATION_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(30); + private static final long SCENE_MATERIALIZATION_STEP_NANOS = TimeUnit.MILLISECONDS.toNanos(2); + private static final long SCENE_MATERIALIZATION_WAIT_NANOS = TimeUnit.MILLISECONDS.toNanos(1); private final Path outDir; private final GuideSiteExportOptions options; @@ -213,6 +225,7 @@ public Result run() throws Exception { GuideSiteTemplateRegistry templates = new GuideSiteTemplateRegistry(); GuidePage compiledPage = PageCompiler .compile(scopedGuide, scopedGuide.getExtensions(), variant.parsedPage()); + materializeScenes(scopedGuide, compiledPage); List exportedScenes = exportScenes( guide, variant.parsedPage(), @@ -307,6 +320,7 @@ public Result run() throws Exception { } } finally { restoreMinecraftLanguage(originalMcLanguage); + sceneExporter.close(); } for (Map.Entry>> entry : searchEntriesByLanguage.entrySet()) { @@ -790,6 +804,47 @@ private List exportScenes(MutableGuide guide, ParsedGuid return htmlScenes; } + private void materializeScenes(Guide guide, GuidePage compiledPage) { + LytDocument document = compiledPage.document(); + if (!containsScenePlaceholder(document)) { + return; + } + + LytHost host = new LytHost(); + SceneScript sceneScript = new SceneScript(); + host.setCurrentPageCollection(guide); + host.setCurrentPageId( + compiledPage.id() + .toString()); + host.registerScript("Scene", sceneScript); + host.registerScript("GameScene", sceneScript); + host.mountDocument(document); + + long timeoutAt = System.nanoTime() + SCENE_MATERIALIZATION_TIMEOUT_NANOS; + while (host.hasWork() && System.nanoTime() < timeoutAt) { + host.step(System.nanoTime() + SCENE_MATERIALIZATION_STEP_NANOS); + if (host.hasWork()) { + LockSupport.parkNanos(SCENE_MATERIALIZATION_WAIT_NANOS); + } + } + host.mountDocument(null); + if (containsScenePlaceholder(document)) { + throw new IllegalStateException("Timed out while materializing GameScene for " + compiledPage.id()); + } + } + + private boolean containsScenePlaceholder(LytNode node) { + if (node instanceof ScenePlaceholder) { + return true; + } + for (LytNode child : node.getChildren()) { + if (containsScenePlaceholder(child)) { + return true; + } + } + return false; + } + private GuideSiteExportedScene exportScene(ParsedGuidePage parsedPage, LytGuidebookScene scene, GuideSiteTemplateRegistry templates, GuideSiteAssetRegistry assets, GuideSiteSceneRuntimeExporter exporter, GuideSitePageAssetExporter assetExporter, GuideSiteItemIconResolver itemIconResolver) throws Exception { @@ -1097,9 +1152,9 @@ private SceneStateManifestPlan buildSceneStateManifestPlan(LytGuidebookScene sce List ponderTicks = buildPonderTickStates(scene, ponderKeyframes); List structurePlans = buildStructureStatePlans(scene); - long variantCount = (long) visibleLayers.size() * (long) ponderTicks.size(); + long variantCount = multiplySceneVariantCounts(visibleLayers.size(), ponderTicks.size()); for (StructureStatePlan structurePlan : structurePlans) { - variantCount *= structurePlan.states.size(); + variantCount = multiplySceneVariantCounts(variantCount, structurePlan.stateCount()); if (variantCount > MAX_SCENE_STATE_VARIANTS) { warnSceneStateVariantLimit(variantCount); return null; @@ -1112,6 +1167,9 @@ private SceneStateManifestPlan buildSceneStateManifestPlan(LytGuidebookScene sce if (variantCount <= 1 && !scene.hasPonderData()) { return null; } + for (StructureStatePlan structurePlan : structurePlans) { + structurePlan.materializeStates(); + } LinkedHashMap controls = new LinkedHashMap<>(); if (visibleLayers.size() > 1) { @@ -1304,7 +1362,7 @@ private List buildStructureStatePlans(LytGuidebookScene scen tiers, selectableChannels, channelIds, - buildStructureVariantStates(tiers, channelIds, channelValues))); + channelValues)); } return plans; } @@ -1326,34 +1384,6 @@ private String buildStructureControlLabel(StructureLibSceneBinding binding, return "Structure"; } - private List buildStructureVariantStates(List tiers, List channelIds, - List> channelValues) { - ArrayList states = new ArrayList<>(); - appendStructureVariantStates(states, tiers, channelIds, channelValues, 0, new ArrayList<>()); - return states.isEmpty() - ? List.of(new StructureVariantState(StructureLibPreviewSelection.DEFAULT_MASTER_TIER, null)) - : states; - } - - private void appendStructureVariantStates(List states, List tiers, - List channelIds, List> channelValues, int channelIndex, List currentChannels) { - if (channelIndex >= channelValues.size()) { - for (Integer tier : tiers) { - LinkedHashMap channelState = new LinkedHashMap<>(channelIds.size()); - for (int i = 0; i < channelIds.size(); i++) { - channelState.put(channelIds.get(i), currentChannels.get(i)); - } - states.add(new StructureVariantState(tier, channelState)); - } - return; - } - for (Integer value : channelValues.get(channelIndex)) { - currentChannels.add(value); - appendStructureVariantStates(states, tiers, channelIds, channelValues, channelIndex + 1, currentChannels); - currentChannels.removeLast(); - } - } - private void addUniquePonderTickState(List states, int tick) { int normalized = Math.max(0, tick); if (!states.contains(normalized)) { @@ -1377,9 +1407,12 @@ private List buildTierStates(StructureLibSceneBinding binding, Structur .of(binding != null ? binding.getCurrentTier() : StructureLibPreviewSelection.DEFAULT_MASTER_TIER); } ArrayList tiers = new ArrayList<>(); + int maxTier = Math.min( + metadata.getTierData() + .getMaxValue(), + MAX_SCENE_STRUCTURE_TIER); for (int tier = metadata.getTierData() - .getMinValue(); tier <= metadata.getTierData() - .getMaxValue(); tier++) { + .getMinValue(); tier <= maxTier; tier++) { tiers.add(tier); } return tiers; @@ -1402,13 +1435,25 @@ private List buildChannelStates(StructureLibSceneMetadata.ChannelData c if (channelData == null || !channelData.isSelectable()) { return List.of(0); } - ArrayList states = new ArrayList<>(channelData.getMaxValue() + 1); - for (int value = channelData.getMinValue(); value <= channelData.getMaxValue(); value++) { + int maxValue = Math.min(channelData.getMaxValue(), MAX_SCENE_STRUCTURE_CHANNEL_VALUE); + ArrayList states = new ArrayList<>(maxValue + 1); + for (int value = channelData.getMinValue(); value <= maxValue; value++) { states.add(value); } return states; } + private long multiplySceneVariantCounts(long left, long right) { + if (left <= 0 || right <= 0) { + return 0; + } + if (left > MAX_SCENE_STATE_VARIANTS || right > MAX_SCENE_STATE_VARIANTS + || left > MAX_SCENE_STATE_VARIANTS / right) { + return MAX_SCENE_STATE_VARIANTS + 1L; + } + return left * right; + } + private GuideSiteHtmlCompiler.SceneResolver createSceneResolver(List exportedScenes) { return new GuideSiteHtmlCompiler.SceneResolver() { @@ -1552,19 +1597,57 @@ private static final class StructureStatePlan { private final List tiers; private final List selectableChannels; private final List channelIds; - private final List states; + private final List> channelValues; + private List states = List.of(); private StructureStatePlan(String bindingKey, @Nullable String structureName, String label, List tiers, List selectableChannels, List channelIds, - List states) { + List> channelValues) { this.bindingKey = bindingKey; this.structureName = structureName; this.label = label; this.tiers = tiers != null ? new ArrayList<>(tiers) : List.of(); this.selectableChannels = selectableChannels != null ? new ArrayList<>(selectableChannels) : List.of(); this.channelIds = channelIds != null ? new ArrayList<>(channelIds) : List.of(); - this.states = states != null ? new ArrayList<>(states) - : List.of(new StructureVariantState(StructureLibPreviewSelection.DEFAULT_MASTER_TIER, null)); + this.channelValues = channelValues != null ? new ArrayList<>(channelValues) : List.of(); + } + + private long stateCount() { + long count = Math.max(1, tiers.size()); + for (List values : channelValues) { + int valueCount = values != null ? values.size() : 0; + if (valueCount <= 0 || count > MAX_SCENE_STATE_VARIANTS / valueCount) { + return MAX_SCENE_STATE_VARIANTS + 1L; + } + count *= valueCount; + } + return count; + } + + private void materializeStates() { + ArrayList materialized = new ArrayList<>((int) stateCount()); + appendStates(materialized, 0, new ArrayList<>()); + states = materialized.isEmpty() + ? List.of(new StructureVariantState(StructureLibPreviewSelection.DEFAULT_MASTER_TIER, null)) + : materialized; + } + + private void appendStates(List out, int channelIndex, List currentChannels) { + if (channelIndex >= channelValues.size()) { + for (Integer tier : tiers) { + LinkedHashMap channelState = new LinkedHashMap<>(channelIds.size()); + for (int i = 0; i < channelIds.size(); i++) { + channelState.put(channelIds.get(i), currentChannels.get(i)); + } + out.add(new StructureVariantState(tier, channelState)); + } + return; + } + for (Integer value : channelValues.get(channelIndex)) { + currentChannels.add(value); + appendStates(out, channelIndex + 1, currentChannels); + currentChannels.removeLast(); + } } private boolean hasControls() { @@ -1584,12 +1667,16 @@ private Map toControlMap() { } if (!selectableChannels.isEmpty()) { ArrayList> channelControls = new ArrayList<>(selectableChannels.size()); - for (StructureLibSceneMetadata.ChannelData channelData : selectableChannels) { + for (int i = 0; i < selectableChannels.size(); i++) { + StructureLibSceneMetadata.ChannelData channelData = selectableChannels.get(i); LinkedHashMap channelControl = new LinkedHashMap<>(); channelControl.put("id", channelData.getChannelId()); channelControl.put("label", channelData.getLabel()); channelControl.put("min", channelData.getMinValue()); - channelControl.put("max", channelData.getMaxValue()); + channelControl.put( + "max", + channelValues.get(i) + .getLast()); channelControl.put("unsetLabel", GuidebookText.SceneNotSet.text()); channelControls.add(channelControl); } diff --git a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteHtmlCompiler.java b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteHtmlCompiler.java index 6678419d..ea034453 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteHtmlCompiler.java +++ b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteHtmlCompiler.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -26,9 +27,7 @@ import com.hfstudio.guidenh.guide.internal.markdown.MarkdownRuntimeBlocks.BlockquoteDirective; import com.hfstudio.guidenh.guide.internal.markdown.MarkdownRuntimeBlocks.QuoteIconSpec; import com.hfstudio.guidenh.guide.internal.mermaid.MermaidDiagramType; -import com.hfstudio.guidenh.guide.internal.mermaid.flowchart.FlowchartDocument; import com.hfstudio.guidenh.guide.internal.mermaid.flowchart.FlowchartParser; -import com.hfstudio.guidenh.guide.internal.mermaid.mindmap.MindmapDocument; import com.hfstudio.guidenh.guide.internal.mermaid.mindmap.MindmapParser; import com.hfstudio.guidenh.guide.sound.GuideSoundSpec; import com.hfstudio.guidenh.guide.sound.GuideSoundTrigger; @@ -38,6 +37,7 @@ import com.hfstudio.guidenh.libs.mdast.mdx.model.MdxJsxFlowElement; import com.hfstudio.guidenh.libs.mdast.mdx.model.MdxJsxTextElement; import com.hfstudio.guidenh.libs.mdast.model.MdAstAnyContent; +import com.hfstudio.guidenh.libs.mdast.model.MdAstBlockquote; import com.hfstudio.guidenh.libs.mdast.model.MdAstParent; import com.hfstudio.guidenh.libs.mdast.model.MdAstText; @@ -53,6 +53,11 @@ default String render(MdxJsxElementFields element, String defaultNamespace) { return render(recipeId, fallbackText, defaultNamespace); } + default String render(MdxJsxElementFields element, String defaultNamespace, + GuideSiteTemplateRegistry templates) { + return render(element, defaultNamespace); + } + String render(String recipeId, String fallbackText, String defaultNamespace); } @@ -279,6 +284,9 @@ private String compileNode(MdAstAnyContent node, GuideSiteTemplateRegistry templ currentPageId, sceneResolver); } + if (node instanceof MdAstBlockquote blockquote) { + return compileBlockquoteMarkdown(blockquote, templates, defaultNamespace, currentPageId, sceneResolver); + } if (node instanceof MdAstParent) { return compileChildren( ((MdAstParent) node).children(), @@ -408,12 +416,13 @@ private String compileCustomFlowElement(MdxJsxFlowElement flowElement, GuideSite if (isTooltipElement(flowElement)) return "

" + compileTooltip(flowElement, templates, defaultNamespace, currentPageId, sceneResolver) + "

"; - if (isRecipeElement(flowElement)) return compileRecipe(flowElement, defaultNamespace); + if (isRecipeElement(flowElement)) return compileRecipe(flowElement, defaultNamespace, templates); if (isSceneElement(flowElement)) return compileScene(flowElement, templates, defaultNamespace, currentPageId, sceneResolver); if (isFloatingImageElement(flowElement)) return compileFloatingImage(flowElement, templates, defaultNamespace, currentPageId, sceneResolver); - if (isLatexElement(flowElement)) return compileLatex(flowElement, true, templates); + if (isLatexElement(flowElement)) + return compileLatex(flowElement, true, templates, defaultNamespace, currentPageId, sceneResolver); String rendered = mdxTagRenderer .render(flowElement, defaultNamespace, currentPageId, templates, sceneResolver, this); if (rendered != null) return rendered; @@ -427,12 +436,13 @@ private String compileCustomTextElement(MdxJsxTextElement textElement, GuideSite if (isHtmlBreakElement(textElement)) return compileHtmlBreak(textElement); if (isTooltipElement(textElement)) return compileTooltip(textElement, templates, defaultNamespace, currentPageId, sceneResolver); - if (isRecipeElement(textElement)) return compileRecipe(textElement, defaultNamespace); + if (isRecipeElement(textElement)) return compileRecipe(textElement, defaultNamespace, templates); if (isSceneElement(textElement)) return compileScene(textElement, templates, defaultNamespace, currentPageId, sceneResolver); if (isFloatingImageElement(textElement)) return compileFloatingImage(textElement, templates, defaultNamespace, currentPageId, sceneResolver); - if (isLatexElement(textElement)) return compileLatex(textElement, false, templates); + if (isLatexElement(textElement)) + return compileLatex(textElement, false, templates, defaultNamespace, currentPageId, sceneResolver); String rendered = mdxTagRenderer .render(textElement, defaultNamespace, currentPageId, templates, sceneResolver, this); if (rendered != null) return rendered; @@ -445,7 +455,7 @@ private String compileParagraph(MdxJsxElementFields el, GuideSiteTemplateRegistr String defaultNamespace, @Nullable ResourceLocation currentPageId, SceneResolver sceneResolver) { String displayFormula = extractSoleDisplayLatexFromElement(el); if (displayFormula != null) { - return renderLatex(displayFormula, null, 1.0f, 100.0f, false, null, 0, 0, true, templates); + return renderLatex(displayFormula, null, 1.0f, 100.0f, false, null, null, 0, 0, true, templates); } return "

" + compileChildren(el.children(), templates, defaultNamespace, currentPageId, sceneResolver) + "

"; @@ -478,36 +488,82 @@ private String compileBlockquoteMdx(MdxJsxElementFields el, GuideSiteTemplateReg + ""; } + private String compileBlockquoteMarkdown(MdAstBlockquote blockquote, GuideSiteTemplateRegistry templates, + String defaultNamespace, @Nullable ResourceLocation currentPageId, SceneResolver sceneResolver) { + BlockquoteDirective directive = MarkdownRuntimeBlocks.parseBlockquoteDirective(blockquote); + if (directive != null && directive.alertType() != null) { + return compileAlertBoxMdx(directive, templates, defaultNamespace, currentPageId, sceneResolver); + } + if (directive != null) { + return compileQuoteBoxMdx(directive, templates, defaultNamespace, currentPageId, sceneResolver); + } + return "
" + + compileChildren(blockquote.children(), templates, defaultNamespace, currentPageId, sceneResolver) + + "
"; + } + private String compileAlertBoxMdx(BlockquoteDirective directive, GuideSiteTemplateRegistry templates, String defaultNamespace, @Nullable ResourceLocation currentPageId, SceneResolver sceneResolver) { - String body = compileChildren(directive.children(), templates, defaultNamespace, currentPageId, sceneResolver); - String typeName = directive.alertType() - .displayText(); - return "
" + escapeHtml(typeName) + "" + body + "
"; + return compileQuoteBoxMdx(directive, templates, defaultNamespace, currentPageId, sceneResolver); } private String compileQuoteBoxMdx(BlockquoteDirective directive, GuideSiteTemplateRegistry templates, String defaultNamespace, @Nullable ResourceLocation currentPageId, SceneResolver sceneResolver) { - String body = compileChildren(directive.children(), templates, defaultNamespace, currentPageId, sceneResolver); - StringBuilder html = new StringBuilder("
"); - if (directive.title() != null) { - html.append("") - .append(escapeHtml(directive.title())) - .append("
"); + if (directive.title() != null || directive.icon() != null) { + html.append("
"); + if (directive.icon() != null) { + html.append( + renderQuoteIcon(directive.icon(), templates, defaultNamespace, currentPageId, sceneResolver)); + } + if (directive.title() != null) { + html.append("") + .append(escapeHtml(directive.title())) + .append(""); + } + html.append("
"); } - html.append(body) + html.append("
") + .append(body) + .append("
") .append("
"); return html.toString(); } + private String compileDirectiveBody(BlockquoteDirective directive, GuideSiteTemplateRegistry templates, + String defaultNamespace, @Nullable ResourceLocation currentPageId, SceneResolver sceneResolver) { + StringBuilder body = new StringBuilder(); + boolean directiveHandled = false; + for (MdAstAnyContent child : directive.children()) { + if (child == directive.firstParagraph() || directive.firstParagraph() == null && !directiveHandled) { + directiveHandled = true; + if (hasText(directive.remainingText())) { + body.append("

") + .append(compileText(directive.remainingText(), templates, defaultNamespace, currentPageId)) + .append("

"); + } + continue; + } + body.append(compileChildren(List.of(child), templates, defaultNamespace, currentPageId, sceneResolver)); + } + return body.toString(); + } + private String compileListMdx(MdxJsxElementFields el, GuideSiteTemplateRegistry templates, String defaultNamespace, @Nullable ResourceLocation currentPageId, SceneResolver sceneResolver) { String tag = "ol".equals(el.name()) ? "ol" : "ul"; @@ -554,24 +610,7 @@ private String compileCodeBlockMdx(MdxJsxElementFields el, GuideSiteTemplateRegi return rendered != null ? rendered : GuideSiteGraphRenderer.renderFileTree(codeText); } if ("mermaid".equals(lang)) { - try { - MermaidDiagramType type = MermaidDiagramType.detect(codeText); - switch (type) { - case MINDMAP -> { - MindmapDocument doc = MindmapParser.parse(codeText); - return GuideSiteGraphRenderer.renderMermaidTree(doc); - } - case FLOWCHART -> { - FlowchartDocument doc = FlowchartParser.parse(codeText); - return GuideSiteGraphRenderer.renderFlowchart(doc); - } - case UNKNOWN -> { - return CODE_BLOCK_RENDERER.render("mermaid", codeText, width, height); - } - } - } catch (Exception ignored) { - return CODE_BLOCK_RENDERER.render("mermaid", codeText, width, height); - } + return renderMermaidCodeBlock(codeText, lang, width, height); } if ("funcgraph".equals(lang) || "functiongraph".equals(lang)) { try { @@ -585,6 +624,19 @@ private String compileCodeBlockMdx(MdxJsxElementFields el, GuideSiteTemplateRegi return CODE_BLOCK_RENDERER.render(lang, codeText, width, height); } + private String renderMermaidCodeBlock(String codeText, String language, @Nullable Integer width, + @Nullable Integer height) { + try { + return switch (MermaidDiagramType.detect(codeText)) { + case FLOWCHART -> GuideSiteGraphRenderer.renderFlowchart(FlowchartParser.parse(codeText), Map.of()); + case MINDMAP -> GuideSiteGraphRenderer.renderMermaidTree(MindmapParser.parse(codeText), Map.of()); + case UNKNOWN -> CODE_BLOCK_RENDERER.render(language, codeText, width, height); + }; + } catch (IllegalArgumentException ignored) { + return CODE_BLOCK_RENDERER.render(language, codeText, width, height); + } + } + @Nullable private static Integer parseMetaInt(String meta, String key) { if (meta == null || meta.isEmpty()) { @@ -833,8 +885,9 @@ private boolean isLatexElement(MdxJsxElementFields element) { return "Latex".equals(element.name()); } - private String compileRecipe(MdxJsxElementFields element, String defaultNamespace) { - return recipeTagRenderer.render(element, defaultNamespace); + private String compileRecipe(MdxJsxElementFields element, String defaultNamespace, + GuideSiteTemplateRegistry templates) { + return recipeTagRenderer.render(element, defaultNamespace, templates); } private String compileSpoiler(MdxJsxElementFields element, GuideSiteTemplateRegistry templates, @@ -1094,7 +1147,8 @@ private String buildCroppedFloatingImageHtml(String src, String alt, @Nullable S return html.toString(); } - private String compileLatex(MdxJsxElementFields element, boolean display, GuideSiteTemplateRegistry templates) { + private String compileLatex(MdxJsxElementFields element, boolean display, GuideSiteTemplateRegistry templates, + String defaultNamespace, @Nullable ResourceLocation currentPageId, SceneResolver sceneResolver) { String formula = element.getAttributeString("formula", null); if (formula == null || formula.trim() .isEmpty()) { @@ -1103,7 +1157,8 @@ private String compileLatex(MdxJsxElementFields element, boolean display, GuideS String color = element.getAttributeString("color", null); float scale = parseFloat(element.getAttributeString("scale", null), 1.0f); float sourceScale = parseFloat(element.getAttributeString("sourceScale", null), 100.0f); - boolean showTooltip = readBoolean(element, "showTooltip", false); + String tooltipHtml = compileLatexTooltip(element, templates, defaultNamespace, currentPageId, sceneResolver); + boolean showTooltip = tooltipHtml == null && readBoolean(element, "showTooltip", false); String valign = element.getAttributeString("valign", null); int offsetX = readInt(element, "offsetX", 0); int offsetY = readInt(element, "offsetY", 0); @@ -1113,6 +1168,7 @@ private String compileLatex(MdxJsxElementFields element, boolean display, GuideS scale, sourceScale, showTooltip, + tooltipHtml, valign, offsetX, offsetY, @@ -1121,8 +1177,8 @@ private String compileLatex(MdxJsxElementFields element, boolean display, GuideS } private String renderLatex(String formula, @Nullable String color, float scale, float sourceScale, - boolean showTooltip, @Nullable String valign, int offsetX, int offsetY, boolean display, - GuideSiteTemplateRegistry templates) { + boolean showTooltip, @Nullable String tooltipHtml, @Nullable String valign, int offsetX, int offsetY, + boolean display, GuideSiteTemplateRegistry templates) { String tag = display ? "div" : "span"; StringBuilder classes = new StringBuilder( display ? "guide-latex guide-latex-display" : "guide-latex guide-latex-inline"); @@ -1168,7 +1224,9 @@ private String renderLatex(String formula, @Nullable String color, float scale, } String templateId = null; - if (showTooltip) { + if (tooltipHtml != null) { + templateId = templates.create(tooltipHtml); + } else if (showTooltip) { templateId = templates.create("" + escapeHtml(formula) + ""); } @@ -1176,8 +1234,11 @@ private String renderLatex(String formula, @Nullable String color, float scale, html.append("<") .append(tag) .append(" class=\"") - .append(classes) - .append("\""); + .append(classes); + if (templateId != null) { + html.append(" guide-tooltip"); + } + html.append("\""); if (templateId != null) { html.append(" data-template=\"") .append(escapeAttribute(templateId)) @@ -1203,7 +1264,28 @@ private String renderLatexBody(String formula, @Nullable GuideSiteLatexExporter. return "\"""; + + "\" draggable=\"false\">"; + } + + @Nullable + private String compileLatexTooltip(MdxJsxElementFields element, GuideSiteTemplateRegistry templates, + String defaultNamespace, @Nullable ResourceLocation currentPageId, SceneResolver sceneResolver) { + if (!element.children() + .isEmpty()) { + String richTooltip = compileChildren( + element.children(), + templates, + defaultNamespace, + currentPageId, + sceneResolver); + if (hasText(richTooltip)) { + return richTooltip; + } + } + String tooltip = element.getAttributeString("tooltip", null); + return tooltip != null && !tooltip.trim() + .isEmpty() ? GuideSiteSceneAnnotationSerializer.renderTooltipHtml(new TextTooltip(tooltip), currentPageId) + : null; } private int displayWidth(GuideSiteLatexExporter.ExportedLatex exported, float scale) { @@ -1259,7 +1341,8 @@ private String compileLatexText(String text, GuideSiteTemplateRegistry templates StringBuilder html = new StringBuilder(); for (MarkdownLatexShorthand.Segment segment : MarkdownLatexShorthand.split(text)) { if (segment.isFormula()) { - html.append(renderLatex(segment.getValue(), null, 1.0f, 100.0f, false, null, 0, 0, false, templates)); + html.append( + renderLatex(segment.getValue(), null, 1.0f, 100.0f, false, null, null, 0, 0, false, templates)); } else { html.append(escapeHtml(segment.getValue())); } diff --git a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteLatexExporter.java b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteLatexExporter.java index 6852e8b2..662fd956 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteLatexExporter.java +++ b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteLatexExporter.java @@ -61,7 +61,7 @@ public ExportedLatex export(String formula, int fillColorArgb, float sourceScale e.getMessage()); return null; } catch (Exception e) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [GuideSiteLatexExporter] Failed to export LaTeX formula '{}': {}", formula, e.getMessage(), diff --git a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteRecipeExporter.java b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteRecipeExporter.java index f7417f0a..cdb82e8f 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteRecipeExporter.java +++ b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteRecipeExporter.java @@ -19,6 +19,13 @@ public class GuideSiteRecipeExporter { /** NEI slot chrome is commonly {@code 18脳18} pixels; vanilla/GT handlers draw {@code 16脳16} items inset by 1px. */ public static final int NEI_SLOT_GUI_PIXELS = 18; + @Nullable + private GuideSiteTemplateRegistry tooltipTemplates; + + public void setTooltipTemplates(@Nullable GuideSiteTemplateRegistry tooltipTemplates) { + this.tooltipTemplates = tooltipTemplates; + } + public String renderHtmlGrid(List> ingredients, String resultItemId) { return renderGrid( unresolvedItems(ingredients), @@ -97,9 +104,7 @@ private String renderGrid(List> ingredients, GuideSi html.append("
"); - // Emit the native `title=` tooltip so hovering the result slot reports the - // ItemStack display name even though we don't register a full template. - GuideSiteItemHtml.appendIcon(html, resultItem, null, 1f, true); + appendTooltipIcon(html, resultItem); html.append("
"); html.append(""); return html.toString(); @@ -269,7 +274,7 @@ private void appendSlotBoxes(StringBuilder html, List safeCandidates) { for (int i = 0; i < safeCandidates.size(); i++) { int beforeIcon = html.length(); - GuideSiteItemHtml.appendIcon(html, safeCandidates.get(i), null, 1f, true); + appendTooltipIcon(html, safeCandidates.get(i)); if (safeCandidates.size() > 1 && i == 0) { int classAttr = html.indexOf("class=\"", beforeIcon); if (classAttr >= 0) { @@ -280,6 +285,27 @@ private void appendSlotContents(StringBuilder html, List } } + private void appendTooltipIcon(StringBuilder html, GuideSiteExportedItem item) { + GuideSiteTemplateRegistry templates = tooltipTemplates; + if (templates == null || item.isEmpty()) { + GuideSiteItemHtml.appendIcon(html, item, null); + return; + } + + String label = item.displayName() + .isEmpty() ? item.itemId() : item.displayName(); + String templateId = templates.create( + "

" + GuideSiteItemHtml.escapeHtml(label) + + "

" + + GuideSiteItemHtml.escapeHtml(item.itemId()) + + "

"); + html.append(""); + GuideSiteItemHtml.appendIcon(html, item, null); + html.append(""); + } + public List> ingredientsFromVanillaEntry(RecipeLookup.Entry entry) { List> ingredients = new ArrayList<>(); for (int i = 0; i < entry.input3x3.length; i++) { diff --git a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteRecipeTagRenderer.java b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteRecipeTagRenderer.java index 1c00c237..5c033b19 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteRecipeTagRenderer.java +++ b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteRecipeTagRenderer.java @@ -372,6 +372,16 @@ public String render(MdxJsxElementFields element, String defaultNamespace) { usageQuery)); } + @Override + public String render(MdxJsxElementFields element, String defaultNamespace, GuideSiteTemplateRegistry templates) { + exporter.setTooltipTemplates(templates); + try { + return render(element, defaultNamespace); + } finally { + exporter.setTooltipTemplates(null); + } + } + @Override public String render(String recipeId, String fallbackText, String defaultNamespace) { return renderInternal( diff --git a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneRuntimeExporter.java b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneRuntimeExporter.java index b01402e7..cd8ac52a 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneRuntimeExporter.java +++ b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneRuntimeExporter.java @@ -7,6 +7,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.zip.GZIPOutputStream; import javax.imageio.ImageIO; @@ -29,6 +32,7 @@ import com.hfstudio.guidenh.guide.scene.GuidebookSceneLayerSelection; import com.hfstudio.guidenh.guide.scene.LytGuidebookScene; import com.hfstudio.guidenh.guide.scene.support.GuideDebugLog; +import com.hfstudio.guidenh.guide.siteexport.site.GuideSiteSceneTessellatorCapture.TextureExportCache; import guideme.flatbuffers.scene.ExpAnimatedTexturePart; import guideme.flatbuffers.scene.ExpAnimatedTexturePartFrame; @@ -42,7 +46,7 @@ import guideme.flatbuffers.scene.ExpVertexFormat; import guideme.flatbuffers.scene.ExpVertexFormatElement; -public class GuideSiteSceneRuntimeExporter { +public class GuideSiteSceneRuntimeExporter implements AutoCloseable { private static final int PLACEHOLDER_SCALE = 2; private static final ResourceLocation RAIN_TEXTURE = new ResourceLocation( @@ -55,14 +59,29 @@ public class GuideSiteSceneRuntimeExporter { private static final String SNOW_ANIMATED_TEXTURE_ID = "guidenh-weather-snow"; private final GuideSiteAssetRegistry assets; + private final TextureExportCache textureCache = new TextureExportCache(); + private final ExecutorService encodingExecutor = Executors.newSingleThreadExecutor(runnable -> { + Thread thread = new Thread(runnable, "guidenh-site-scene-encode"); + thread.setDaemon(true); + return thread; + }); public GuideSiteSceneRuntimeExporter(GuideSiteAssetRegistry assets) { this.assets = assets; } public GuideSiteExportedScene exportScene(LytGuidebookScene scene) throws Exception { - byte[] placeholderBytes = renderPlaceholder(scene); - byte[] sceneBytes = exportScenePayload(scene); + BufferedImage placeholderImage = renderPlaceholderImage(scene); + Future placeholderEncoding = encodingExecutor.submit(() -> encodePng(placeholderImage)); + byte[] sceneBytes; + byte[] placeholderBytes; + try { + sceneBytes = exportScenePayload(scene); + placeholderBytes = placeholderEncoding.get(); + } catch (Exception e) { + placeholderEncoding.cancel(true); + throw e; + } GuideSiteSceneExporter exporter = new GuideSiteSceneExporter(assets, () -> placeholderBytes, () -> sceneBytes); GuideSiteSceneExporter.SceneFiles files = exporter.writeSceneAssets(); @@ -73,7 +92,12 @@ public GuideSiteExportedScene exportScene(LytGuidebookScene scene) throws Except scene.getSceneHeight()); } - private byte[] renderPlaceholder(LytGuidebookScene scene) throws Exception { + @Override + public void close() { + encodingExecutor.shutdownNow(); + } + + private BufferedImage renderPlaceholderImage(LytGuidebookScene scene) throws Exception { int originalBackground = scene.getSceneBackgroundColor(); int originalBorder = scene.getSceneBorderColor(); int originalWidth = scene.getSceneWidth(); @@ -103,9 +127,7 @@ private byte[] renderPlaceholder(LytGuidebookScene scene) throws Exception { image = framebuffer.render(scene); } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ImageIO.write(image, "png", out); - return out.toByteArray(); + return image; } finally { scene.setSceneBackgroundColor(originalBackground); scene.setSceneBorderColor(originalBorder); @@ -117,11 +139,20 @@ private byte[] renderPlaceholder(LytGuidebookScene scene) throws Exception { } } + private byte[] encodePng(BufferedImage image) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ImageIO.write(image, "png", out); + return out.toByteArray(); + } + private byte[] exportScenePayload(LytGuidebookScene scene) throws Exception { Matrix4f inverseViewMatrix = new Matrix4f( scene.getCamera() .getViewMatrix()).invert(); - GuideSiteSceneTessellatorCapture recorder = new GuideSiteSceneTessellatorCapture(assets, inverseViewMatrix); + GuideSiteSceneTessellatorCapture recorder = new GuideSiteSceneTessellatorCapture( + assets, + textureCache, + inverseViewMatrix); int width = Math.max(16, scene.getSceneWidth()); int height = Math.max(16, scene.getSceneHeight()); @@ -134,7 +165,7 @@ private byte[] exportScenePayload(LytGuidebookScene scene) throws Exception { GuideSiteSceneTessellatorCapture.RecordingResult result = recorder.finish(); if (result.meshes.isEmpty()) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "Scene site export captured no tessellated meshes for a {}x{} scene; exported 3D preview will be blank.", width, height); diff --git a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneTessellatorCapture.java b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneTessellatorCapture.java index c29639c8..62c25b7f 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneTessellatorCapture.java +++ b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneTessellatorCapture.java @@ -1,10 +1,12 @@ package com.hfstudio.guidenh.guide.siteexport.site; import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -36,6 +38,7 @@ public class GuideSiteSceneTessellatorCapture { private static volatile @Nullable GuideSiteSceneTessellatorCapture ACTIVE; private final GuideSiteAssetRegistry assets; + private final TextureExportCache textureCache; private final Matrix4f inverseViewMatrix; private final Matrix4f currentWorldMatrix = new Matrix4f(); private final Matrix4f modelViewMatrix = new Matrix4f(); @@ -57,8 +60,10 @@ public class GuideSiteSceneTessellatorCapture { @Nullable private String currentSourceTextureId; - public GuideSiteSceneTessellatorCapture(GuideSiteAssetRegistry assets, Matrix4f inverseViewMatrix) { + public GuideSiteSceneTessellatorCapture(GuideSiteAssetRegistry assets, TextureExportCache textureCache, + Matrix4f inverseViewMatrix) { this.assets = assets; + this.textureCache = textureCache; this.inverseViewMatrix = new Matrix4f(inverseViewMatrix); } @@ -103,7 +108,7 @@ public void setCurrentSourceTextureId(@Nullable String currentSourceTextureId) { public void startDrawing(int drawMode) { if (drawing) { // A previous batch was not properly closed, so drop it before recording a new one. - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "Scene capture startDrawing called while already drawing (mode={}); discarding previous unclosed batch", drawMode); drawing = false; @@ -132,7 +137,7 @@ public int draw() { captureCurrentMesh(); } } catch (Throwable e) { - GuideDebugLog.warnAlways("Scene capture mesh export failed ({} vertices)", vertexCount, e); + GuideDebugLog.warn("Scene capture mesh export failed ({} vertices)", vertexCount, e); } finally { currentVertexBytes = EMPTY_VERTEX_BYTES; currentVertexCount = 0; @@ -229,7 +234,7 @@ private TextureExport exportCurrentTexture() throws Exception { int level0Width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH); int level0Height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT); if (level0Width <= 0 || level0Height <= 0) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "exportCurrentTexture: bound texture id={} has invalid level-0 dimensions {}x{}; skipping", textureId, level0Width, @@ -254,6 +259,30 @@ private TextureExport exportCurrentTexture() throws Exception { exportHeight = lh; if (lw <= MAX_EXPORT_TEXTURE_SIZE && lh <= MAX_EXPORT_TEXTURE_SIZE) break; } + } + + int magFilter = GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER); + int minFilter = GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER); + boolean linearFiltering = magFilter == GL11.GL_LINEAR; + boolean useMipmaps = minFilter == GL11.GL_NEAREST_MIPMAP_NEAREST + || minFilter == GL11.GL_LINEAR_MIPMAP_NEAREST + || minFilter == GL11.GL_NEAREST_MIPMAP_LINEAR + || minFilter == GL11.GL_LINEAR_MIPMAP_LINEAR; + TextureCacheKey cacheKey = new TextureCacheKey( + textureId, + level0Width, + level0Height, + exportMipLevel, + currentSourceTextureId, + linearFiltering, + useMipmaps); + TextureExport cached = textureCache.get(cacheKey); + if (cached != null) { + textures.put(textureId, cached); + return cached; + } + + if (exportMipLevel > 0) { GuideDebugLog.debugAlways( "exportCurrentTexture: texture id={} is {}x{} - using mip level {} ({}x{}) for site export", textureId, @@ -274,15 +303,14 @@ private TextureExport exportCurrentTexture() throws Exception { GL11.glGetTexImage(GL11.GL_TEXTURE_2D, exportMipLevel, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels); BufferedImage image = new BufferedImage(exportWidth, exportHeight, BufferedImage.TYPE_INT_ARGB); - for (int y = 0; y < exportHeight; y++) { - for (int x = 0; x < exportWidth; x++) { - int index = (x + y * exportWidth) * 4; - int r = pixels.get(index) & 0xFF; - int g = pixels.get(index + 1) & 0xFF; - int b = pixels.get(index + 2) & 0xFF; - int a = pixels.get(index + 3) & 0xFF; - image.setRGB(x, y, a << 24 | r << 16 | g << 8 | b); - } + int[] argbPixels = ((DataBufferInt) image.getRaster() + .getDataBuffer()).getData(); + for (int pixelIndex = 0, byteIndex = 0; pixelIndex < argbPixels.length; pixelIndex++, byteIndex += 4) { + int r = pixels.get(byteIndex) & 0xFF; + int g = pixels.get(byteIndex + 1) & 0xFF; + int b = pixels.get(byteIndex + 2) & 0xFF; + int a = pixels.get(byteIndex + 3) & 0xFF; + argbPixels[pixelIndex] = a << 24 | r << 16 | g << 8 | b; } ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -290,14 +318,6 @@ private TextureExport exportCurrentTexture() throws Exception { String texturePath = assets.writeShared("scene-textures", ".png", out.toByteArray()); - int magFilter = GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER); - int minFilter = GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER); - boolean linearFiltering = magFilter == GL11.GL_LINEAR; - boolean useMipmaps = minFilter == GL11.GL_NEAREST_MIPMAP_NEAREST - || minFilter == GL11.GL_LINEAR_MIPMAP_NEAREST - || minFilter == GL11.GL_NEAREST_MIPMAP_LINEAR - || minFilter == GL11.GL_LINEAR_MIPMAP_LINEAR; - TextureExport export = new TextureExport( "gltex-" + textureId, texturePath, @@ -305,6 +325,7 @@ private TextureExport exportCurrentTexture() throws Exception { linearFiltering, useMipmaps); textures.put(textureId, export); + textureCache.put(cacheKey, export); return export; } finally { if (savedActiveUnit != OpenGlHelper.defaultTexUnit) { @@ -770,4 +791,23 @@ private static class TextureExport { } } + public static class TextureExportCache { + + private final Map exports = new HashMap<>(); + + public TextureExportCache() {} + + @Nullable + private TextureExport get(TextureCacheKey key) { + return exports.get(key); + } + + private void put(TextureCacheKey key, TextureExport export) { + exports.put(key, export); + } + } + + private record TextureCacheKey(int textureId, int level0Width, int level0Height, int exportMipLevel, + @Nullable String sourceTextureId, boolean linearFiltering, boolean useMipmaps) {} + } diff --git a/src/main/java/com/hfstudio/guidenh/guide/sound/GuideSoundPlayback.java b/src/main/java/com/hfstudio/guidenh/guide/sound/GuideSoundPlayback.java index 3d034684..ab241f84 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/sound/GuideSoundPlayback.java +++ b/src/main/java/com/hfstudio/guidenh/guide/sound/GuideSoundPlayback.java @@ -114,6 +114,6 @@ private static void warnLowFrequency(ResourceLocation soundId, RuntimeException return; } LAST_WARNED_AT.put(key, now); - GuideDebugLog.warnAlways("[GuideNH] [GuideSoundPlayback] Failed to play sound {}", soundId, e); + GuideDebugLog.warn("[GuideNH] [GuideSoundPlayback] Failed to play sound {}", soundId, e); } } diff --git a/src/main/java/com/hfstudio/guidenh/integration/GuideNhClientIntegrationBootstrap.java b/src/main/java/com/hfstudio/guidenh/integration/GuideNhClientIntegrationBootstrap.java index 75e10f0f..b73539e0 100644 --- a/src/main/java/com/hfstudio/guidenh/integration/GuideNhClientIntegrationBootstrap.java +++ b/src/main/java/com/hfstudio/guidenh/integration/GuideNhClientIntegrationBootstrap.java @@ -80,6 +80,30 @@ public static void preInitClient() { if (Mods.BetterQuesting.isModLoaded()) { BqGuidePageUriHandler.register(); + // TODO: Enable these registrations after BetterQuesting releases the editor and text-box extension APIs. + // TextEditorActionRegistry.register( + // new ResourceLocation("guidenh", "guide_link"), + // new TextEditorMacro("guidenh.compat.bq.insert_guide_link", "[guide] ", "[/guide]")); + // ResourceLocation guidePageInteraction = new ResourceLocation("guidenh", "guide_page"); + // PanelTextBox.registerTextProcessor( + // guidePageInteraction, + // text -> BqGuidePageLinks.replaceGuideTags( + // text, + // (target, label) -> PanelTextBox.createInteractiveText(guidePageInteraction, target, label))); + // PanelTextBox.registerTextInteraction(guidePageInteraction, new PanelTextBox.TextInteraction() { + // + // @Override + // public boolean onClick(String target) { + // return BqGuidePageUriHandler.open(BqGuidePageLinks.parsePageSpec(target)); + // } + // + // @Override + // public List getTooltip(String target) { + // PageAnchor anchor = BqGuidePageLinks.parsePageSpec(target); + // return anchor != null ? BqGuidePageLinks.getTooltip(anchor) : null; + // } + // }); + GuideNhClientIntegrationRegistry.global() .registerQuestHoverProvider(new BetterQuestingQuestHoverProvider()); } diff --git a/src/main/java/com/hfstudio/guidenh/integration/ae2/Ae2CableConnectionRules.java b/src/main/java/com/hfstudio/guidenh/integration/ae2/Ae2CableConnectionRules.java index 77af693e..ad406c96 100644 --- a/src/main/java/com/hfstudio/guidenh/integration/ae2/Ae2CableConnectionRules.java +++ b/src/main/java/com/hfstudio/guidenh/integration/ae2/Ae2CableConnectionRules.java @@ -1,33 +1,20 @@ package com.hfstudio.guidenh.integration.ae2; -import org.jetbrains.annotations.Nullable; +import net.minecraftforge.common.util.ForgeDirection; -import appeng.api.util.AEColor; +import appeng.me.helpers.AENetworkProxy; public class Ae2CableConnectionRules { private Ae2CableConnectionRules() {} - public static boolean shouldConnect(boolean sourceHasSidePart, boolean sourceBlocked, boolean sourceCanConnect, - boolean neighborCanConnect, boolean neighborFaceBlockedByPart, boolean neighborBlocked, - boolean neighborAcceptsSide, @Nullable AEColor sourceCableColor, @Nullable AEColor neighborCableColor) { - return !sourceHasSidePart && !sourceBlocked - && sourceCanConnect - && neighborCanConnect - && !neighborFaceBlockedByPart - && !neighborBlocked - && neighborAcceptsSide - && areCableColorsCompatible(sourceCableColor, neighborCableColor); - } - - public static boolean facePartBlocksAdjacentCable(boolean hasFacePart, boolean facePartCanConnect) { - return hasFacePart && !facePartCanConnect; - } - - public static boolean areCableColorsCompatible(@Nullable AEColor color1, @Nullable AEColor color2) { - if (color1 == null || color2 == null) { - return true; - } - return color1 == AEColor.Transparent || color2 == AEColor.Transparent || color1 == color2; + public static boolean shouldConnect(AENetworkProxy source, ForgeDirection sourceDirection, AENetworkProxy target, + ForgeDirection targetDirection) { + return source.getConnectableSides() + .contains(sourceDirection) + && target.getConnectableSides() + .contains(targetDirection) + && source.getColor() + .matches(target.getColor()); } } diff --git a/src/main/java/com/hfstudio/guidenh/integration/ae2/Ae2ExternalGridPart.java b/src/main/java/com/hfstudio/guidenh/integration/ae2/Ae2ExternalGridPart.java new file mode 100644 index 00000000..f7d1fdb6 --- /dev/null +++ b/src/main/java/com/hfstudio/guidenh/integration/ae2/Ae2ExternalGridPart.java @@ -0,0 +1,8 @@ +package com.hfstudio.guidenh.integration.ae2; + +import appeng.me.helpers.AENetworkProxy; + +public interface Ae2ExternalGridPart { + + AENetworkProxy guideNh$getExternalConnectionProxy(); +} diff --git a/src/main/java/com/hfstudio/guidenh/integration/ae2/Ae2Helpers.java b/src/main/java/com/hfstudio/guidenh/integration/ae2/Ae2Helpers.java index a934c28e..8b61838e 100644 --- a/src/main/java/com/hfstudio/guidenh/integration/ae2/Ae2Helpers.java +++ b/src/main/java/com/hfstudio/guidenh/integration/ae2/Ae2Helpers.java @@ -29,13 +29,9 @@ import com.hfstudio.guidenh.guide.scene.support.GuideBlockStatsStackResolver; import appeng.api.AEApi; -import appeng.api.implementations.parts.IPartCable; -import appeng.api.networking.IGridHost; import appeng.api.parts.IFacadePart; import appeng.api.parts.IPart; import appeng.api.parts.PartItemStack; -import appeng.api.util.AECableType; -import appeng.api.util.AEColor; import appeng.me.helpers.AENetworkProxy; import appeng.me.helpers.IGridProxyable; import appeng.parts.CableBusContainer; @@ -52,7 +48,7 @@ /** * AE2 guide preview: applies server-authoritative AE2 preview bytes from {@link GuidebookLevel#previewAuthorityStore()} * ({@link Ae2ServerPreviewRegistration#SUPPLEMENT_ID} cable bus; {@link Ae2BaseTileNetworkStreamPreview#SUPPLEMENT_ID} - * other {@link AEBaseTile}), merged with locally inferred cable facings where applicable. + * other {@link AEBaseTile}), merged with locally inferred cable facings for the current preview layout. */ public class Ae2Helpers { @@ -154,10 +150,21 @@ public static void prepare(GuidebookLevel level) { for (TileEntity te : level.getTileEntities()) { CableBusContainer container = resolveCableContainer(te); if (container != null) { - syncCableBusConnections(container, level); syncCableBusSidePartStreams(container, level); } } + for (TileEntity te : level.getTileEntities()) { + CableBusContainer container = resolveCableContainer(te); + if (container != null) { + container.updateConnections(); + } + } + for (TileEntity te : level.getTileEntities()) { + CableBusContainer container = resolveCableContainer(te); + if (container != null) { + syncCableBusConnections(container, level); + } + } if (level.getOrCreateFakeWorld() instanceof GuidebookPreviewWorld previewWorld) { previewWorld.syncLoadedTileEntities(level.getTileEntities()); } @@ -240,7 +247,6 @@ public static void syncCableBusConnections(CableBusContainer container, Guideboo return; } - int csDirections = computeCableConnectionMask(container, level); TileEntity tile = container.getTile(); long posKey = GuidebookLevel.packPos(tile.xCoord, tile.yCoord, tile.zCoord); byte[] raw = level.previewAuthorityStore() @@ -248,15 +254,12 @@ public static void syncCableBusConnections(CableBusContainer container, Guideboo Ae2CablePreviewSnapshot snap = raw != null ? Ae2CablePreviewWireCodec.decode(raw) : Ae2CablePreviewSnapshot.EMPTY; - int poweredMask = 1 << ForgeDirection.UNKNOWN.ordinal(); + int csDirections = computeCableConnectionMask(container, level); int csOut; int sideOut; if (snap.hasCableCore()) { csOut = (snap.gridCsUnsigned() & ~CS_DIRECTION_MASK) | (csDirections & CS_DIRECTION_MASK); sideOut = snap.sideOut(); - if (sideOut != 0 && (csOut & poweredMask) == 0) { - csOut |= poweredMask; - } } else { csOut = csDirections; sideOut = 0; @@ -470,56 +473,19 @@ public static int computeCableConnectionMask(CableBusContainer container, Guideb int y = tile.yCoord; int z = tile.zCoord; - AEColor sourceCableColor = getCableColorFromContainer(container); + if (!(container.getPart(ForgeDirection.UNKNOWN) instanceof PartCable cable)) { + return 0; + } + AENetworkProxy sourceProxy = cable.getProxy(); int cs = 0; for (ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS) { TileEntity adj = level.getTileEntity(x + dir.offsetX, y + dir.offsetY, z + dir.offsetZ); - CableBusContainer adjContainer = resolveCableContainer(adj); - boolean sourceHasSidePart = container.getPart(dir) != null; - boolean sourceBlocked = isBlocked(container, dir); - boolean sourceCanConnect = container.getCableConnectionType(dir) != AECableType.NONE; - boolean neighborCanConnect; - boolean neighborFaceBlockedByPart = false; - boolean neighborBlocked = false; - boolean neighborAcceptsSide = true; - AEColor neighborCableColor = null; - - if (adjContainer != null) { - ForgeDirection opposite = dir.getOpposite(); - neighborCanConnect = canConnectCableBusOnSide(adjContainer, opposite); - neighborFaceBlockedByPart = Ae2CableConnectionRules - .facePartBlocksAdjacentCable(adjContainer.getPart(opposite) != null, neighborCanConnect); - neighborBlocked = isBlocked(adjContainer, opposite); - neighborCableColor = getCableColorFromContainer(adjContainer); - } else if (adj instanceof IGridHost adjHost) { - ForgeDirection opposite = dir.getOpposite(); - neighborCanConnect = adjHost.getCableConnectionType(opposite) != AECableType.NONE; - if (adjHost instanceof IGridProxyable adjProxyable) { - AENetworkProxy proxy = null; - try { - proxy = adjProxyable.getProxy(); - } catch (Throwable ignored) {} - if (proxy == null) { - continue; - } - neighborAcceptsSide = proxy.getConnectableSides() - .contains(opposite); - } - } else { + AENetworkProxy targetProxy = resolveExternalConnectionProxy(adj, dir.getOpposite()); + if (targetProxy == null) { continue; } - - if (!Ae2CableConnectionRules.shouldConnect( - sourceHasSidePart, - sourceBlocked, - sourceCanConnect, - neighborCanConnect, - neighborFaceBlockedByPart, - neighborBlocked, - neighborAcceptsSide, - sourceCableColor, - neighborCableColor)) { + if (!Ae2CableConnectionRules.shouldConnect(sourceProxy, dir, targetProxy, dir.getOpposite())) { continue; } cs |= (1 << dir.ordinal()); @@ -533,36 +499,28 @@ private static CableBusContainer resolveCableContainer(@Nullable TileEntity tile return Ae2CableStructureSupport.resolveCableContainer(tileEntity); } - private static boolean isBlocked(CableBusContainer container, ForgeDirection direction) { - try { - return container.isBlocked(direction); - } catch (Throwable ignored) { - return false; - } - } - @Optional.Method(modid = "appliedenergistics2") - private static boolean canConnectCableBusOnSide(CableBusContainer container, ForgeDirection direction) { - try { - return container.getCableConnectionType(direction) != AECableType.NONE; - } catch (Throwable ignored) { - return false; + @Nullable + private static AENetworkProxy resolveExternalConnectionProxy(@Nullable TileEntity tileEntity, + ForgeDirection direction) { + CableBusContainer container = resolveCableContainer(tileEntity); + if (container != null) { + IPart sidePart = container.getPart(direction); + if (sidePart != null) { + return sidePart instanceof Ae2ExternalGridPart externalPart + ? externalPart.guideNh$getExternalConnectionProxy() + : null; + } + IPart centerPart = container.getPart(ForgeDirection.UNKNOWN); + return getProxy(centerPart); } + return tileEntity instanceof IGridProxyable proxyable ? proxyable.getProxy() : null; } @Optional.Method(modid = "appliedenergistics2") @Nullable - private static AEColor getCableColorFromContainer(@Nullable CableBusContainer container) { - if (container == null) { - return null; - } - try { - IPart centerPart = container.getPart(ForgeDirection.UNKNOWN); - if (centerPart instanceof IPartCable cable) { - return cable.getCableColor(); - } - } catch (Throwable ignored) {} - return null; + private static AENetworkProxy getProxy(@Nullable IPart part) { + return part instanceof IGridProxyable proxyable ? proxyable.getProxy() : null; } @Optional.Method(modid = "appliedenergistics2") diff --git a/src/main/java/com/hfstudio/guidenh/integration/betterquesting/BqGuidePageLinks.java b/src/main/java/com/hfstudio/guidenh/integration/betterquesting/BqGuidePageLinks.java index c0b5f438..6657bd15 100644 --- a/src/main/java/com/hfstudio/guidenh/integration/betterquesting/BqGuidePageLinks.java +++ b/src/main/java/com/hfstudio/guidenh/integration/betterquesting/BqGuidePageLinks.java @@ -5,6 +5,7 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.function.BiFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -41,7 +42,7 @@ public static String replaceGuideTags(String text) { clearCachesIfStale(); if (text.length() > MAX_CACHEABLE_TEXT_LENGTH) { - return replaceGuideTagsUncached(text); + return replaceGuideTagsUncached(text, BqGuidePageLinks::createUriLink); } String cached = TEXT_CACHE.get(text); @@ -49,10 +50,21 @@ public static String replaceGuideTags(String text) { return cached; } - String converted = replaceGuideTagsUncached(text); + String converted = replaceGuideTagsUncached(text, BqGuidePageLinks::createUriLink); return putBounded(TEXT_CACHE, text, converted); } + public static String replaceGuideTags(String text, BiFunction interactiveTextFactory) { + if (text == null || !text.contains("[guide")) { + return text; + } + + clearCachesIfStale(); + return replaceGuideTagsUncached( + text, + (anchor, label) -> interactiveTextFactory.apply(anchor.toString(), label)); + } + public static boolean isGuideUri(@Nullable String url) { return url != null && url.startsWith(URI_SCHEME + ":"); } @@ -106,14 +118,19 @@ public static boolean isGuideUri(@Nullable String url) { if (anchor == null) { return null; } + return putBounded(TOOLTIP_CACHE, url, getTooltip(anchor)); + } + + public static List getTooltip(PageAnchor anchor) { GuidePageLinkTarget target = GuidePageLinkTarget.resolve(anchor); List tooltip = List.of( EnumChatFormatting.AQUA + I18n.format("guidenh.compat.bq.open_guide_page"), EnumChatFormatting.GRAY + target.title()); - return putBounded(TOOLTIP_CACHE, url, tooltip); + return tooltip; } - private static String replaceGuideTagsUncached(String text) { + private static String replaceGuideTagsUncached(String text, + BiFunction interactiveTextFactory) { Matcher matcher = GUIDE_TAG.matcher(text); StringBuffer result = new StringBuffer(text.length()); while (matcher.find()) { @@ -133,15 +150,13 @@ private static String replaceGuideTagsUncached(String text) { String label = explicitText != null && !explicitText.isEmpty() ? explicitText : GuidePageLinkTarget.resolve(anchor) .title(); - matcher.appendReplacement( - result, - Matcher.quoteReplacement("[url link=" + toUri(anchor) + "]" + label + "[/url]")); + matcher.appendReplacement(result, Matcher.quoteReplacement(interactiveTextFactory.apply(anchor, label))); } matcher.appendTail(result); return result.toString(); } - private static @Nullable PageAnchor parsePageSpec(String pageSpec) { + public static @Nullable PageAnchor parsePageSpec(String pageSpec) { Optional cached = PAGE_SPEC_CACHE.get(pageSpec); if (cached != null) { return cached.orElse(null); @@ -193,8 +208,8 @@ private static void clearCachesIfStale() { } } - private static String toUri(PageAnchor anchor) { - return URI_SCHEME + ":" + anchor; + private static String createUriLink(PageAnchor anchor, String label) { + return "[url link=" + URI_SCHEME + ":" + anchor + "]" + label + "[/url]"; } private static @Nullable String firstNonBlank(@Nullable String first, @Nullable String second) { diff --git a/src/main/java/com/hfstudio/guidenh/integration/betterquesting/BqGuidePageUriHandler.java b/src/main/java/com/hfstudio/guidenh/integration/betterquesting/BqGuidePageUriHandler.java index 44cc698a..746234c5 100644 --- a/src/main/java/com/hfstudio/guidenh/integration/betterquesting/BqGuidePageUriHandler.java +++ b/src/main/java/com/hfstudio/guidenh/integration/betterquesting/BqGuidePageUriHandler.java @@ -26,10 +26,11 @@ public static synchronized void register() { @Override public boolean test(URI uri) { PageAnchor anchor = BqGuidePageLinks.parseUri(uri); - if (anchor == null) { - return false; - } + return open(anchor); + } + public static boolean open(PageAnchor anchor) { + if (anchor == null) return false; GuidePageLinkTarget target = GuidePageLinkTarget.resolve(anchor); Minecraft mc = Minecraft.getMinecraft(); if (mc == null) { diff --git a/src/main/java/com/hfstudio/guidenh/integration/betterquesting/QuestIndex.java b/src/main/java/com/hfstudio/guidenh/integration/betterquesting/QuestIndex.java index f095084d..ec17b5ff 100644 --- a/src/main/java/com/hfstudio/guidenh/integration/betterquesting/QuestIndex.java +++ b/src/main/java/com/hfstudio/guidenh/integration/betterquesting/QuestIndex.java @@ -64,8 +64,7 @@ public static List> getQuestAnchors(ParsedGuidePage page) } if (!(questIdsNode instanceof ListquestIdList)) { - GuideDebugLog - .warnAlways("[GuideNH] [QuestIndex] Page {} contains malformed quest_ids frontmatter", page.getId()); + GuideDebugLog.warn("[GuideNH] [QuestIndex] Page {} contains malformed quest_ids frontmatter", page.getId()); return List.of(); } @@ -76,14 +75,13 @@ public static List> getQuestAnchors(ParsedGuidePage page) if (listEntry instanceof String questIdStr) { String trimmed = questIdStr.trim(); if (trimmed.isEmpty()) { - GuideDebugLog.warnAlways( - "[GuideNH] [QuestIndex] Page {} contains an empty quest_ids frontmatter entry", - pageId); + GuideDebugLog + .warn("[GuideNH] [QuestIndex] Page {} contains an empty quest_ids frontmatter entry", pageId); continue; } UUID parsed = QuestIdParser.parse(trimmed); if (parsed == null) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [QuestIndex] Page {} contains a malformed quest_ids frontmatter entry: {}", pageId, trimmed); @@ -91,7 +89,7 @@ public static List> getQuestAnchors(ParsedGuidePage page) } anchors.add(Pair.of(parsed, new PageAnchor(pageId, null))); } else { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [QuestIndex] Page {} contains a malformed quest_ids frontmatter entry: {}", pageId, listEntry); diff --git a/src/main/java/com/hfstudio/guidenh/integration/nei/NeiRecipeLookup.java b/src/main/java/com/hfstudio/guidenh/integration/nei/NeiRecipeLookup.java index 7427fc32..756de2a8 100644 --- a/src/main/java/com/hfstudio/guidenh/integration/nei/NeiRecipeLookup.java +++ b/src/main/java/com/hfstudio/guidenh/integration/nei/NeiRecipeLookup.java @@ -27,7 +27,7 @@ public class NeiRecipeLookup { NeiRecipeLookup.class.getClassLoader()); ok = true; } catch (Throwable t) { - GuideDebugLog.warnAlways( + GuideDebugLog.warn( "[GuideNH] [NeiRecipeLookup] NEI API incompatible; recipe rendering falls back to vanilla. Reason: {}", t.toString()); } @@ -98,7 +98,7 @@ public static List findCraftingRecipeRefs(ItemStack target) { } return out; } catch (Throwable t) { - GuideDebugLog.warnAlways("[GuideNH] [NeiRecipeLookup] NEI crafting refs query failed", t); + GuideDebugLog.warn("[GuideNH] [NeiRecipeLookup] NEI crafting refs query failed", t); return List.of(); } } @@ -117,7 +117,7 @@ public static List findUsages(ItemStack target) { try { return processHandlers(NeiDirectCalls.getUsageHandlers(target)); } catch (Throwable t) { - GuideDebugLog.warnAlways("[GuideNH] [NeiRecipeLookup] NEI usage query failed", t); + GuideDebugLog.warn("[GuideNH] [NeiRecipeLookup] NEI usage query failed", t); return List.of(); } } @@ -131,7 +131,7 @@ public static List queryRawCraftingHandlers(ItemStack target) { try { return NeiDirectCalls.getCraftingHandlers(target); } catch (Throwable t) { - GuideDebugLog.warnAlways("[GuideNH] [NeiRecipeLookup] queryRawCraftingHandlers failed", t); + GuideDebugLog.warn("[GuideNH] [NeiRecipeLookup] queryRawCraftingHandlers failed", t); return List.of(); } } @@ -145,7 +145,7 @@ public static List queryRawUsageHandlers(ItemStack target) { try { return NeiDirectCalls.getUsageHandlers(target); } catch (Throwable t) { - GuideDebugLog.warnAlways("[GuideNH] [NeiRecipeLookup] queryRawUsageHandlers failed", t); + GuideDebugLog.warn("[GuideNH] [NeiRecipeLookup] queryRawUsageHandlers failed", t); return List.of(); } } diff --git a/src/main/java/com/hfstudio/guidenh/mixins/Mixins.java b/src/main/java/com/hfstudio/guidenh/mixins/Mixins.java index 605dcf5e..bc724788 100644 --- a/src/main/java/com/hfstudio/guidenh/mixins/Mixins.java +++ b/src/main/java/com/hfstudio/guidenh/mixins/Mixins.java @@ -15,10 +15,14 @@ public enum Mixins implements IMixins { EARLY(Side.CLIENT, "forge.AccessorForgeHooksClient", "forge.AccessorGuiIngameForge", "fml.AccessorFMLClientHandler", "minecraft.AccessorAbstractResourcePack", "minecraft.AccessorFallbackResourceManager", "minecraft.AccessorSimpleReloadableResourceManager", "forge.AccessorShapedOreRecipe", - "forge.AccessorShapelessOreRecipe"), + "forge.AccessorShapelessOreRecipe", "minecraft.MixinTessellatorSceneExportCapture", + "minecraft.MixinModelRendererSceneExportCapture"), BQ_COMPAT(Side.CLIENT, Phase.LATE, Mods.BetterQuesting, "compat.MixinPanelButtonQuest", "compat.MixinPanelTextBox", - "compat.AccessorPanelTextBox", "compat.AccessorPanelTextBoxHotZone"), + "compat.MixinGuiTextEditor", "compat.AccessorPanelTextBox", "compat.AccessorPanelTextBoxHotZone"), + + AE2_EXTERNAL_CABLE_PARTS(Side.CLIENT, Phase.LATE, Mods.AE2, "compat.ae2.MixinPartQuartzFiber", + "compat.ae2.MixinPartP2PTunnelME", "compat.ae2.MixinPartToggleBus"), GREGTECH_HATCH_BUILDER(Side.CLIENT, Phase.LATE, Mods.GregTech, "compat.gregtech.AccessorHatchElementBuilder"), diff --git a/src/main/java/com/hfstudio/guidenh/mixins/late/compat/MixinGuiTextEditor.java b/src/main/java/com/hfstudio/guidenh/mixins/late/compat/MixinGuiTextEditor.java new file mode 100644 index 00000000..cb2c7be5 --- /dev/null +++ b/src/main/java/com/hfstudio/guidenh/mixins/late/compat/MixinGuiTextEditor.java @@ -0,0 +1,27 @@ +package com.hfstudio.guidenh.mixins.late.compat; + +import net.minecraft.client.resources.I18n; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import betterquesting.api2.client.gui.controls.PanelButtonStorage; +import betterquesting.api2.client.gui.misc.GuiAlign; +import betterquesting.api2.client.gui.misc.GuiTransform; +import betterquesting.client.gui2.editors.GuiTextEditor; + +@Mixin(value = GuiTextEditor.class, remap = false) +public abstract class MixinGuiTextEditor { + + @Inject(method = "initPanel", at = @At("TAIL")) + private void guidenh$addGuideLinkMacro(CallbackInfo ci) { + ((GuiTextEditor) (Object) this).addPanel( + new PanelButtonStorage<>( + new GuiTransform(GuiAlign.TOP_LEFT, 16, 16, 100, 16, 0), + 2, + I18n.format("guidenh.compat.bq.insert_guide_link"), + "[guide] [/guide]")); + } +} diff --git a/src/main/java/com/hfstudio/guidenh/mixins/late/compat/ae2/MixinPartP2PTunnelME.java b/src/main/java/com/hfstudio/guidenh/mixins/late/compat/ae2/MixinPartP2PTunnelME.java new file mode 100644 index 00000000..77b89d7f --- /dev/null +++ b/src/main/java/com/hfstudio/guidenh/mixins/late/compat/ae2/MixinPartP2PTunnelME.java @@ -0,0 +1,23 @@ +package com.hfstudio.guidenh.mixins.late.compat.ae2; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import com.hfstudio.guidenh.integration.ae2.Ae2ExternalGridPart; + +import appeng.me.helpers.AENetworkProxy; +import appeng.parts.p2p.PartP2PTunnelME; + +@Mixin(value = PartP2PTunnelME.class, remap = false) +public abstract class MixinPartP2PTunnelME implements Ae2ExternalGridPart { + + @Shadow + @Final + private AENetworkProxy outerProxy; + + @Override + public AENetworkProxy guideNh$getExternalConnectionProxy() { + return outerProxy; + } +} diff --git a/src/main/java/com/hfstudio/guidenh/mixins/late/compat/ae2/MixinPartQuartzFiber.java b/src/main/java/com/hfstudio/guidenh/mixins/late/compat/ae2/MixinPartQuartzFiber.java new file mode 100644 index 00000000..e54c0ce6 --- /dev/null +++ b/src/main/java/com/hfstudio/guidenh/mixins/late/compat/ae2/MixinPartQuartzFiber.java @@ -0,0 +1,23 @@ +package com.hfstudio.guidenh.mixins.late.compat.ae2; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import com.hfstudio.guidenh.integration.ae2.Ae2ExternalGridPart; + +import appeng.me.helpers.AENetworkProxy; +import appeng.parts.networking.PartQuartzFiber; + +@Mixin(value = PartQuartzFiber.class, remap = false) +public abstract class MixinPartQuartzFiber implements Ae2ExternalGridPart { + + @Shadow + @Final + private AENetworkProxy outerProxy; + + @Override + public AENetworkProxy guideNh$getExternalConnectionProxy() { + return outerProxy; + } +} diff --git a/src/main/java/com/hfstudio/guidenh/mixins/late/compat/ae2/MixinPartToggleBus.java b/src/main/java/com/hfstudio/guidenh/mixins/late/compat/ae2/MixinPartToggleBus.java new file mode 100644 index 00000000..b517be7b --- /dev/null +++ b/src/main/java/com/hfstudio/guidenh/mixins/late/compat/ae2/MixinPartToggleBus.java @@ -0,0 +1,23 @@ +package com.hfstudio.guidenh.mixins.late.compat.ae2; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import com.hfstudio.guidenh.integration.ae2.Ae2ExternalGridPart; + +import appeng.me.helpers.AENetworkProxy; +import appeng.parts.misc.PartToggleBus; + +@Mixin(value = PartToggleBus.class, remap = false) +public abstract class MixinPartToggleBus implements Ae2ExternalGridPart { + + @Shadow + @Final + private AENetworkProxy outerProxy; + + @Override + public AENetworkProxy guideNh$getExternalConnectionProxy() { + return outerProxy; + } +} diff --git a/src/main/java/com/hfstudio/guidenh/network/GuideNhClientBridgeHandler.java b/src/main/java/com/hfstudio/guidenh/network/GuideNhClientBridgeHandler.java index 286713d1..438b6574 100644 --- a/src/main/java/com/hfstudio/guidenh/network/GuideNhClientBridgeHandler.java +++ b/src/main/java/com/hfstudio/guidenh/network/GuideNhClientBridgeHandler.java @@ -1,7 +1,6 @@ package com.hfstudio.guidenh.network; -import net.minecraft.client.Minecraft; - +import com.hfstudio.guidenh.client.GuideNhClientTaskScheduler; import com.hfstudio.guidenh.client.command.GuideNhClientBridgeController; import cpw.mods.fml.common.network.simpleimpl.IMessage; @@ -17,8 +16,7 @@ public class GuideNhClientBridgeHandler implements IMessageHandler Minecraft.getMinecraft() - .func_152344_a(task), + GuideNhClientTaskScheduler::execute, GuideNhClientBridgeController.getInstance()::beginImportStructure); return null; } diff --git a/src/main/java/com/hfstudio/guidenh/network/GuideNhRegionExportClientHandler.java b/src/main/java/com/hfstudio/guidenh/network/GuideNhRegionExportClientHandler.java index 61ddfac7..cde56542 100644 --- a/src/main/java/com/hfstudio/guidenh/network/GuideNhRegionExportClientHandler.java +++ b/src/main/java/com/hfstudio/guidenh/network/GuideNhRegionExportClientHandler.java @@ -1,7 +1,6 @@ package com.hfstudio.guidenh.network; -import net.minecraft.client.Minecraft; - +import com.hfstudio.guidenh.client.GuideNhClientTaskScheduler; import com.hfstudio.guidenh.client.command.GuideNhClientBridgeController; import cpw.mods.fml.common.network.simpleimpl.IMessage; @@ -15,10 +14,9 @@ public class GuideNhRegionExportClientHandler implements IMessageHandler GuideNhClientBridgeController.getInstance() - .handleRegionExportReply(message)); + GuideNhClientTaskScheduler.execute( + () -> GuideNhClientBridgeController.getInstance() + .handleRegionExportReply(message)); return null; } } diff --git a/src/main/resources/assets/guidenh/lang/en_US.lang b/src/main/resources/assets/guidenh/lang/en_US.lang index 64b5c4b7..099f2407 100644 --- a/src/main/resources/assets/guidenh/lang/en_US.lang +++ b/src/main/resources/assets/guidenh/lang/en_US.lang @@ -509,6 +509,8 @@ guideme.guidebook.Search=Search guideme.guidebook.HomePage=Home Page guideme.guidebook.HomePageSpecialPages=Special Pages guideme.guidebook.NavBarSpecialPages=Special Pages +guideme.guidebook.NavBarExpandAll=Expand All +guideme.guidebook.NavBarCollapseAll=Collapse All guideme.guidebook.SpecialPageShowMore=Show More guideme.guidebook.SiteExportNoPages=No guide pages were exported. guideme.guidebook.SiteExportOpenGuide=Open exported guide @@ -632,6 +634,7 @@ guidenh.compat.bq.locked=[Locked Quest] guidenh.compat.bq.missing=[Unknown Quest] guidenh.compat.bq.open_in_guide=Open in Guide guidenh.compat.bq.open_guide_page=Open GuideNH page +guidenh.compat.bq.insert_guide_link=GuideNH Link guidenh.mediawiki.contributor.abkqpo.name=ABKQPO guidenh.mediawiki.contributor.abkqpo.role=Owner diff --git a/src/main/resources/assets/guidenh/lang/zh_CN.lang b/src/main/resources/assets/guidenh/lang/zh_CN.lang index 14fa3a05..a283d643 100644 --- a/src/main/resources/assets/guidenh/lang/zh_CN.lang +++ b/src/main/resources/assets/guidenh/lang/zh_CN.lang @@ -509,6 +509,8 @@ guideme.guidebook.Search=搜索 guideme.guidebook.HomePage=首页 guideme.guidebook.HomePageSpecialPages=特殊页面 guideme.guidebook.NavBarSpecialPages=特殊页面 +guideme.guidebook.NavBarExpandAll=全部展开 +guideme.guidebook.NavBarCollapseAll=全部收起 guideme.guidebook.SpecialPageShowMore=显示更多 guideme.guidebook.SiteExportNoPages=没有导出任何指南页面。 guideme.guidebook.SiteExportOpenGuide=打开导出的指南 @@ -629,6 +631,7 @@ guidenh.compat.bq.locked=[未解锁任务] guidenh.compat.bq.missing=[未知任务] guidenh.compat.bq.open_in_guide=在指南中打开 guidenh.compat.bq.open_guide_page=打开 GuideNH 页面 +guidenh.compat.bq.insert_guide_link=GuideNH 链接 guidenh.mediawiki.contributor.abkqpo.name=ABKQPO guidenh.mediawiki.contributor.abkqpo.role=拥有者 diff --git a/src/main/resources/assets/guidenh/siteexport/app.css b/src/main/resources/assets/guidenh/siteexport/app.css index 0a902285..8a3ef37a 100644 --- a/src/main/resources/assets/guidenh/siteexport/app.css +++ b/src/main/resources/assets/guidenh/siteexport/app.css @@ -73,6 +73,12 @@ html, body { min-height: 100vh; } +.guide-site-body { + width: 100vw; + height: 100vh; + overflow: hidden; +} + h1, h2, h3, h4, h5, h6 { font-size: 1rem; margin: 1em 0 0.5em; @@ -458,8 +464,9 @@ p { .guide-site { display: grid; grid-template-columns: var(--sidebar-width) minmax(0, 1fr); - grid-template-rows: var(--header-height) 1fr; + grid-template-rows: var(--header-height) minmax(0, 1fr); height: 100vh; + min-height: 0; overflow: hidden; } @@ -500,6 +507,7 @@ p { border-right: calc(1px * var(--gui-scale)) solid rgba(255,255,255,0.07); display: flex; flex-direction: column; + min-height: 0; overflow: hidden; } @@ -683,6 +691,8 @@ p { grid-column: 2; grid-row: 2; overflow-y: auto; + min-width: 0; + min-height: 0; padding: calc(6px * var(--gui-scale)); } @@ -863,6 +873,8 @@ p { width: 100%; height: 100%; image-rendering: auto; + user-select: none; + -webkit-user-drag: none; } .guide-latex-valign-top { @@ -1776,6 +1788,7 @@ p { padding: 0.45rem 0.7rem 0.7rem 0.7rem; border-left: calc(2px * var(--gui-scale)) solid var(--guide-content-tabs-accent); background: rgba(64, 64, 64, 0.25); + position: relative; } .guide-content-tabs-title { @@ -1810,8 +1823,13 @@ p { .guide-content-tabs-input { position: absolute; + width: 1px; + height: 1px; + margin: -1px; opacity: 0; + overflow: hidden; pointer-events: none; + clip: rect(0 0 0 0); } .guide-content-tabs-label { diff --git a/src/main/resources/assets/guidenh/siteexport/layout.html b/src/main/resources/assets/guidenh/siteexport/layout.html index b1bad76e..fb4dbdea 100644 --- a/src/main/resources/assets/guidenh/siteexport/layout.html +++ b/src/main/resources/assets/guidenh/siteexport/layout.html @@ -8,7 +8,7 @@ - +
diff --git a/src/main/resources/assets/guidenh/siteexport/model-viewer/modelViewer.js b/src/main/resources/assets/guidenh/siteexport/model-viewer/modelViewer.js index d373f712..0de9fa59 100644 --- a/src/main/resources/assets/guidenh/siteexport/model-viewer/modelViewer.js +++ b/src/main/resources/assets/guidenh/siteexport/model-viewer/modelViewer.js @@ -596,11 +596,6 @@ function buildStateKey(state) { let key = `layer=${Math.max(0, Number(state.visibleLayer) || 0)}|ponder=${Math.max(0, Number(state.ponderTick) || 0)}`; const structures = normalizeStateStructures(state); if (Object.keys(structures).length === 0) { - key += `|tier=${Math.max(1, Number(state.tier) || 1)}`; - const channels = state.channels || {}; - for (const channelId of Object.keys(channels)) { - key += `|channel:${channelId}=${Math.max(0, Number(channels[channelId]) || 0)}`; - } return key; } for (const structureId of Object.keys(structures)) { diff --git a/src/main/resources/assets/guidenh/siteexport/model-viewer/vendor/modelViewer-A42QTX7N.js b/src/main/resources/assets/guidenh/siteexport/model-viewer/vendor/modelViewer-A42QTX7N.js index f230ea9f..1dcde11c 100644 --- a/src/main/resources/assets/guidenh/siteexport/model-viewer/vendor/modelViewer-A42QTX7N.js +++ b/src/main/resources/assets/guidenh/siteexport/model-viewer/vendor/modelViewer-A42QTX7N.js @@ -3409,7 +3409,7 @@ void main() { squared_mean = squared_mean / samples; float std_dev = sqrt( squared_mean - mean * mean ); gl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) ); -}`;function Wp(s,e,t){let n=new ji,i=new ve,r=new ve,o=new rt,a=new ma({depthPacking:qc}),c=new ga,l={},h=t.maxTextureSize,f={[_n]:St,[St]:_n,[nn]:nn},d=new hn({defines:{VSM_SAMPLES:8},uniforms:{shadow_pass:{value:null},resolution:{value:new ve},radius:{value:4}},vertexShader:Hp,fragmentShader:Gp}),p=d.clone();p.defines.HORIZONTAL_PASS=1;let g=new Mt;g.setAttribute("position",new dt(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));let _=new ft(g,d),m=this;this.enabled=!1,this.autoUpdate=!0,this.needsUpdate=!1,this.type=Al,this.render=function(M,S,P){if(m.enabled===!1||m.autoUpdate===!1&&m.needsUpdate===!1||M.length===0)return;let R=s.getRenderTarget(),D=s.getActiveCubeFace(),x=s.getActiveMipmapLevel(),T=s.state;T.setBlending(qt),T.buffers.color.setClear(1,1,1,1),T.buffers.depth.setTest(!0),T.setScissorTest(!1);for(let W=0,J=M.length;Wh||i.y>h)&&(i.x>h&&(r.x=Math.floor(h/k.x),i.x=r.x*k.x,O.mapSize.x=r.x),i.y>h&&(r.y=Math.floor(h/k.y),i.y=r.y*k.y,O.mapSize.y=r.y)),O.map===null){let $=this.type!==Vi?{minFilter:st,magFilter:st}:{};O.map=new on(i.x,i.y,$),O.map.texture.name=U.name+".shadowMap",O.camera.updateProjectionMatrix()}s.setRenderTarget(O.map),s.clear();let te=O.getViewportCount();for(let $=0;$0||S.map&&S.alphaTest>0){let T=D.uuid,W=S.uuid,J=l[T];J===void 0&&(J={},l[T]=J);let U=J[W];U===void 0&&(U=D.clone(),J[W]=U),D=U}if(D.visible=S.visible,D.wireframe=S.wireframe,R===Vi?D.side=S.shadowSide!==null?S.shadowSide:S.side:D.side=S.shadowSide!==null?S.shadowSide:f[S.side],D.alphaMap=S.alphaMap,D.alphaTest=S.alphaTest,D.map=S.map,D.clipShadows=S.clipShadows,D.clippingPlanes=S.clippingPlanes,D.clipIntersection=S.clipIntersection,D.displacementMap=S.displacementMap,D.displacementScale=S.displacementScale,D.displacementBias=S.displacementBias,D.wireframeLinewidth=S.wireframeLinewidth,D.linewidth=S.linewidth,P.isPointLight===!0&&D.isMeshDistanceMaterial===!0){let T=s.properties.get(D);T.light=P}return D}function v(M,S,P,R,D){if(M.visible===!1)return;if(M.layers.test(S.layers)&&(M.isMesh||M.isLine||M.isPoints)&&(M.castShadow||M.receiveShadow&&D===Vi)&&(!M.frustumCulled||n.intersectsObject(M))){M.modelViewMatrix.multiplyMatrices(P.matrixWorldInverse,M.matrixWorld);let W=e.update(M),J=M.material;if(Array.isArray(J)){let U=W.groups;for(let O=0,k=U.length;O=1):Y.indexOf("OpenGL ES")!==-1&&($=parseFloat(/^OpenGL ES (\d)/.exec(Y)[1]),te=$>=2);let j=null,ne={},_e=s.getParameter(3088),ce=s.getParameter(2978),V=new rt().fromArray(_e),Z=new rt().fromArray(ce);function re(I,G,Q){let ue=new Uint8Array(4),ge=s.createTexture();s.bindTexture(I,ge),s.texParameteri(I,10241,9728),s.texParameteri(I,10240,9728);for(let Ve=0;Ve"u"?!1:/OculusBrowser/g.test(navigator.userAgent),g=new WeakMap,_,m=new WeakMap,u=!1;try{u=typeof OffscreenCanvas<"u"&&new OffscreenCanvas(1,1).getContext("2d")!==null}catch{}function w(E,y){return u?new OffscreenCanvas(E,y):Zs("canvas")}function v(E,y,z,K){let ee=1;if((E.width>K||E.height>K)&&(ee=K/Math.max(E.width,E.height)),ee<1||y===!0)if(typeof HTMLImageElement<"u"&&E instanceof HTMLImageElement||typeof HTMLCanvasElement<"u"&&E instanceof HTMLCanvasElement||typeof ImageBitmap<"u"&&E instanceof ImageBitmap){let ae=y?Dl:Math.floor,A=ae(ee*E.width),q=ae(ee*E.height);_===void 0&&(_=w(A,q));let F=z?w(A,q):_;return F.width=A,F.height=q,F.getContext("2d").drawImage(E,0,0,A,q),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+E.width+"x"+E.height+") to ("+A+"x"+q+")."),F}else return"data"in E&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+E.width+"x"+E.height+")."),E;return E}function M(E){return sa(E.width)&&sa(E.height)}function S(E){return a?!1:E.wrapS!==ut||E.wrapT!==ut||E.minFilter!==st&&E.minFilter!==yt}function P(E,y){return E.generateMipmaps&&y&&E.minFilter!==st&&E.minFilter!==yt}function R(E){s.generateMipmap(E)}function D(E,y,z,K,ee=!1){if(a===!1)return y;if(E!==null){if(s[E]!==void 0)return s[E];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+E+"'")}let ae=y;return y===6403&&(z===5126&&(ae=33326),z===5131&&(ae=33325),z===5121&&(ae=33321)),y===33319&&(z===5126&&(ae=33328),z===5131&&(ae=33327),z===5121&&(ae=33323)),y===6408&&(z===5126&&(ae=34836),z===5131&&(ae=34842),z===5121&&(ae=K===He&&ee===!1?35907:32856),z===32819&&(ae=32854),z===32820&&(ae=32855)),(ae===33325||ae===33326||ae===33327||ae===33328||ae===34842||ae===34836)&&e.get("EXT_color_buffer_float"),ae}function x(E,y,z){return P(E,z)===!0||E.isFramebufferTexture&&E.minFilter!==st&&E.minFilter!==yt?Math.log2(Math.max(y.width,y.height))+1:E.mipmaps!==void 0&&E.mipmaps.length>0?E.mipmaps.length:E.isCompressedTexture&&Array.isArray(E.image)?y.mipmaps.length:1}function T(E){return E===st||E===ro||E===br?9728:9729}function W(E){let y=E.target;y.removeEventListener("dispose",W),U(y),y.isVideoTexture&&g.delete(y)}function J(E){let y=E.target;y.removeEventListener("dispose",J),k(y)}function U(E){let y=n.get(E);if(y.__webglInit===void 0)return;let z=E.source,K=m.get(z);if(K){let ee=K[y.__cacheKey];ee.usedTimes--,ee.usedTimes===0&&O(E),Object.keys(K).length===0&&m.delete(z)}n.remove(E)}function O(E){let y=n.get(E);s.deleteTexture(y.__webglTexture);let z=E.source,K=m.get(z);delete K[y.__cacheKey],o.memory.textures--}function k(E){let y=E.texture,z=n.get(E),K=n.get(y);if(K.__webglTexture!==void 0&&(s.deleteTexture(K.__webglTexture),o.memory.textures--),E.depthTexture&&E.depthTexture.dispose(),E.isWebGLCubeRenderTarget)for(let ee=0;ee<6;ee++)s.deleteFramebuffer(z.__webglFramebuffer[ee]),z.__webglDepthbuffer&&s.deleteRenderbuffer(z.__webglDepthbuffer[ee]);else{if(s.deleteFramebuffer(z.__webglFramebuffer),z.__webglDepthbuffer&&s.deleteRenderbuffer(z.__webglDepthbuffer),z.__webglMultisampledFramebuffer&&s.deleteFramebuffer(z.__webglMultisampledFramebuffer),z.__webglColorRenderbuffer)for(let ee=0;ee=c&&console.warn("THREE.WebGLTextures: Trying to use "+E+" texture units while this GPU supports only "+c),te+=1,E}function j(E){let y=[];return y.push(E.wrapS),y.push(E.wrapT),y.push(E.wrapR||0),y.push(E.magFilter),y.push(E.minFilter),y.push(E.anisotropy),y.push(E.internalFormat),y.push(E.format),y.push(E.type),y.push(E.generateMipmaps),y.push(E.premultiplyAlpha),y.push(E.flipY),y.push(E.unpackAlignment),y.push(E.encoding),y.join()}function ne(E,y){let z=n.get(E);if(E.isVideoTexture&&ke(E),E.isRenderTargetTexture===!1&&E.version>0&&z.__version!==E.version){let K=E.image;if(K===null)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else if(K.complete===!1)console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete");else{Se(z,E,y);return}}t.bindTexture(3553,z.__webglTexture,33984+y)}function _e(E,y){let z=n.get(E);if(E.version>0&&z.__version!==E.version){Se(z,E,y);return}t.bindTexture(35866,z.__webglTexture,33984+y)}function ce(E,y){let z=n.get(E);if(E.version>0&&z.__version!==E.version){Se(z,E,y);return}t.bindTexture(32879,z.__webglTexture,33984+y)}function V(E,y){let z=n.get(E);if(E.version>0&&z.__version!==E.version){be(z,E,y);return}t.bindTexture(34067,z.__webglTexture,33984+y)}let Z={[yi]:10497,[ut]:33071,[ta]:33648},re={[st]:9728,[ro]:9984,[br]:9986,[yt]:9729,[Ic]:9985,[qi]:9987};function le(E,y,z){if(z?(s.texParameteri(E,10242,Z[y.wrapS]),s.texParameteri(E,10243,Z[y.wrapT]),(E===32879||E===35866)&&s.texParameteri(E,32882,Z[y.wrapR]),s.texParameteri(E,10240,re[y.magFilter]),s.texParameteri(E,10241,re[y.minFilter])):(s.texParameteri(E,10242,33071),s.texParameteri(E,10243,33071),(E===32879||E===35866)&&s.texParameteri(E,32882,33071),(y.wrapS!==ut||y.wrapT!==ut)&&console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping."),s.texParameteri(E,10240,T(y.magFilter)),s.texParameteri(E,10241,T(y.minFilter)),y.minFilter!==st&&y.minFilter!==yt&&console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.")),e.has("EXT_texture_filter_anisotropic")===!0){let K=e.get("EXT_texture_filter_anisotropic");if(y.magFilter===st||y.minFilter!==br&&y.minFilter!==qi||y.type===In&&e.has("OES_texture_float_linear")===!1||a===!1&&y.type===Yi&&e.has("OES_texture_half_float_linear")===!1)return;(y.anisotropy>1||n.get(y).__currentAnisotropy)&&(s.texParameterf(E,K.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(y.anisotropy,i.getMaxAnisotropy())),n.get(y).__currentAnisotropy=y.anisotropy)}}function B(E,y){let z=!1;E.__webglInit===void 0&&(E.__webglInit=!0,y.addEventListener("dispose",W));let K=y.source,ee=m.get(K);ee===void 0&&(ee={},m.set(K,ee));let ae=j(y);if(ae!==E.__cacheKey){ee[ae]===void 0&&(ee[ae]={texture:s.createTexture(),usedTimes:0},o.memory.textures++,z=!0),ee[ae].usedTimes++;let A=ee[E.__cacheKey];A!==void 0&&(ee[E.__cacheKey].usedTimes--,A.usedTimes===0&&O(y)),E.__cacheKey=ae,E.__webglTexture=ee[ae].texture}return z}function Se(E,y,z){let K=3553;(y.isDataArrayTexture||y.isCompressedArrayTexture)&&(K=35866),y.isData3DTexture&&(K=32879);let ee=B(E,y),ae=y.source;t.bindTexture(K,E.__webglTexture,33984+z);let A=n.get(ae);if(ae.version!==A.__version||ee===!0){t.activeTexture(33984+z),s.pixelStorei(37440,y.flipY),s.pixelStorei(37441,y.premultiplyAlpha),s.pixelStorei(3317,y.unpackAlignment),s.pixelStorei(37443,0);let q=S(y)&&M(y.image)===!1,F=v(y.image,q,!1,h);F=at(y,F);let oe=M(F)||a,fe=r.convert(y.format,y.encoding),pe=r.convert(y.type),he=D(y.internalFormat,fe,pe,y.encoding,y.isVideoTexture);le(K,y,oe);let de,we=y.mipmaps,Ie=a&&y.isVideoTexture!==!0,Ze=A.__version===void 0||ee===!0,I=x(y,F,oe);if(y.isDepthTexture)he=6402,a?y.type===In?he=36012:y.type===Cn?he=33190:y.type===pi?he=35056:he=33189:y.type===In&&console.error("WebGLRenderer: Floating point depth texture requires WebGL2."),y.format===Rn&&he===6402&&y.type!==Pl&&y.type!==Cn&&(console.warn("THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture."),y.type=Cn,pe=r.convert(y.type)),y.format===vi&&he===6402&&(he=34041,y.type!==pi&&(console.warn("THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture."),y.type=pi,pe=r.convert(y.type))),Ze&&(Ie?t.texStorage2D(3553,1,he,F.width,F.height):t.texImage2D(3553,0,he,F.width,F.height,0,fe,pe,null));else if(y.isDataTexture)if(we.length>0&&oe){Ie&&Ze&&t.texStorage2D(3553,I,he,we[0].width,we[0].height);for(let G=0,Q=we.length;G>=1,Q>>=1}}else if(we.length>0&&oe){Ie&&Ze&&t.texStorage2D(3553,I,he,we[0].width,we[0].height);for(let G=0,Q=we.length;G0&&Ze++,t.texStorage2D(34067,Ze,de,F[0].width,F[0].height));for(let G=0;G<6;G++)if(q){we?t.texSubImage2D(34069+G,0,0,0,F[G].width,F[G].height,pe,he,F[G].data):t.texImage2D(34069+G,0,de,F[G].width,F[G].height,0,pe,he,F[G].data);for(let Q=0;Q=34069&&ee<=34074)&&s.framebufferTexture2D(36160,K,ee,n.get(z).__webglTexture,0),t.bindFramebuffer(36160,null)}function ye(E,y,z){if(s.bindRenderbuffer(36161,E),y.depthBuffer&&!y.stencilBuffer){let K=33189;if(z||Oe(y)){let ee=y.depthTexture;ee&&ee.isDepthTexture&&(ee.type===In?K=36012:ee.type===Cn&&(K=33190));let ae=Ye(y);Oe(y)?d.renderbufferStorageMultisampleEXT(36161,ae,K,y.width,y.height):s.renderbufferStorageMultisample(36161,ae,K,y.width,y.height)}else s.renderbufferStorage(36161,K,y.width,y.height);s.framebufferRenderbuffer(36160,36096,36161,E)}else if(y.depthBuffer&&y.stencilBuffer){let K=Ye(y);z&&Oe(y)===!1?s.renderbufferStorageMultisample(36161,K,35056,y.width,y.height):Oe(y)?d.renderbufferStorageMultisampleEXT(36161,K,35056,y.width,y.height):s.renderbufferStorage(36161,34041,y.width,y.height),s.framebufferRenderbuffer(36160,33306,36161,E)}else{let K=y.isWebGLMultipleRenderTargets===!0?y.texture:[y.texture];for(let ee=0;ee0&&Oe(E)===!1){let q=ae?y:[y];z.__webglMultisampledFramebuffer=s.createFramebuffer(),z.__webglColorRenderbuffer=[],t.bindFramebuffer(36160,z.__webglMultisampledFramebuffer);for(let F=0;F0&&Oe(E)===!1){let y=E.isWebGLMultipleRenderTargets?E.texture:[E.texture],z=E.width,K=E.height,ee=16384,ae=[],A=E.stencilBuffer?33306:36096,q=n.get(E),F=E.isWebGLMultipleRenderTargets===!0;if(F)for(let oe=0;oe0&&e.has("WEBGL_multisampled_render_to_texture")===!0&&y.__useRenderToTexture!==!1}function ke(E){let y=o.render.frame;g.get(E)!==y&&(g.set(E,y),E.update())}function at(E,y){let z=E.encoding,K=E.format,ee=E.type;return E.isCompressedTexture===!0||E.isVideoTexture===!0||E.format===ia||z!==Dn&&(z===He?a===!1?e.has("EXT_sRGB")===!0&&K===Bt?(E.format=ia,E.minFilter=yt,E.generateMipmaps=!1):y=Js.sRGBToLinear(y):(K!==Bt||ee!==Ln)&&console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture encoding:",z)),y}this.allocateTextureUnit=Y,this.resetTextureUnits=$,this.setTexture2D=ne,this.setTexture2DArray=_e,this.setTexture3D=ce,this.setTextureCube=V,this.rebindTextures=Te,this.setupRenderTarget=qe,this.updateRenderTargetMipmap=We,this.updateMultisampleRenderTarget=$e,this.setupDepthRenderbuffer=me,this.setupFrameBufferTexture=ie,this.useMultisampledRTT=Oe}function Yp(s,e,t){let n=t.isWebGL2;function i(r,o=null){let a;if(r===Ln)return 5121;if(r===Dc)return 32819;if(r===Uc)return 32820;if(r===Pc)return 5120;if(r===Rc)return 5122;if(r===Pl)return 5123;if(r===Lc)return 5124;if(r===Cn)return 5125;if(r===In)return 5126;if(r===Yi)return n?5131:(a=e.get("OES_texture_half_float"),a!==null?a.HALF_FLOAT_OES:null);if(r===Nc)return 6406;if(r===Bt)return 6408;if(r===Oc)return 6409;if(r===Fc)return 6410;if(r===Rn)return 6402;if(r===vi)return 34041;if(r===ia)return a=e.get("EXT_sRGB"),a!==null?a.SRGB_ALPHA_EXT:null;if(r===Bc)return 6403;if(r===zc)return 36244;if(r===kc)return 33319;if(r===Vc)return 33320;if(r===Hc)return 36249;if(r===Mr||r===Sr||r===wr||r===Er)if(o===He)if(a=e.get("WEBGL_compressed_texture_s3tc_srgb"),a!==null){if(r===Mr)return a.COMPRESSED_SRGB_S3TC_DXT1_EXT;if(r===Sr)return a.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;if(r===wr)return a.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;if(r===Er)return a.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}else return null;else if(a=e.get("WEBGL_compressed_texture_s3tc"),a!==null){if(r===Mr)return a.COMPRESSED_RGB_S3TC_DXT1_EXT;if(r===Sr)return a.COMPRESSED_RGBA_S3TC_DXT1_EXT;if(r===wr)return a.COMPRESSED_RGBA_S3TC_DXT3_EXT;if(r===Er)return a.COMPRESSED_RGBA_S3TC_DXT5_EXT}else return null;if(r===ao||r===oo||r===lo||r===co)if(a=e.get("WEBGL_compressed_texture_pvrtc"),a!==null){if(r===ao)return a.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;if(r===oo)return a.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;if(r===lo)return a.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;if(r===co)return a.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG}else return null;if(r===Gc)return a=e.get("WEBGL_compressed_texture_etc1"),a!==null?a.COMPRESSED_RGB_ETC1_WEBGL:null;if(r===ho||r===uo)if(a=e.get("WEBGL_compressed_texture_etc"),a!==null){if(r===ho)return o===He?a.COMPRESSED_SRGB8_ETC2:a.COMPRESSED_RGB8_ETC2;if(r===uo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:a.COMPRESSED_RGBA8_ETC2_EAC}else return null;if(r===fo||r===po||r===mo||r===go||r===_o||r===xo||r===yo||r===vo||r===bo||r===Mo||r===So||r===wo||r===Eo||r===To)if(a=e.get("WEBGL_compressed_texture_astc"),a!==null){if(r===fo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:a.COMPRESSED_RGBA_ASTC_4x4_KHR;if(r===po)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:a.COMPRESSED_RGBA_ASTC_5x4_KHR;if(r===mo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:a.COMPRESSED_RGBA_ASTC_5x5_KHR;if(r===go)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:a.COMPRESSED_RGBA_ASTC_6x5_KHR;if(r===_o)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:a.COMPRESSED_RGBA_ASTC_6x6_KHR;if(r===xo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:a.COMPRESSED_RGBA_ASTC_8x5_KHR;if(r===yo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:a.COMPRESSED_RGBA_ASTC_8x6_KHR;if(r===vo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:a.COMPRESSED_RGBA_ASTC_8x8_KHR;if(r===bo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:a.COMPRESSED_RGBA_ASTC_10x5_KHR;if(r===Mo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:a.COMPRESSED_RGBA_ASTC_10x6_KHR;if(r===So)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:a.COMPRESSED_RGBA_ASTC_10x8_KHR;if(r===wo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:a.COMPRESSED_RGBA_ASTC_10x10_KHR;if(r===Eo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:a.COMPRESSED_RGBA_ASTC_12x10_KHR;if(r===To)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:a.COMPRESSED_RGBA_ASTC_12x12_KHR}else return null;if(r===Tr)if(a=e.get("EXT_texture_compression_bptc"),a!==null){if(r===Tr)return o===He?a.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT:a.COMPRESSED_RGBA_BPTC_UNORM_EXT}else return null;if(r===Wc||r===Ao||r===Co||r===Io)if(a=e.get("EXT_texture_compression_rgtc"),a!==null){if(r===Tr)return a.COMPRESSED_RED_RGTC1_EXT;if(r===Ao)return a.COMPRESSED_SIGNED_RED_RGTC1_EXT;if(r===Co)return a.COMPRESSED_RED_GREEN_RGTC2_EXT;if(r===Io)return a.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT}else return null;return r===pi?n?34042:(a=e.get("WEBGL_depth_texture"),a!==null?a.UNSIGNED_INT_24_8_WEBGL:null):s[r]!==void 0?s[r]:null}return{convert:i}}var _a=class extends Ct{constructor(e=[]){super(),this.isArrayCamera=!0,this.cameras=e}},Dt=class extends pt{constructor(){super(),this.isGroup=!0,this.type="Group"}},Zp={type:"move"},Xi=class{constructor(){this._targetRay=null,this._grip=null,this._hand=null}getHandSpace(){return this._hand===null&&(this._hand=new Dt,this._hand.matrixAutoUpdate=!1,this._hand.visible=!1,this._hand.joints={},this._hand.inputState={pinching:!1}),this._hand}getTargetRaySpace(){return this._targetRay===null&&(this._targetRay=new Dt,this._targetRay.matrixAutoUpdate=!1,this._targetRay.visible=!1,this._targetRay.hasLinearVelocity=!1,this._targetRay.linearVelocity=new C,this._targetRay.hasAngularVelocity=!1,this._targetRay.angularVelocity=new C),this._targetRay}getGripSpace(){return this._grip===null&&(this._grip=new Dt,this._grip.matrixAutoUpdate=!1,this._grip.visible=!1,this._grip.hasLinearVelocity=!1,this._grip.linearVelocity=new C,this._grip.hasAngularVelocity=!1,this._grip.angularVelocity=new C),this._grip}dispatchEvent(e){return this._targetRay!==null&&this._targetRay.dispatchEvent(e),this._grip!==null&&this._grip.dispatchEvent(e),this._hand!==null&&this._hand.dispatchEvent(e),this}connect(e){if(e&&e.hand){let t=this._hand;if(t)for(let n of e.hand.values())this._getHandJoint(t,n)}return this.dispatchEvent({type:"connected",data:e}),this}disconnect(e){return this.dispatchEvent({type:"disconnected",data:e}),this._targetRay!==null&&(this._targetRay.visible=!1),this._grip!==null&&(this._grip.visible=!1),this._hand!==null&&(this._hand.visible=!1),this}update(e,t,n){let i=null,r=null,o=null,a=this._targetRay,c=this._grip,l=this._hand;if(e&&t.session.visibilityState!=="visible-blurred"){if(l&&e.hand){o=!0;for(let _ of e.hand.values()){let m=t.getJointPose(_,n),u=this._getHandJoint(l,_);m!==null&&(u.matrix.fromArray(m.transform.matrix),u.matrix.decompose(u.position,u.rotation,u.scale),u.jointRadius=m.radius),u.visible=m!==null}let h=l.joints["index-finger-tip"],f=l.joints["thumb-tip"],d=h.position.distanceTo(f.position),p=.02,g=.005;l.inputState.pinching&&d>p+g?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:e.handedness,target:this})):!l.inputState.pinching&&d<=p-g&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:e.handedness,target:this}))}else c!==null&&e.gripSpace&&(r=t.getPose(e.gripSpace,n),r!==null&&(c.matrix.fromArray(r.transform.matrix),c.matrix.decompose(c.position,c.rotation,c.scale),r.linearVelocity?(c.hasLinearVelocity=!0,c.linearVelocity.copy(r.linearVelocity)):c.hasLinearVelocity=!1,r.angularVelocity?(c.hasAngularVelocity=!0,c.angularVelocity.copy(r.angularVelocity)):c.hasAngularVelocity=!1));a!==null&&(i=t.getPose(e.targetRaySpace,n),i===null&&r!==null&&(i=r),i!==null&&(a.matrix.fromArray(i.transform.matrix),a.matrix.decompose(a.position,a.rotation,a.scale),i.linearVelocity?(a.hasLinearVelocity=!0,a.linearVelocity.copy(i.linearVelocity)):a.hasLinearVelocity=!1,i.angularVelocity?(a.hasAngularVelocity=!0,a.angularVelocity.copy(i.angularVelocity)):a.hasAngularVelocity=!1,this.dispatchEvent(Zp)))}return a!==null&&(a.visible=i!==null),c!==null&&(c.visible=r!==null),l!==null&&(l.visible=o!==null),this}_getHandJoint(e,t){if(e.joints[t.jointName]===void 0){let n=new Dt;n.matrixAutoUpdate=!1,n.visible=!1,e.joints[t.jointName]=n,e.add(n)}return e.joints[t.jointName]}},xa=class extends bt{constructor(e,t,n,i,r,o,a,c,l,h){if(h=h!==void 0?h:Rn,h!==Rn&&h!==vi)throw new Error("DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat");n===void 0&&h===Rn&&(n=Cn),n===void 0&&h===vi&&(n=pi),super(null,i,r,o,a,c,h,n,l),this.isDepthTexture=!0,this.image={width:e,height:t},this.magFilter=a!==void 0?a:st,this.minFilter=c!==void 0?c:st,this.flipY=!1,this.generateMipmaps=!1}},ya=class extends Yt{constructor(e,t){super();let n=this,i=null,r=1,o=null,a="local-floor",c=1,l=null,h=null,f=null,d=null,p=null,g=null,_=t.getContextAttributes(),m=null,u=null,w=[],v=[],M=new Set,S=new Map,P=new Ct;P.layers.enable(1),P.viewport=new rt;let R=new Ct;R.layers.enable(2),R.viewport=new rt;let D=[P,R],x=new _a;x.layers.enable(1),x.layers.enable(2);let T=null,W=null;this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getController=function(V){let Z=w[V];return Z===void 0&&(Z=new Xi,w[V]=Z),Z.getTargetRaySpace()},this.getControllerGrip=function(V){let Z=w[V];return Z===void 0&&(Z=new Xi,w[V]=Z),Z.getGripSpace()},this.getHand=function(V){let Z=w[V];return Z===void 0&&(Z=new Xi,w[V]=Z),Z.getHandSpace()};function J(V){let Z=v.indexOf(V.inputSource);if(Z===-1)return;let re=w[Z];re!==void 0&&re.dispatchEvent({type:V.type,data:V.inputSource})}function U(){i.removeEventListener("select",J),i.removeEventListener("selectstart",J),i.removeEventListener("selectend",J),i.removeEventListener("squeeze",J),i.removeEventListener("squeezestart",J),i.removeEventListener("squeezeend",J),i.removeEventListener("end",U),i.removeEventListener("inputsourceschange",O);for(let V=0;V=0&&(v[le]=null,w[le].disconnect(re))}for(let Z=0;Z=v.length){v.push(re),le=Se;break}else if(v[Se]===null){v[Se]=re,le=Se;break}if(le===-1)break}let B=w[le];B&&B.connect(re)}}let k=new C,te=new C;function $(V,Z,re){k.setFromMatrixPosition(Z.matrixWorld),te.setFromMatrixPosition(re.matrixWorld);let le=k.distanceTo(te),B=Z.projectionMatrix.elements,Se=re.projectionMatrix.elements,be=B[14]/(B[10]-1),ie=B[14]/(B[10]+1),ye=(B[9]+1)/B[5],Fe=(B[9]-1)/B[5],me=(B[8]-1)/B[0],Te=(Se[8]+1)/Se[0],qe=be*me,We=be*Te,$e=le/(-me+Te),Ye=$e*-me;Z.matrixWorld.decompose(V.position,V.quaternion,V.scale),V.translateX(Ye),V.translateZ($e),V.matrixWorld.compose(V.position,V.quaternion,V.scale),V.matrixWorldInverse.copy(V.matrixWorld).invert();let Oe=be+$e,ke=ie+$e,at=qe-Ye,E=We+(le-Ye),y=ye*ie/ke*Oe,z=Fe*ie/ke*Oe;V.projectionMatrix.makePerspective(at,E,y,z,Oe,ke),V.projectionMatrixInverse.copy(V.projectionMatrix).invert()}function Y(V,Z){Z===null?V.matrixWorld.copy(V.matrix):V.matrixWorld.multiplyMatrices(Z.matrixWorld,V.matrix),V.matrixWorldInverse.copy(V.matrixWorld).invert()}this.updateCamera=function(V){if(i===null)return;x.near=R.near=P.near=V.near,x.far=R.far=P.far=V.far,(T!==x.near||W!==x.far)&&(i.updateRenderState({depthNear:x.near,depthFar:x.far}),T=x.near,W=x.far);let Z=V.parent,re=x.cameras;Y(x,Z);for(let le=0;leB&&(S.set(le,le.lastChangedTime),n.dispatchEvent({type:"planechanged",data:le}))}}g=null}let ce=new Fl;ce.setAnimationLoop(_e),this.setAnimationLoop=function(V){ne=V},this.dispose=function(){}}};function Jp(s,e){function t(m,u){m.matrixAutoUpdate===!0&&m.updateMatrix(),u.value.copy(m.matrix)}function n(m,u){u.color.getRGB(m.fogColor.value,Ol(s)),u.isFog?(m.fogNear.value=u.near,m.fogFar.value=u.far):u.isFogExp2&&(m.fogDensity.value=u.density)}function i(m,u,w,v,M){u.isMeshBasicMaterial||u.isMeshLambertMaterial?r(m,u):u.isMeshToonMaterial?(r(m,u),f(m,u)):u.isMeshPhongMaterial?(r(m,u),h(m,u)):u.isMeshStandardMaterial?(r(m,u),d(m,u),u.isMeshPhysicalMaterial&&p(m,u,M)):u.isMeshMatcapMaterial?(r(m,u),g(m,u)):u.isMeshDepthMaterial?r(m,u):u.isMeshDistanceMaterial?(r(m,u),_(m,u)):u.isMeshNormalMaterial?r(m,u):u.isLineBasicMaterial?(o(m,u),u.isLineDashedMaterial&&a(m,u)):u.isPointsMaterial?c(m,u,w,v):u.isSpriteMaterial?l(m,u):u.isShadowMaterial?(m.color.value.copy(u.color),m.opacity.value=u.opacity):u.isShaderMaterial&&(u.uniformsNeedUpdate=!1)}function r(m,u){m.opacity.value=u.opacity,u.color&&m.diffuse.value.copy(u.color),u.emissive&&m.emissive.value.copy(u.emissive).multiplyScalar(u.emissiveIntensity),u.map&&(m.map.value=u.map,t(u.map,m.mapTransform)),u.alphaMap&&(m.alphaMap.value=u.alphaMap,t(u.alphaMap,m.alphaMapTransform)),u.bumpMap&&(m.bumpMap.value=u.bumpMap,t(u.bumpMap,m.bumpMapTransform),m.bumpScale.value=u.bumpScale,u.side===St&&(m.bumpScale.value*=-1)),u.normalMap&&(m.normalMap.value=u.normalMap,t(u.normalMap,m.normalMapTransform),m.normalScale.value.copy(u.normalScale),u.side===St&&m.normalScale.value.negate()),u.displacementMap&&(m.displacementMap.value=u.displacementMap,t(u.displacementMap,m.displacementMapTransform),m.displacementScale.value=u.displacementScale,m.displacementBias.value=u.displacementBias),u.emissiveMap&&(m.emissiveMap.value=u.emissiveMap,t(u.emissiveMap,m.emissiveMapTransform)),u.specularMap&&(m.specularMap.value=u.specularMap,t(u.specularMap,m.specularMapTransform)),u.alphaTest>0&&(m.alphaTest.value=u.alphaTest);let w=e.get(u).envMap;if(w&&(m.envMap.value=w,m.flipEnvMap.value=w.isCubeTexture&&w.isRenderTargetTexture===!1?-1:1,m.reflectivity.value=u.reflectivity,m.ior.value=u.ior,m.refractionRatio.value=u.refractionRatio),u.lightMap){m.lightMap.value=u.lightMap;let v=s.useLegacyLights===!0?Math.PI:1;m.lightMapIntensity.value=u.lightMapIntensity*v,t(u.lightMap,m.lightMapTransform)}u.aoMap&&(m.aoMap.value=u.aoMap,m.aoMapIntensity.value=u.aoMapIntensity,t(u.aoMap,m.aoMapTransform))}function o(m,u){m.diffuse.value.copy(u.color),m.opacity.value=u.opacity,u.map&&(m.map.value=u.map,t(u.map,m.mapTransform))}function a(m,u){m.dashSize.value=u.dashSize,m.totalSize.value=u.dashSize+u.gapSize,m.scale.value=u.scale}function c(m,u,w,v){m.diffuse.value.copy(u.color),m.opacity.value=u.opacity,m.size.value=u.size*w,m.scale.value=v*.5,u.map&&(m.map.value=u.map,t(u.map,m.uvTransform)),u.alphaMap&&(m.alphaMap.value=u.alphaMap),u.alphaTest>0&&(m.alphaTest.value=u.alphaTest)}function l(m,u){m.diffuse.value.copy(u.color),m.opacity.value=u.opacity,m.rotation.value=u.rotation,u.map&&(m.map.value=u.map,t(u.map,m.mapTransform)),u.alphaMap&&(m.alphaMap.value=u.alphaMap),u.alphaTest>0&&(m.alphaTest.value=u.alphaTest)}function h(m,u){m.specular.value.copy(u.specular),m.shininess.value=Math.max(u.shininess,1e-4)}function f(m,u){u.gradientMap&&(m.gradientMap.value=u.gradientMap)}function d(m,u){m.metalness.value=u.metalness,u.metalnessMap&&(m.metalnessMap.value=u.metalnessMap,t(u.metalnessMap,m.metalnessMapTransform)),m.roughness.value=u.roughness,u.roughnessMap&&(m.roughnessMap.value=u.roughnessMap,t(u.roughnessMap,m.roughnessMapTransform)),e.get(u).envMap&&(m.envMapIntensity.value=u.envMapIntensity)}function p(m,u,w){m.ior.value=u.ior,u.sheen>0&&(m.sheenColor.value.copy(u.sheenColor).multiplyScalar(u.sheen),m.sheenRoughness.value=u.sheenRoughness,u.sheenColorMap&&(m.sheenColorMap.value=u.sheenColorMap,t(u.sheenColorMap,m.sheenColorMapTransform)),u.sheenRoughnessMap&&(m.sheenRoughnessMap.value=u.sheenRoughnessMap,t(u.sheenRoughnessMap,m.sheenRoughnessMapTransform))),u.clearcoat>0&&(m.clearcoat.value=u.clearcoat,m.clearcoatRoughness.value=u.clearcoatRoughness,u.clearcoatMap&&(m.clearcoatMap.value=u.clearcoatMap,t(u.clearcoatMap,m.clearcoatMapTransform)),u.clearcoatRoughnessMap&&(m.clearcoatRoughnessMap.value=u.clearcoatRoughnessMap,t(u.clearcoatRoughnessMap,m.clearcoatRoughnessMapTransform)),u.clearcoatNormalMap&&(m.clearcoatNormalMap.value=u.clearcoatNormalMap,t(u.clearcoatNormalMap,m.clearcoatNormalMapTransform),m.clearcoatNormalScale.value.copy(u.clearcoatNormalScale),u.side===St&&m.clearcoatNormalScale.value.negate())),u.iridescence>0&&(m.iridescence.value=u.iridescence,m.iridescenceIOR.value=u.iridescenceIOR,m.iridescenceThicknessMinimum.value=u.iridescenceThicknessRange[0],m.iridescenceThicknessMaximum.value=u.iridescenceThicknessRange[1],u.iridescenceMap&&(m.iridescenceMap.value=u.iridescenceMap,t(u.iridescenceMap,m.iridescenceMapTransform)),u.iridescenceThicknessMap&&(m.iridescenceThicknessMap.value=u.iridescenceThicknessMap,t(u.iridescenceThicknessMap,m.iridescenceThicknessMapTransform))),u.transmission>0&&(m.transmission.value=u.transmission,m.transmissionSamplerMap.value=w.texture,m.transmissionSamplerSize.value.set(w.width,w.height),u.transmissionMap&&(m.transmissionMap.value=u.transmissionMap,t(u.transmissionMap,m.transmissionMapTransform)),m.thickness.value=u.thickness,u.thicknessMap&&(m.thicknessMap.value=u.thicknessMap,t(u.thicknessMap,m.thicknessMapTransform)),m.attenuationDistance.value=u.attenuationDistance,m.attenuationColor.value.copy(u.attenuationColor)),m.specularIntensity.value=u.specularIntensity,m.specularColor.value.copy(u.specularColor),u.specularColorMap&&(m.specularColorMap.value=u.specularColorMap,t(u.specularColorMap,m.specularColorMapTransform)),u.specularIntensityMap&&(m.specularIntensityMap.value=u.specularIntensityMap,t(u.specularIntensityMap,m.specularIntensityMapTransform))}function g(m,u){u.matcap&&(m.matcap.value=u.matcap)}function _(m,u){let w=e.get(u).light;m.referencePosition.value.setFromMatrixPosition(w.matrixWorld),m.nearDistance.value=w.shadow.camera.near,m.farDistance.value=w.shadow.camera.far}return{refreshFogUniforms:n,refreshMaterialUniforms:i}}function $p(s,e,t,n){let i={},r={},o=[],a=t.isWebGL2?s.getParameter(35375):0;function c(w,v){let M=v.program;n.uniformBlockBinding(w,M)}function l(w,v){let M=i[w.id];M===void 0&&(g(w),M=h(w),i[w.id]=M,w.addEventListener("dispose",m));let S=v.program;n.updateUBOMapping(w,S);let P=e.render.frame;r[w.id]!==P&&(d(w),r[w.id]=P)}function h(w){let v=f();w.__bindingPointIndex=v;let M=s.createBuffer(),S=w.__size,P=w.usage;return s.bindBuffer(35345,M),s.bufferData(35345,S,P),s.bindBuffer(35345,null),s.bindBufferBase(35345,v,M),M}function f(){for(let w=0;w0){P=M%S;let J=S-P;P!==0&&J-T.boundary<0&&(M+=S-P,x.__offset=M)}M+=T.storage}return P=M%S,P>0&&(M+=S-P),w.__size=M,w.__cache={},this}function _(w){let v={boundary:0,storage:0};return typeof w=="number"?(v.boundary=4,v.storage=4):w.isVector2?(v.boundary=8,v.storage=8):w.isVector3||w.isColor?(v.boundary=16,v.storage=12):w.isVector4?(v.boundary=16,v.storage=16):w.isMatrix3?(v.boundary=48,v.storage=48):w.isMatrix4?(v.boundary=64,v.storage=64):w.isTexture?console.warn("THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group."):console.warn("THREE.WebGLRenderer: Unsupported uniform value type.",w),v}function m(w){let v=w.target;v.removeEventListener("dispose",m);let M=o.indexOf(v.__bindingPointIndex);o.splice(M,1),s.deleteBuffer(i[v.id]),delete i[v.id],delete r[v.id]}function u(){for(let w in i)s.deleteBuffer(i[w]);o=[],i={},r={}}return{bind:c,update:l,dispose:u}}function jp(){let s=Zs("canvas");return s.style.display="block",s}var Ki=class{constructor(e={}){let{canvas:t=jp(),context:n=null,depth:i=!0,stencil:r=!0,alpha:o=!1,antialias:a=!1,premultipliedAlpha:c=!0,preserveDrawingBuffer:l=!1,powerPreference:h="default",failIfMajorPerformanceCaveat:f=!1}=e;this.isWebGLRenderer=!0;let d;n!==null?d=n.getContextAttributes().alpha:d=o;let p=null,g=null,_=[],m=[];this.domElement=t,this.debug={checkShaderErrors:!0,onShaderError:null},this.autoClear=!0,this.autoClearColor=!0,this.autoClearDepth=!0,this.autoClearStencil=!0,this.sortObjects=!0,this.clippingPlanes=[],this.localClippingEnabled=!1,this.outputEncoding=Dn,this.useLegacyLights=!0,this.toneMapping=rn,this.toneMappingExposure=1;let u=this,w=!1,v=0,M=0,S=null,P=-1,R=null,D=new rt,x=new rt,T=null,W=t.width,J=t.height,U=1,O=null,k=null,te=new rt(0,0,W,J),$=new rt(0,0,W,J),Y=!1,j=new ji,ne=!1,_e=!1,ce=null,V=new Qe,Z=new C,re={background:null,fog:null,environment:null,overrideMaterial:null,isScene:!0};function le(){return S===null?U:1}let B=n;function Se(b,N){for(let H=0;H0?g=m[m.length-1]:g=null,_.pop(),_.length>0?p=_[_.length-1]:p=null};function ot(b,N,H,L){if(b.visible===!1)return;if(b.layers.test(N.layers)){if(b.isGroup)H=b.renderOrder;else if(b.isLOD)b.autoUpdate===!0&&b.update(N);else if(b.isLight)g.pushLight(b),b.castShadow&&g.pushShadow(b);else if(b.isSprite){if(!b.frustumCulled||j.intersectsSprite(b)){L&&Z.setFromMatrixPosition(b.matrixWorld).applyMatrix4(V);let Me=Oe.update(b),Ee=b.material;Ee.visible&&p.push(b,Me,Ee,H,Z.z,null)}}else if((b.isMesh||b.isLine||b.isPoints)&&(b.isSkinnedMesh&&b.skeleton.frame!==Fe.render.frame&&(b.skeleton.update(),b.skeleton.frame=Fe.render.frame),!b.frustumCulled||j.intersectsObject(b))){L&&Z.setFromMatrixPosition(b.matrixWorld).applyMatrix4(V);let Me=Oe.update(b),Ee=b.material;if(Array.isArray(Ee)){let Ae=Me.groups;for(let Pe=0,Re=Ae.length;Pe0&&Je(X,xe,N,H),L&&ye.viewport(D.copy(L)),X.length>0&&Rt(X,N,H),xe.length>0&&Rt(xe,N,H),Me.length>0&&Rt(Me,N,H),ye.buffers.depth.setTest(!0),ye.buffers.depth.setMask(!0),ye.buffers.color.setMask(!0),ye.setPolygonOffset(!1)}function Je(b,N,H,L){if(ce===null){let Ee=ie.isWebGL2;ce=new on(1024,1024,{generateMipmaps:!0,type:be.has("EXT_color_buffer_half_float")?Yi:Ln,minFilter:qi,samples:Ee&&a===!0?4:0})}let X=u.getRenderTarget();u.setRenderTarget(ce),u.clear();let xe=u.toneMapping;u.toneMapping=rn,Rt(b,H,L),Te.updateMultisampleRenderTarget(ce),Te.updateRenderTargetMipmap(ce);let Me=!1;for(let Ee=0,Ae=N.length;Ee0&&Te.useMultisampledRTT(b)===!1?X=me.get(b).__webglMultisampledFramebuffer:X=Re,D.copy(b.viewport),x.copy(b.scissor),T=b.scissorTest}else D.copy(te).multiplyScalar(U).floor(),x.copy($).multiplyScalar(U).floor(),T=Y;if(ye.bindFramebuffer(36160,X)&&ie.drawBuffers&&L&&ye.drawBuffers(b,X),ye.viewport(D),ye.scissor(x),ye.setScissorTest(T),xe){let Ae=me.get(b.texture);B.framebufferTexture2D(36160,36064,34069+N,Ae.__webglTexture,H)}else if(Me){let Ae=me.get(b.texture),Pe=N||0;B.framebufferTextureLayer(36160,36064,Ae.__webglTexture,H||0,Pe)}P=-1},this.readRenderTargetPixels=function(b,N,H,L,X,xe,Me){if(!(b&&b.isWebGLRenderTarget)){console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");return}let Ee=me.get(b).__webglFramebuffer;if(b.isWebGLCubeRenderTarget&&Me!==void 0&&(Ee=Ee[Me]),Ee){ye.bindFramebuffer(36160,Ee);try{let Ae=b.texture,Pe=Ae.format,Re=Ae.type;if(Pe!==Bt&&F.convert(Pe)!==B.getParameter(35739)){console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");return}let Le=Re===Yi&&(be.has("EXT_color_buffer_half_float")||ie.isWebGL2&&be.has("EXT_color_buffer_float"));if(Re!==Ln&&F.convert(Re)!==B.getParameter(35738)&&!(Re===In&&(ie.isWebGL2||be.has("OES_texture_float")||be.has("WEBGL_color_buffer_float")))&&!Le){console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");return}N>=0&&N<=b.width-L&&H>=0&&H<=b.height-X&&B.readPixels(N,H,L,X,F.convert(Pe),F.convert(Re),xe)}finally{let Ae=S!==null?me.get(S).__webglFramebuffer:null;ye.bindFramebuffer(36160,Ae)}}},this.copyFramebufferToTexture=function(b,N,H=0){let L=Math.pow(2,-H),X=Math.floor(N.image.width*L),xe=Math.floor(N.image.height*L);Te.setTexture2D(N,0),B.copyTexSubImage2D(3553,H,0,0,b.x,b.y,X,xe),ye.unbindTexture()},this.copyTextureToTexture=function(b,N,H,L=0){let X=N.image.width,xe=N.image.height,Me=F.convert(H.format),Ee=F.convert(H.type);Te.setTexture2D(H,0),B.pixelStorei(37440,H.flipY),B.pixelStorei(37441,H.premultiplyAlpha),B.pixelStorei(3317,H.unpackAlignment),N.isDataTexture?B.texSubImage2D(3553,L,b.x,b.y,X,xe,Me,Ee,N.image.data):N.isCompressedTexture?B.compressedTexSubImage2D(3553,L,b.x,b.y,N.mipmaps[0].width,N.mipmaps[0].height,Me,N.mipmaps[0].data):B.texSubImage2D(3553,L,b.x,b.y,Me,Ee,N.image),L===0&&H.generateMipmaps&&B.generateMipmap(3553),ye.unbindTexture()},this.copyTextureToTexture3D=function(b,N,H,L,X=0){if(u.isWebGL1Renderer){console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.");return}let xe=b.max.x-b.min.x+1,Me=b.max.y-b.min.y+1,Ee=b.max.z-b.min.z+1,Ae=F.convert(L.format),Pe=F.convert(L.type),Re;if(L.isData3DTexture)Te.setTexture3D(L,0),Re=32879;else if(L.isDataArrayTexture)Te.setTexture2DArray(L,0),Re=35866;else{console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");return}B.pixelStorei(37440,L.flipY),B.pixelStorei(37441,L.premultiplyAlpha),B.pixelStorei(3317,L.unpackAlignment);let Le=B.getParameter(3314),Be=B.getParameter(32878),mt=B.getParameter(3316),Ut=B.getParameter(3315),vn=B.getParameter(32877),je=H.isCompressedTexture?H.mipmaps[0]:H.image;B.pixelStorei(3314,je.width),B.pixelStorei(32878,je.height),B.pixelStorei(3316,b.min.x),B.pixelStorei(3315,b.min.y),B.pixelStorei(32877,b.min.z),H.isDataTexture||H.isData3DTexture?B.texSubImage3D(Re,X,N.x,N.y,N.z,xe,Me,Ee,Ae,Pe,je.data):H.isCompressedArrayTexture?(console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture."),B.compressedTexSubImage3D(Re,X,N.x,N.y,N.z,xe,Me,Ee,Ae,je.data)):B.texSubImage3D(Re,X,N.x,N.y,N.z,xe,Me,Ee,Ae,Pe,je),B.pixelStorei(3314,Le),B.pixelStorei(32878,Be),B.pixelStorei(3316,mt),B.pixelStorei(3315,Ut),B.pixelStorei(32877,vn),X===0&&L.generateMipmaps&&B.generateMipmap(Re),ye.unbindTexture()},this.initTexture=function(b){b.isCubeTexture?Te.setTextureCube(b,0):b.isData3DTexture?Te.setTexture3D(b,0):b.isDataArrayTexture||b.isCompressedArrayTexture?Te.setTexture2DArray(b,0):Te.setTexture2D(b,0),ye.unbindTexture()},this.resetState=function(){v=0,M=0,S=null,ye.reset(),oe.reset()},typeof __THREE_DEVTOOLS__<"u"&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}get physicallyCorrectLights(){return console.warn("THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead."),!this.useLegacyLights}set physicallyCorrectLights(e){console.warn("THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead."),this.useLegacyLights=!e}},va=class extends Ki{};va.prototype.isWebGL1Renderer=!0;var tr=class extends pt{constructor(){super(),this.isScene=!0,this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.backgroundBlurriness=0,this.backgroundIntensity=1,this.overrideMaterial=null,typeof __THREE_DEVTOOLS__<"u"&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(e,t){return super.copy(e,t),e.background!==null&&(this.background=e.background.clone()),e.environment!==null&&(this.environment=e.environment.clone()),e.fog!==null&&(this.fog=e.fog.clone()),this.backgroundBlurriness=e.backgroundBlurriness,this.backgroundIntensity=e.backgroundIntensity,e.overrideMaterial!==null&&(this.overrideMaterial=e.overrideMaterial.clone()),this.matrixAutoUpdate=e.matrixAutoUpdate,this}toJSON(e){let t=super.toJSON(e);return this.fog!==null&&(t.object.fog=this.fog.toJSON()),this.backgroundBlurriness>0&&(t.object.backgroundBlurriness=this.backgroundBlurriness),this.backgroundIntensity!==1&&(t.object.backgroundIntensity=this.backgroundIntensity),t}get autoUpdate(){return console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate}set autoUpdate(e){console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate=e}},Qi=class{constructor(e,t){this.isInterleavedBuffer=!0,this.array=e,this.stride=t,this.count=e!==void 0?e.length/t:0,this.usage=na,this.updateRange={offset:0,count:-1},this.version=0,this.uuid=an()}onUploadCallback(){}set needsUpdate(e){e===!0&&this.version++}setUsage(e){return this.usage=e,this}copy(e){return this.array=new e.array.constructor(e.array),this.count=e.count,this.stride=e.stride,this.usage=e.usage,this}copyAt(e,t,n){e*=this.stride,n*=t.stride;for(let i=0,r=this.stride;ie.far||t.push({distance:c,point:Bi.clone(),uv:Pn.getInterpolation(Bi,ks,ki,Vs,gl,Jr,_l,new ve),face:null,object:this})}copy(e,t){return super.copy(e,t),e.center!==void 0&&this.center.copy(e.center),this.material=e.material,this}};function Hs(s,e,t,n,i,r){hi.subVectors(s,t).addScalar(.5).multiply(n),i!==void 0?(zi.x=r*hi.x-i*hi.y,zi.y=i*hi.x+r*hi.y):zi.copy(hi),s.copy(e),s.x+=zi.x,s.y+=zi.y,s.applyMatrix4(Hl)}var yn=class extends ln{constructor(e){super(),this.isLineBasicMaterial=!0,this.type="LineBasicMaterial",this.color=new De(16777215),this.map=null,this.linewidth=1,this.linecap="round",this.linejoin="round",this.fog=!0,this.setValues(e)}copy(e){return super.copy(e),this.color.copy(e.color),this.map=e.map,this.linewidth=e.linewidth,this.linecap=e.linecap,this.linejoin=e.linejoin,this.fog=e.fog,this}},xl=new C,yl=new C,vl=new Qe,$r=new Ji,Gs=new bi,ba=class extends pt{constructor(e=new Mt,t=new yn){super(),this.isLine=!0,this.type="Line",this.geometry=e,this.material=t,this.updateMorphTargets()}copy(e,t){return super.copy(e,t),this.material=e.material,this.geometry=e.geometry,this}computeLineDistances(){let e=this.geometry;if(e.index===null){let t=e.attributes.position,n=[0];for(let i=1,r=t.count;ic)continue;d.applyMatrix4(this.matrixWorld);let D=e.ray.origin.distanceTo(d);De.far||t.push({distance:D,point:f.clone().applyMatrix4(this.matrixWorld),index:v,face:null,faceIndex:null,object:this})}}else{let u=Math.max(0,o.start),w=Math.min(m.count,o.start+o.count);for(let v=u,M=w-1;vc)continue;d.applyMatrix4(this.matrixWorld);let P=e.ray.origin.distanceTo(d);Pe.far||t.push({distance:P,point:f.clone().applyMatrix4(this.matrixWorld),index:v,face:null,faceIndex:null,object:this})}}}updateMorphTargets(){let t=this.geometry.morphAttributes,n=Object.keys(t);if(n.length>0){let i=t[n[0]];if(i!==void 0){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let r=0,o=i.length;r=r)){let a=t[1];e=r)break e}o=n,n=0;break t}break n}for(;n>>1;et;)--o;if(++o,r!==0||o!==i){r>=o&&(o=Math.max(o,1),r=o-1);let a=this.getValueSize();this.times=gn(n,r,o),this.values=gn(this.values,r*a,o*a)}return this}validate(){let e=!0,t=this.getValueSize();t-Math.floor(t)!==0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),e=!1);let n=this.times,i=this.values,r=n.length;r===0&&(console.error("THREE.KeyframeTrack: Track is empty.",this),e=!1);let o=null;for(let a=0;a!==r;a++){let c=n[a];if(typeof c=="number"&&isNaN(c)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,a,c),e=!1;break}if(o!==null&&o>c){console.error("THREE.KeyframeTrack: Out of order keys.",this,a,c,o),e=!1;break}o=c}if(i!==void 0&&Gl(i))for(let a=0,c=i.length;a!==c;++a){let l=i[a];if(isNaN(l)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,a,l),e=!1;break}}return e}optimize(){let e=gn(this.times),t=gn(this.values),n=this.getValueSize(),i=this.getInterpolation()===Ar,r=e.length-1,o=1;for(let a=1;a0){e[o]=e[r];for(let a=r*n,c=o*n,l=0;l!==n;++l)t[c+l]=t[a+l];++o}return o!==e.length?(this.times=gn(e,0,o),this.values=gn(t,0,o*n)):(this.times=e,this.values=t),this}clone(){let e=gn(this.times,0),t=gn(this.values,0),n=this.constructor,i=new n(this.name,e,t);return i.createInterpolant=this.createInterpolant,i}};Vt.prototype.TimeBufferType=Float32Array;Vt.prototype.ValueBufferType=Float32Array;Vt.prototype.DefaultInterpolation=Ys;var Fn=class extends Vt{};Fn.prototype.ValueTypeName="bool";Fn.prototype.ValueBufferType=Array;Fn.prototype.DefaultInterpolation=qs;Fn.prototype.InterpolantFactoryMethodLinear=void 0;Fn.prototype.InterpolantFactoryMethodSmooth=void 0;var Ea=class extends Vt{};Ea.prototype.ValueTypeName="color";var Ta=class extends Vt{};Ta.prototype.ValueTypeName="number";var Aa=class extends Ci{constructor(e,t,n,i){super(e,t,n,i)}interpolate_(e,t,n,i){let r=this.resultBuffer,o=this.sampleValues,a=this.valueSize,c=(n-t)/(i-t),l=e*a;for(let h=l+a;l!==h;l+=4)zt.slerpFlat(r,0,o,l-a,o,l,c);return r}},ts=class extends Vt{InterpolantFactoryMethodLinear(e){return new Aa(this.times,this.values,this.getValueSize(),e)}};ts.prototype.ValueTypeName="quaternion";ts.prototype.DefaultInterpolation=Ys;ts.prototype.InterpolantFactoryMethodSmooth=void 0;var Bn=class extends Vt{};Bn.prototype.ValueTypeName="string";Bn.prototype.ValueBufferType=Array;Bn.prototype.DefaultInterpolation=qs;Bn.prototype.InterpolantFactoryMethodLinear=void 0;Bn.prototype.InterpolantFactoryMethodSmooth=void 0;var Ca=class extends Vt{};Ca.prototype.ValueTypeName="vector";var Sl={enabled:!1,files:{},add:function(s,e){this.enabled!==!1&&(this.files[s]=e)},get:function(s){if(this.enabled!==!1)return this.files[s]},remove:function(s){delete this.files[s]},clear:function(){this.files={}}},Ia=class{constructor(e,t,n){let i=this,r=!1,o=0,a=0,c,l=[];this.onStart=void 0,this.onLoad=e,this.onProgress=t,this.onError=n,this.itemStart=function(h){a++,r===!1&&i.onStart!==void 0&&i.onStart(h,o,a),r=!0},this.itemEnd=function(h){o++,i.onProgress!==void 0&&i.onProgress(h,o,a),o===a&&(r=!1,i.onLoad!==void 0&&i.onLoad())},this.itemError=function(h){i.onError!==void 0&&i.onError(h)},this.resolveURL=function(h){return c?c(h):h},this.setURLModifier=function(h){return c=h,this},this.addHandler=function(h,f){return l.push(h,f),this},this.removeHandler=function(h){let f=l.indexOf(h);return f!==-1&&l.splice(f,2),this},this.getHandler=function(h){for(let f=0,d=l.length;f"u"&&console.warn("THREE.ImageBitmapLoader: createImageBitmap() not supported."),typeof fetch>"u"&&console.warn("THREE.ImageBitmapLoader: fetch() not supported."),this.options={premultiplyAlpha:"none"}}setOptions(e){return this.options=e,this}load(e,t,n,i){e===void 0&&(e=""),this.path!==void 0&&(e=this.path+e),e=this.manager.resolveURL(e);let r=this,o=Sl.get(e);if(o!==void 0)return r.manager.itemStart(e),setTimeout(function(){t&&t(o),r.manager.itemEnd(e)},0),o;let a={};a.credentials=this.crossOrigin==="anonymous"?"same-origin":"include",a.headers=this.requestHeader,fetch(e,a).then(function(c){return c.blob()}).then(function(c){return createImageBitmap(c,Object.assign(r.options,{colorSpaceConversion:"none"}))}).then(function(c){Sl.add(e,c),t&&t(c),r.manager.itemEnd(e)}).catch(function(c){i&&i(c),r.manager.itemError(e),r.manager.itemEnd(e)}),r.manager.itemStart(e)}};var ka="\\[\\]\\.:\\/",Qp=new RegExp("["+ka+"]","g"),Va="[^"+ka+"]",em="[^"+ka.replace("\\.","")+"]",tm=/((?:WC+[\/:])*)/.source.replace("WC",Va),nm=/(WCOD+)?/.source.replace("WCOD",em),im=/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC",Va),sm=/\.(WC+)(?:\[(.+)\])?/.source.replace("WC",Va),rm=new RegExp("^"+tm+nm+im+sm+"$"),am=["material","materials","bones","map"],Da=class{constructor(e,t,n){let i=n||Ge.parseTrackName(t);this._targetGroup=e,this._bindings=e.subscribe_(t,i)}getValue(e,t){this.bind();let n=this._targetGroup.nCachedObjects_,i=this._bindings[n];i!==void 0&&i.getValue(e,t)}setValue(e,t){let n=this._bindings;for(let i=this._targetGroup.nCachedObjects_,r=n.length;i!==r;++i)n[i].setValue(e,t)}bind(){let e=this._bindings;for(let t=this._targetGroup.nCachedObjects_,n=e.length;t!==n;++t)e[t].bind()}unbind(){let e=this._bindings;for(let t=this._targetGroup.nCachedObjects_,n=e.length;t!==n;++t)e[t].unbind()}},Ge=class s{constructor(e,t,n){this.path=t,this.parsedPath=n||s.parseTrackName(t),this.node=s.findNode(e,this.parsedPath.nodeName),this.rootNode=e,this.getValue=this._getValue_unbound,this.setValue=this._setValue_unbound}static create(e,t,n){return e&&e.isAnimationObjectGroup?new s.Composite(e,t,n):new s(e,t,n)}static sanitizeNodeName(e){return e.replace(/\s/g,"_").replace(Qp,"")}static parseTrackName(e){let t=rm.exec(e);if(t===null)throw new Error("PropertyBinding: Cannot parse trackName: "+e);let n={nodeName:t[2],objectName:t[3],objectIndex:t[4],propertyName:t[5],propertyIndex:t[6]},i=n.nodeName&&n.nodeName.lastIndexOf(".");if(i!==void 0&&i!==-1){let r=n.nodeName.substring(i+1);am.indexOf(r)!==-1&&(n.nodeName=n.nodeName.substring(0,i),n.objectName=r)}if(n.propertyName===null||n.propertyName.length===0)throw new Error("PropertyBinding: can not parse propertyName from trackName: "+e);return n}static findNode(e,t){if(t===void 0||t===""||t==="."||t===-1||t===e.name||t===e.uuid)return e;if(e.skeleton){let n=e.skeleton.getBoneByName(t);if(n!==void 0)return n}if(e.children){let n=function(r){for(let o=0;oMath.PI&&(we-=pe),Ie<-Math.PI?Ie+=pe:Ie>Math.PI&&(Ie-=pe),we<=Ie?a.theta=Math.max(we,Math.min(Ie,a.theta)):a.theta=a.theta>(we+Ie)/2?Math.max(we,a.theta):Math.min(Ie,a.theta)),a.phi=Math.max(n.minPolarAngle,Math.min(n.maxPolarAngle,a.phi)),a.makeSafe(),a.radius*=l,a.radius=Math.max(n.minDistance,Math.min(n.maxDistance,a.radius)),n.enableDamping===!0?n.target.addScaledVector(h,n.dampingFactor):n.target.add(h),A.setFromSpherical(a),A.applyQuaternion(F),de.copy(n.target).add(A),n.object.lookAt(n.target),n.enableDamping===!0?(c.theta*=1-n.dampingFactor,c.phi*=1-n.dampingFactor,h.multiplyScalar(1-n.dampingFactor)):(c.set(0,0,0),h.set(0,0,0)),l=1,f||oe.distanceToSquared(n.object.position)>o||8*(1-fe.dot(n.object.quaternion))>o?(n.dispatchEvent(Xl),oe.copy(n.object.position),fe.copy(n.object.quaternion),f=!1,!0):!1}})(),this.dispose=function(){n.domElement.removeEventListener("contextmenu",y),n.domElement.removeEventListener("pointerdown",Te),n.domElement.removeEventListener("pointercancel",We),n.domElement.removeEventListener("wheel",Oe),n.domElement.removeEventListener("pointermove",qe),n.domElement.removeEventListener("pointerup",We),n._domElementKeyEvents!==null&&(n._domElementKeyEvents.removeEventListener("keydown",ke),n._domElementKeyEvents=null)};let n=this,i={NONE:-1,ROTATE:0,DOLLY:1,PAN:2,TOUCH_ROTATE:3,TOUCH_PAN:4,TOUCH_DOLLY_PAN:5,TOUCH_DOLLY_ROTATE:6},r=i.NONE,o=1e-6,a=new is,c=new is,l=1,h=new C,f=!1,d=new ve,p=new ve,g=new ve,_=new ve,m=new ve,u=new ve,w=new ve,v=new ve,M=new ve,S=[],P={};function R(){return 2*Math.PI/60/60*n.autoRotateSpeed}function D(){return Math.pow(.95,n.zoomSpeed)}function x(A){c.theta-=A}function T(A){c.phi-=A}let W=(function(){let A=new C;return function(F,oe){A.setFromMatrixColumn(oe,0),A.multiplyScalar(-F),h.add(A)}})(),J=(function(){let A=new C;return function(F,oe){n.screenSpacePanning===!0?A.setFromMatrixColumn(oe,1):(A.setFromMatrixColumn(oe,0),A.crossVectors(n.object.up,A)),A.multiplyScalar(F),h.add(A)}})(),U=(function(){let A=new C;return function(F,oe){let fe=n.domElement;if(n.object.isPerspectiveCamera){let pe=n.object.position;A.copy(pe).sub(n.target);let he=A.length();he*=Math.tan(n.object.fov/2*Math.PI/180),W(2*F*he/fe.clientHeight,n.object.matrix),J(2*oe*he/fe.clientHeight,n.object.matrix)}else n.object.isOrthographicCamera?(W(F*(n.object.right-n.object.left)/n.object.zoom/fe.clientWidth,n.object.matrix),J(oe*(n.object.top-n.object.bottom)/n.object.zoom/fe.clientHeight,n.object.matrix)):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."),n.enablePan=!1)}})();function O(A){n.object.isPerspectiveCamera?l/=A:n.object.isOrthographicCamera?(n.object.zoom=Math.max(n.minZoom,Math.min(n.maxZoom,n.object.zoom*A)),n.object.updateProjectionMatrix(),f=!0):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),n.enableZoom=!1)}function k(A){n.object.isPerspectiveCamera?l*=A:n.object.isOrthographicCamera?(n.object.zoom=Math.max(n.minZoom,Math.min(n.maxZoom,n.object.zoom/A)),n.object.updateProjectionMatrix(),f=!0):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),n.enableZoom=!1)}function te(A){d.set(A.clientX,A.clientY)}function $(A){w.set(A.clientX,A.clientY)}function Y(A){_.set(A.clientX,A.clientY)}function j(A){p.set(A.clientX,A.clientY),g.subVectors(p,d).multiplyScalar(n.rotateSpeed);let q=n.domElement;x(2*Math.PI*g.x/q.clientHeight),T(2*Math.PI*g.y/q.clientHeight),d.copy(p),n.update()}function ne(A){v.set(A.clientX,A.clientY),M.subVectors(v,w),M.y>0?O(D()):M.y<0&&k(D()),w.copy(v),n.update()}function _e(A){m.set(A.clientX,A.clientY),u.subVectors(m,_).multiplyScalar(n.panSpeed),U(u.x,u.y),_.copy(m),n.update()}function ce(A){A.deltaY<0?k(D()):A.deltaY>0&&O(D()),n.update()}function V(A){let q=!1;switch(A.code){case n.keys.UP:A.ctrlKey||A.metaKey||A.shiftKey?T(2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):U(0,n.keyPanSpeed),q=!0;break;case n.keys.BOTTOM:A.ctrlKey||A.metaKey||A.shiftKey?T(-2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):U(0,-n.keyPanSpeed),q=!0;break;case n.keys.LEFT:A.ctrlKey||A.metaKey||A.shiftKey?x(2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):U(n.keyPanSpeed,0),q=!0;break;case n.keys.RIGHT:A.ctrlKey||A.metaKey||A.shiftKey?x(-2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):U(-n.keyPanSpeed,0),q=!0;break}q&&(A.preventDefault(),n.update())}function Z(){if(S.length===1)d.set(S[0].pageX,S[0].pageY);else{let A=.5*(S[0].pageX+S[1].pageX),q=.5*(S[0].pageY+S[1].pageY);d.set(A,q)}}function re(){if(S.length===1)_.set(S[0].pageX,S[0].pageY);else{let A=.5*(S[0].pageX+S[1].pageX),q=.5*(S[0].pageY+S[1].pageY);_.set(A,q)}}function le(){let A=S[0].pageX-S[1].pageX,q=S[0].pageY-S[1].pageY,F=Math.sqrt(A*A+q*q);w.set(0,F)}function B(){n.enableZoom&&le(),n.enablePan&&re()}function Se(){n.enableZoom&&le(),n.enableRotate&&Z()}function be(A){if(S.length==1)p.set(A.pageX,A.pageY);else{let F=ae(A),oe=.5*(A.pageX+F.x),fe=.5*(A.pageY+F.y);p.set(oe,fe)}g.subVectors(p,d).multiplyScalar(n.rotateSpeed);let q=n.domElement;x(2*Math.PI*g.x/q.clientHeight),T(2*Math.PI*g.y/q.clientHeight),d.copy(p)}function ie(A){if(S.length===1)m.set(A.pageX,A.pageY);else{let q=ae(A),F=.5*(A.pageX+q.x),oe=.5*(A.pageY+q.y);m.set(F,oe)}u.subVectors(m,_).multiplyScalar(n.panSpeed),U(u.x,u.y),_.copy(m)}function ye(A){let q=ae(A),F=A.pageX-q.x,oe=A.pageY-q.y,fe=Math.sqrt(F*F+oe*oe);v.set(0,fe),M.set(0,Math.pow(v.y/w.y,n.zoomSpeed)),O(M.y),w.copy(v)}function Fe(A){n.enableZoom&&ye(A),n.enablePan&&ie(A)}function me(A){n.enableZoom&&ye(A),n.enableRotate&&be(A)}function Te(A){n.enabled!==!1&&(S.length===0&&(n.domElement.setPointerCapture(A.pointerId),n.domElement.addEventListener("pointermove",qe),n.domElement.addEventListener("pointerup",We)),z(A),A.pointerType==="touch"?at(A):$e(A))}function qe(A){n.enabled!==!1&&(A.pointerType==="touch"?E(A):Ye(A))}function We(A){K(A),S.length===0&&(n.domElement.releasePointerCapture(A.pointerId),n.domElement.removeEventListener("pointermove",qe),n.domElement.removeEventListener("pointerup",We)),n.dispatchEvent(ql),r=i.NONE}function $e(A){let q;switch(A.button){case 0:q=n.mouseButtons.LEFT;break;case 1:q=n.mouseButtons.MIDDLE;break;case 2:q=n.mouseButtons.RIGHT;break;default:q=-1}switch(q){case zn.DOLLY:if(n.enableZoom===!1)return;$(A),r=i.DOLLY;break;case zn.ROTATE:if(A.ctrlKey||A.metaKey||A.shiftKey){if(n.enablePan===!1)return;Y(A),r=i.PAN}else{if(n.enableRotate===!1)return;te(A),r=i.ROTATE}break;case zn.PAN:if(A.ctrlKey||A.metaKey||A.shiftKey){if(n.enableRotate===!1)return;te(A),r=i.ROTATE}else{if(n.enablePan===!1)return;Y(A),r=i.PAN}break;default:r=i.NONE}r!==i.NONE&&n.dispatchEvent(Ha)}function Ye(A){switch(r){case i.ROTATE:if(n.enableRotate===!1)return;j(A);break;case i.DOLLY:if(n.enableZoom===!1)return;ne(A);break;case i.PAN:if(n.enablePan===!1)return;_e(A);break}}function Oe(A){n.enabled===!1||n.enableZoom===!1||r!==i.NONE||(A.preventDefault(),n.dispatchEvent(Ha),ce(A),n.dispatchEvent(ql))}function ke(A){n.enabled===!1||n.enablePan===!1||V(A)}function at(A){switch(ee(A),S.length){case 1:switch(n.touches.ONE){case kn.ROTATE:if(n.enableRotate===!1)return;Z(),r=i.TOUCH_ROTATE;break;case kn.PAN:if(n.enablePan===!1)return;re(),r=i.TOUCH_PAN;break;default:r=i.NONE}break;case 2:switch(n.touches.TWO){case kn.DOLLY_PAN:if(n.enableZoom===!1&&n.enablePan===!1)return;B(),r=i.TOUCH_DOLLY_PAN;break;case kn.DOLLY_ROTATE:if(n.enableZoom===!1&&n.enableRotate===!1)return;Se(),r=i.TOUCH_DOLLY_ROTATE;break;default:r=i.NONE}break;default:r=i.NONE}r!==i.NONE&&n.dispatchEvent(Ha)}function E(A){switch(ee(A),r){case i.TOUCH_ROTATE:if(n.enableRotate===!1)return;be(A),n.update();break;case i.TOUCH_PAN:if(n.enablePan===!1)return;ie(A),n.update();break;case i.TOUCH_DOLLY_PAN:if(n.enableZoom===!1&&n.enablePan===!1)return;Fe(A),n.update();break;case i.TOUCH_DOLLY_ROTATE:if(n.enableZoom===!1&&n.enableRotate===!1)return;me(A),n.update();break;default:r=i.NONE}}function y(A){n.enabled!==!1&&A.preventDefault()}function z(A){S.push(A)}function K(A){delete P[A.pointerId];for(let q=0;q>24}readUint8(e){return this.bytes_[e]}readInt16(e){return this.readUint16(e)<<16>>16}readUint16(e){return this.bytes_[e]|this.bytes_[e+1]<<8}readInt32(e){return this.bytes_[e]|this.bytes_[e+1]<<8|this.bytes_[e+2]<<16|this.bytes_[e+3]<<24}readUint32(e){return this.readInt32(e)>>>0}readInt64(e){return BigInt.asIntN(64,BigInt(this.readUint32(e))+(BigInt(this.readUint32(e+4))<>8}writeUint16(e,t){this.bytes_[e]=t,this.bytes_[e+1]=t>>8}writeInt32(e,t){this.bytes_[e]=t,this.bytes_[e+1]=t>>8,this.bytes_[e+2]=t>>16,this.bytes_[e+3]=t>>24}writeUint32(e,t){this.bytes_[e]=t,this.bytes_[e+1]=t>>8,this.bytes_[e+2]=t>>16,this.bytes_[e+3]=t>>24}writeInt64(e,t){this.writeInt32(e,Number(BigInt.asIntN(32,t))),this.writeInt32(e+4,Number(BigInt.asIntN(32,t>>BigInt(32))))}writeUint64(e,t){this.writeUint32(e,Number(BigInt.asUintN(32,t))),this.writeUint32(e+4,Number(BigInt.asUintN(32,t>>BigInt(32))))}writeFloat32(e,t){mr[0]=t,this.writeInt32(e,Zt[0])}writeFloat64(e,t){gr[0]=t,this.writeInt32(e,Zt[Ri?0:1]),this.writeInt32(e+4,Zt[Ri?1:0])}getBufferIdentifier(){if(this.bytes_.length=0;n--)e.addOffset(t[n]);return e.endVector()}static startSamplersVector(e,t){e.startVector(4,t,4)}static endExpMaterial(e){return e.endObject()}static createExpMaterial(e,t,n,i,r,o,a){return s.startExpMaterial(e),s.addName(e,t),s.addShaderName(e,n),s.addDisableCulling(e,i),s.addTransparency(e,r),s.addDepthTest(e,o),s.addSamplers(e,a),s.endExpMaterial(e)}};var ds=class{bb=null;bb_pos=0;__init(e,t){return this.bb_pos=e,this.bb=t,this}index(){return this.bb.readUint8(this.bb_pos)}type(){return this.bb.readUint8(this.bb_pos+1)}usage(){return this.bb.readUint8(this.bb_pos+2)}count(){return this.bb.readUint8(this.bb_pos+3)}offset(){return this.bb.readUint8(this.bb_pos+4)}byteSize(){return this.bb.readUint8(this.bb_pos+5)}normalized(){return!!this.bb.readInt8(this.bb_pos+6)}static sizeOf(){return 7}static createExpVertexFormatElement(e,t,n,i,r,o,a,c){return e.prep(1,7),e.writeInt8(+!!c),e.writeInt8(a),e.writeInt8(o),e.writeInt8(r),e.writeInt8(i),e.writeInt8(n),e.writeInt8(t),e.offset()}};var ps=class s{bb=null;bb_pos=0;__init(e,t){return this.bb_pos=e,this.bb=t,this}static getRootAsExpVertexFormat(e,t){return(t||new s).__init(e.readInt32(e.position())+e.position(),e)}static getSizePrefixedRootAsExpVertexFormat(e,t){return e.setPosition(e.position()+4),(t||new s).__init(e.readInt32(e.position())+e.position(),e)}elements(e,t){let n=this.bb.__offset(this.bb_pos,4);return n?(t||new ds).__init(this.bb.__vector(this.bb_pos+n)+e*7,this.bb):null}elementsLength(){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.__vector_len(this.bb_pos+e):0}vertexSize(){let e=this.bb.__offset(this.bb_pos,6);return e?this.bb.readUint8(this.bb_pos+e):0}static startExpVertexFormat(e){e.startObject(2)}static addElements(e,t){e.addFieldOffset(0,t,0)}static startElementsVector(e,t){e.startVector(7,t,1)}static addVertexSize(e,t){e.addFieldInt8(1,t,0)}static endExpVertexFormat(e){return e.endObject()}static createExpVertexFormat(e,t,n){return s.startExpVertexFormat(e),s.addElements(e,t),s.addVertexSize(e,n),s.endExpVertexFormat(e)}};var ms=class s{bb=null;bb_pos=0;__init(e,t){return this.bb_pos=e,this.bb=t,this}static getRootAsExpMesh(e,t){return(t||new s).__init(e.readInt32(e.position())+e.position(),e)}static getSizePrefixedRootAsExpMesh(e,t){return e.setPosition(e.position()+4),(t||new s).__init(e.readInt32(e.position())+e.position(),e)}material(e){let t=this.bb.__offset(this.bb_pos,4);return t?(e||new fs).__init(this.bb.__indirect(this.bb_pos+t),this.bb):null}vertexFormat(e){let t=this.bb.__offset(this.bb_pos,6);return t?(e||new ps).__init(this.bb.__indirect(this.bb_pos+t),this.bb):null}primitiveType(){let e=this.bb.__offset(this.bb_pos,8);return e?this.bb.readUint8(this.bb_pos+e):0}indexBuffer(e){let t=this.bb.__offset(this.bb_pos,10);return t?this.bb.readUint8(this.bb.__vector(this.bb_pos+t)+e):0}indexBufferLength(){let e=this.bb.__offset(this.bb_pos,10);return e?this.bb.__vector_len(this.bb_pos+e):0}indexBufferArray(){let e=this.bb.__offset(this.bb_pos,10);return e?new Uint8Array(this.bb.bytes().buffer,this.bb.bytes().byteOffset+this.bb.__vector(this.bb_pos+e),this.bb.__vector_len(this.bb_pos+e)):null}indexType(){let e=this.bb.__offset(this.bb_pos,12);return e?this.bb.readUint8(this.bb_pos+e):0}indexCount(){let e=this.bb.__offset(this.bb_pos,14);return e?this.bb.readUint32(this.bb_pos+e):0}vertexBuffer(e){let t=this.bb.__offset(this.bb_pos,16);return t?this.bb.readUint8(this.bb.__vector(this.bb_pos+t)+e):0}vertexBufferLength(){let e=this.bb.__offset(this.bb_pos,16);return e?this.bb.__vector_len(this.bb_pos+e):0}vertexBufferArray(){let e=this.bb.__offset(this.bb_pos,16);return e?new Uint8Array(this.bb.bytes().buffer,this.bb.bytes().byteOffset+this.bb.__vector(this.bb_pos+e),this.bb.__vector_len(this.bb_pos+e)):null}static startExpMesh(e){e.startObject(7)}static addMaterial(e,t){e.addFieldOffset(0,t,0)}static addVertexFormat(e,t){e.addFieldOffset(1,t,0)}static addPrimitiveType(e,t){e.addFieldInt8(2,t,0)}static addIndexBuffer(e,t){e.addFieldOffset(3,t,0)}static createIndexBufferVector(e,t){e.startVector(1,t.length,1);for(let n=t.length-1;n>=0;n--)e.addInt8(t[n]);return e.endVector()}static startIndexBufferVector(e,t){e.startVector(1,t,1)}static addIndexType(e,t){e.addFieldInt8(4,t,0)}static addIndexCount(e,t){e.addFieldInt32(5,t,0)}static addVertexBuffer(e,t){e.addFieldOffset(6,t,0)}static createVertexBufferVector(e,t){e.startVector(1,t.length,1);for(let n=t.length-1;n>=0;n--)e.addInt8(t[n]);return e.endVector()}static startVertexBufferVector(e,t){e.startVector(1,t,1)}static endExpMesh(e){return e.endObject()}};var gs=class s{bb=null;bb_pos=0;__init(e,t){return this.bb_pos=e,this.bb=t,this}static getRootAsExpScene(e,t){return(t||new s).__init(e.readInt32(e.position())+e.position(),e)}static getSizePrefixedRootAsExpScene(e,t){return e.setPosition(e.position()+4),(t||new s).__init(e.readInt32(e.position())+e.position(),e)}camera(e){let t=this.bb.__offset(this.bb_pos,4);return t?(e||new hs).__init(this.bb_pos+t,this.bb):null}meshes(e,t){let n=this.bb.__offset(this.bb_pos,6);return n?(t||new ms).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+n)+e*4),this.bb):null}meshesLength(){let e=this.bb.__offset(this.bb_pos,6);return e?this.bb.__vector_len(this.bb_pos+e):0}animatedTextures(e,t){let n=this.bb.__offset(this.bb_pos,8);return n?(t||new cs).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+n)+e*4),this.bb):null}animatedTexturesLength(){let e=this.bb.__offset(this.bb_pos,8);return e?this.bb.__vector_len(this.bb_pos+e):0}static startExpScene(e){e.startObject(3)}static addCamera(e,t){e.addFieldStruct(0,t,0)}static addMeshes(e,t){e.addFieldOffset(1,t,0)}static createMeshesVector(e,t){e.startVector(4,t.length,4);for(let n=t.length-1;n>=0;n--)e.addOffset(t[n]);return e.endVector()}static startMeshesVector(e,t){e.startVector(4,t,4)}static addAnimatedTextures(e,t){e.addFieldOffset(2,t,0)}static createAnimatedTexturesVector(e,t){e.startVector(4,t.length,4);for(let n=t.length-1;n>=0;n--)e.addOffset(t[n]);return e.endVector()}static startAnimatedTexturesVector(e,t){e.startVector(4,t,4)}static endExpScene(e){return e.endObject()}static finishExpSceneBuffer(e,t){e.finish(t)}static finishSizePrefixedExpSceneBuffer(e,t){e.finish(t,void 0,!0)}static createExpScene(e,t,n,i){return s.startExpScene(e),s.addCamera(e,t),s.addMeshes(e,n),s.addAnimatedTextures(e,i),s.endExpScene(e)}};function Ga(s,e){return new e(s.buffer,s.byteOffset,s.byteLength/e.BYTES_PER_ELEMENT)}function Wa(s){let e=s.vertexBufferArray(),t=s.indexBufferArray();if(!e||!t){console.warn("Missing vertex or index buffer build-data");return}console.debug("Vertex buffer size: %d, Index buffer size: %d",e.byteLength,t.byteLength);let n=s.vertexFormat();if(!n){console.warn("Missing vertex format");return}console.debug("Vertex format, stride=%d",n.vertexSize());let i=new Mt;if(s.indexType()==0){let r=Ga(t,Uint32Array);i.setIndex(new Si(r,1,!1))}else if(s.indexType()==1){let r=Ga(t,Uint16Array);i.setIndex(new xn(r,1,!1))}else{console.warn("Unknown index type: %o",s.indexType());return}for(let r=0;r0)continue;let a;switch(o.usage()){case 0:a="position";break;case 1:a="normal";break;case 2:a="color";break;case 3:a="uv";break;default:console.warn("Unsupported vertex format attribute usage: %o",o.usage());continue}console.debug("Element %s, Count=%o, Offset=%o, Normalized=%o",a,o.count(),o.offset(),o.normalized());let c;switch(o.type()){case 0:c=Float32Array;break;case 1:c=Uint8Array;break;case 2:c=Int8Array;break;case 3:c=Uint16Array;break;case 4:c=Int16Array;break;case 5:c=Uint32Array;break;case 6:c=Int32Array;break;default:console.warn("Unsupported element type: %o",o.type());continue}let l=c.BYTES_PER_ELEMENT,h=Ga(e,c),f=new Qi(h,n.vertexSize()/l);i.setAttribute(a,new Ti(f,o.count(),o.offset()/l,o.normalized()))}return i}var fm={rendertype_solid:{lighting:"lightmap",vertexColor:!0,textured:!0,alphaTest:null},rendertype_cutout:{lighting:"lightmap",alphaTest:.1,vertexColor:!0,textured:!0},rendertype_cutout_mipped:{lighting:"lightmap",alphaTest:.5,vertexColor:!0,textured:!0},rendertype_translucent:{lighting:"lightmap",alphaTest:null,vertexColor:!0,textured:!0},rendertype_tripwire:{lighting:"lightmap",alphaTest:.1,vertexColor:!0,textured:!0},rendertype_entity_solid:{lighting:"diffuse",alphaTest:null,vertexColor:!0,textured:!0},rendertype_entity_cutout:{lighting:"diffuse",alphaTest:.1,vertexColor:!0,textured:!0},position_color:{lighting:"none",alphaTest:0,vertexColor:!0,textured:!1},rendertype_entity_cutout_no_cull:{lighting:"diffuse",alphaTest:.1,vertexColor:!0,textured:!0},rendertype_entity_translucent_cull:{lighting:"diffuse",alphaTest:.1,vertexColor:!0,textured:!0},rendertype_text:{lighting:"none",alphaTest:.1,vertexColor:!0,textured:!0}},Jl=fm;function dm(s,e){switch(s.transparency()){case 0:e.blending=qt;break;case 1:e.blending=Xs;break;case 2:e.blending=Ii,e.blendSrc=ss,e.blendDst=Vn;break;case 3:e.blending=Ii,e.blendSrc=hr,e.blendDst=Vn,e.blendSrcAlpha=cr,e.blendDstAlpha=Vn;break;case 4:e.blending=Ii,e.blendSrc=Na,e.blendDst=hr,e.blendSrcAlpha=Vn,e.blendDstAlpha=cr;break;case 5:e.blending=Ii,e.blendSrc=ss,e.blendDst=rs,e.blendSrcAlpha=Vn,e.blendDstAlpha=rs;break}}async function Xa(s,e,t){let n=[];for(let l=0;l"u"){let r=(await import("./decompressFallback-VGYIC7XH.js")).default,o=await r(e);return new Response(o)}let t=new DecompressionStream("gzip"),n=e.stream().pipeThrough(t);return new Response(n)}async function pm(s){let e=await s.blob();s=await qa(e);let t=await s.arrayBuffer();return console.debug("Loaded %s, %d byte compressed, %d byte uncompressed",s.url,e.size,t.byteLength),t}async function Ya(s,e,t){let n=await fetch(e,{signal:t});if(!n.ok)throw n;let i=await pm(n),r=new Uint8Array(i),o=new Li(r),a=new Dt,c=new Map,l=gs.getRootAsExpScene(o);for(let p=0;pR.status==="fulfilled"?new bt(R.value):null),S=c.get(g.textureId()??"")??[];if(!S.length)continue;let P=[];for(let R=0;Rh||i.y>h)&&(i.x>h&&(r.x=Math.floor(h/k.x),i.x=r.x*k.x,O.mapSize.x=r.x),i.y>h&&(r.y=Math.floor(h/k.y),i.y=r.y*k.y,O.mapSize.y=r.y)),O.map===null){let $=this.type!==Vi?{minFilter:st,magFilter:st}:{};O.map=new on(i.x,i.y,$),O.map.texture.name=U.name+".shadowMap",O.camera.updateProjectionMatrix()}s.setRenderTarget(O.map),s.clear();let te=O.getViewportCount();for(let $=0;$0||S.map&&S.alphaTest>0){let T=D.uuid,W=S.uuid,J=l[T];J===void 0&&(J={},l[T]=J);let U=J[W];U===void 0&&(U=D.clone(),J[W]=U),D=U}if(D.visible=S.visible,D.wireframe=S.wireframe,R===Vi?D.side=S.shadowSide!==null?S.shadowSide:S.side:D.side=S.shadowSide!==null?S.shadowSide:f[S.side],D.alphaMap=S.alphaMap,D.alphaTest=S.alphaTest,D.map=S.map,D.clipShadows=S.clipShadows,D.clippingPlanes=S.clippingPlanes,D.clipIntersection=S.clipIntersection,D.displacementMap=S.displacementMap,D.displacementScale=S.displacementScale,D.displacementBias=S.displacementBias,D.wireframeLinewidth=S.wireframeLinewidth,D.linewidth=S.linewidth,P.isPointLight===!0&&D.isMeshDistanceMaterial===!0){let T=s.properties.get(D);T.light=P}return D}function v(M,S,P,R,D){if(M.visible===!1)return;if(M.layers.test(S.layers)&&(M.isMesh||M.isLine||M.isPoints)&&(M.castShadow||M.receiveShadow&&D===Vi)&&(!M.frustumCulled||n.intersectsObject(M))){M.modelViewMatrix.multiplyMatrices(P.matrixWorldInverse,M.matrixWorld);let W=e.update(M),J=M.material;if(Array.isArray(J)){let U=W.groups;for(let O=0,k=U.length;O=1):Y.indexOf("OpenGL ES")!==-1&&($=parseFloat(/^OpenGL ES (\d)/.exec(Y)[1]),te=$>=2);let j=null,ne={},_e=s.getParameter(3088),ce=s.getParameter(2978),V=new rt().fromArray(_e),Z=new rt().fromArray(ce);function re(I,G,Q){let ue=new Uint8Array(4),ge=s.createTexture();s.bindTexture(I,ge),s.texParameteri(I,10241,9728),s.texParameteri(I,10240,9728);for(let Ve=0;Ve"u"?!1:/OculusBrowser/g.test(navigator.userAgent),g=new WeakMap,_,m=new WeakMap,u=!1;try{u=typeof OffscreenCanvas<"u"&&new OffscreenCanvas(1,1).getContext("2d")!==null}catch{}function w(E,y){return u?new OffscreenCanvas(E,y):Zs("canvas")}function v(E,y,z,K){let ee=1;if((E.width>K||E.height>K)&&(ee=K/Math.max(E.width,E.height)),ee<1||y===!0)if(typeof HTMLImageElement<"u"&&E instanceof HTMLImageElement||typeof HTMLCanvasElement<"u"&&E instanceof HTMLCanvasElement||typeof ImageBitmap<"u"&&E instanceof ImageBitmap){let ae=y?Dl:Math.floor,A=ae(ee*E.width),q=ae(ee*E.height);_===void 0&&(_=w(A,q));let F=z?w(A,q):_;return F.width=A,F.height=q,F.getContext("2d").drawImage(E,0,0,A,q),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+E.width+"x"+E.height+") to ("+A+"x"+q+")."),F}else return"data"in E&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+E.width+"x"+E.height+")."),E;return E}function M(E){return sa(E.width)&&sa(E.height)}function S(E){return a?!1:E.wrapS!==ut||E.wrapT!==ut||E.minFilter!==st&&E.minFilter!==yt}function P(E,y){return E.generateMipmaps&&y&&E.minFilter!==st&&E.minFilter!==yt}function R(E){s.generateMipmap(E)}function D(E,y,z,K,ee=!1){if(a===!1)return y;if(E!==null){if(s[E]!==void 0)return s[E];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+E+"'")}let ae=y;return y===6403&&(z===5126&&(ae=33326),z===5131&&(ae=33325),z===5121&&(ae=33321)),y===33319&&(z===5126&&(ae=33328),z===5131&&(ae=33327),z===5121&&(ae=33323)),y===6408&&(z===5126&&(ae=34836),z===5131&&(ae=34842),z===5121&&(ae=K===He&&ee===!1?35907:32856),z===32819&&(ae=32854),z===32820&&(ae=32855)),(ae===33325||ae===33326||ae===33327||ae===33328||ae===34842||ae===34836)&&e.get("EXT_color_buffer_float"),ae}function x(E,y,z){return P(E,z)===!0||E.isFramebufferTexture&&E.minFilter!==st&&E.minFilter!==yt?Math.log2(Math.max(y.width,y.height))+1:E.mipmaps!==void 0&&E.mipmaps.length>0?E.mipmaps.length:E.isCompressedTexture&&Array.isArray(E.image)?y.mipmaps.length:1}function T(E){return E===st||E===ro||E===br?9728:9729}function W(E){let y=E.target;y.removeEventListener("dispose",W),U(y),y.isVideoTexture&&g.delete(y)}function J(E){let y=E.target;y.removeEventListener("dispose",J),k(y)}function U(E){let y=n.get(E);if(y.__webglInit===void 0)return;let z=E.source,K=m.get(z);if(K){let ee=K[y.__cacheKey];ee.usedTimes--,ee.usedTimes===0&&O(E),Object.keys(K).length===0&&m.delete(z)}n.remove(E)}function O(E){let y=n.get(E);s.deleteTexture(y.__webglTexture);let z=E.source,K=m.get(z);delete K[y.__cacheKey],o.memory.textures--}function k(E){let y=E.texture,z=n.get(E),K=n.get(y);if(K.__webglTexture!==void 0&&(s.deleteTexture(K.__webglTexture),o.memory.textures--),E.depthTexture&&E.depthTexture.dispose(),E.isWebGLCubeRenderTarget)for(let ee=0;ee<6;ee++)s.deleteFramebuffer(z.__webglFramebuffer[ee]),z.__webglDepthbuffer&&s.deleteRenderbuffer(z.__webglDepthbuffer[ee]);else{if(s.deleteFramebuffer(z.__webglFramebuffer),z.__webglDepthbuffer&&s.deleteRenderbuffer(z.__webglDepthbuffer),z.__webglMultisampledFramebuffer&&s.deleteFramebuffer(z.__webglMultisampledFramebuffer),z.__webglColorRenderbuffer)for(let ee=0;ee=c&&console.warn("THREE.WebGLTextures: Trying to use "+E+" texture units while this GPU supports only "+c),te+=1,E}function j(E){let y=[];return y.push(E.wrapS),y.push(E.wrapT),y.push(E.wrapR||0),y.push(E.magFilter),y.push(E.minFilter),y.push(E.anisotropy),y.push(E.internalFormat),y.push(E.format),y.push(E.type),y.push(E.generateMipmaps),y.push(E.premultiplyAlpha),y.push(E.flipY),y.push(E.unpackAlignment),y.push(E.encoding),y.join()}function ne(E,y){let z=n.get(E);if(E.isVideoTexture&&ke(E),E.isRenderTargetTexture===!1&&E.version>0&&z.__version!==E.version){let K=E.image;if(K===null)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else if(K.complete===!1)console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete");else{Se(z,E,y);return}}t.bindTexture(3553,z.__webglTexture,33984+y)}function _e(E,y){let z=n.get(E);if(E.version>0&&z.__version!==E.version){Se(z,E,y);return}t.bindTexture(35866,z.__webglTexture,33984+y)}function ce(E,y){let z=n.get(E);if(E.version>0&&z.__version!==E.version){Se(z,E,y);return}t.bindTexture(32879,z.__webglTexture,33984+y)}function V(E,y){let z=n.get(E);if(E.version>0&&z.__version!==E.version){be(z,E,y);return}t.bindTexture(34067,z.__webglTexture,33984+y)}let Z={[yi]:10497,[ut]:33071,[ta]:33648},re={[st]:9728,[ro]:9984,[br]:9986,[yt]:9729,[Ic]:9985,[qi]:9987};function le(E,y,z){if(z?(s.texParameteri(E,10242,Z[y.wrapS]),s.texParameteri(E,10243,Z[y.wrapT]),(E===32879||E===35866)&&s.texParameteri(E,32882,Z[y.wrapR]),s.texParameteri(E,10240,re[y.magFilter]),s.texParameteri(E,10241,re[y.minFilter])):(s.texParameteri(E,10242,33071),s.texParameteri(E,10243,33071),(E===32879||E===35866)&&s.texParameteri(E,32882,33071),(y.wrapS!==ut||y.wrapT!==ut)&&console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping."),s.texParameteri(E,10240,T(y.magFilter)),s.texParameteri(E,10241,T(y.minFilter)),y.minFilter!==st&&y.minFilter!==yt&&console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.")),e.has("EXT_texture_filter_anisotropic")===!0){let K=e.get("EXT_texture_filter_anisotropic");if(y.magFilter===st||y.minFilter!==br&&y.minFilter!==qi||y.type===In&&e.has("OES_texture_float_linear")===!1||a===!1&&y.type===Yi&&e.has("OES_texture_half_float_linear")===!1)return;(y.anisotropy>1||n.get(y).__currentAnisotropy)&&(s.texParameterf(E,K.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(y.anisotropy,i.getMaxAnisotropy())),n.get(y).__currentAnisotropy=y.anisotropy)}}function B(E,y){let z=!1;E.__webglInit===void 0&&(E.__webglInit=!0,y.addEventListener("dispose",W));let K=y.source,ee=m.get(K);ee===void 0&&(ee={},m.set(K,ee));let ae=j(y);if(ae!==E.__cacheKey){ee[ae]===void 0&&(ee[ae]={texture:s.createTexture(),usedTimes:0},o.memory.textures++,z=!0),ee[ae].usedTimes++;let A=ee[E.__cacheKey];A!==void 0&&(ee[E.__cacheKey].usedTimes--,A.usedTimes===0&&O(y)),E.__cacheKey=ae,E.__webglTexture=ee[ae].texture}return z}function Se(E,y,z){let K=3553;(y.isDataArrayTexture||y.isCompressedArrayTexture)&&(K=35866),y.isData3DTexture&&(K=32879);let ee=B(E,y),ae=y.source;t.bindTexture(K,E.__webglTexture,33984+z);let A=n.get(ae);if(ae.version!==A.__version||ee===!0){t.activeTexture(33984+z),s.pixelStorei(37440,y.flipY),s.pixelStorei(37441,y.premultiplyAlpha),s.pixelStorei(3317,y.unpackAlignment),s.pixelStorei(37443,0);let q=S(y)&&M(y.image)===!1,F=v(y.image,q,!1,h);F=at(y,F);let oe=M(F)||a,fe=r.convert(y.format,y.encoding),pe=r.convert(y.type),he=D(y.internalFormat,fe,pe,y.encoding,y.isVideoTexture);le(K,y,oe);let de,we=y.mipmaps,Ie=a&&y.isVideoTexture!==!0,Ze=A.__version===void 0||ee===!0,I=x(y,F,oe);if(y.isDepthTexture)he=6402,a?y.type===In?he=36012:y.type===Cn?he=33190:y.type===pi?he=35056:he=33189:y.type===In&&console.error("WebGLRenderer: Floating point depth texture requires WebGL2."),y.format===Rn&&he===6402&&y.type!==Pl&&y.type!==Cn&&(console.warn("THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture."),y.type=Cn,pe=r.convert(y.type)),y.format===vi&&he===6402&&(he=34041,y.type!==pi&&(console.warn("THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture."),y.type=pi,pe=r.convert(y.type))),Ze&&(Ie?t.texStorage2D(3553,1,he,F.width,F.height):t.texImage2D(3553,0,he,F.width,F.height,0,fe,pe,null));else if(y.isDataTexture)if(we.length>0&&oe){Ie&&Ze&&t.texStorage2D(3553,I,he,we[0].width,we[0].height);for(let G=0,Q=we.length;G>=1,Q>>=1}}else if(we.length>0&&oe){Ie&&Ze&&t.texStorage2D(3553,I,he,we[0].width,we[0].height);for(let G=0,Q=we.length;G0&&Ze++,t.texStorage2D(34067,Ze,de,F[0].width,F[0].height));for(let G=0;G<6;G++)if(q){we?t.texSubImage2D(34069+G,0,0,0,F[G].width,F[G].height,pe,he,F[G].data):t.texImage2D(34069+G,0,de,F[G].width,F[G].height,0,pe,he,F[G].data);for(let Q=0;Q=34069&&ee<=34074)&&s.framebufferTexture2D(36160,K,ee,n.get(z).__webglTexture,0),t.bindFramebuffer(36160,null)}function ye(E,y,z){if(s.bindRenderbuffer(36161,E),y.depthBuffer&&!y.stencilBuffer){let K=33189;if(z||Oe(y)){let ee=y.depthTexture;ee&&ee.isDepthTexture&&(ee.type===In?K=36012:ee.type===Cn&&(K=33190));let ae=Ye(y);Oe(y)?d.renderbufferStorageMultisampleEXT(36161,ae,K,y.width,y.height):s.renderbufferStorageMultisample(36161,ae,K,y.width,y.height)}else s.renderbufferStorage(36161,K,y.width,y.height);s.framebufferRenderbuffer(36160,36096,36161,E)}else if(y.depthBuffer&&y.stencilBuffer){let K=Ye(y);z&&Oe(y)===!1?s.renderbufferStorageMultisample(36161,K,35056,y.width,y.height):Oe(y)?d.renderbufferStorageMultisampleEXT(36161,K,35056,y.width,y.height):s.renderbufferStorage(36161,34041,y.width,y.height),s.framebufferRenderbuffer(36160,33306,36161,E)}else{let K=y.isWebGLMultipleRenderTargets===!0?y.texture:[y.texture];for(let ee=0;ee0&&Oe(E)===!1){let q=ae?y:[y];z.__webglMultisampledFramebuffer=s.createFramebuffer(),z.__webglColorRenderbuffer=[],t.bindFramebuffer(36160,z.__webglMultisampledFramebuffer);for(let F=0;F0&&Oe(E)===!1){let y=E.isWebGLMultipleRenderTargets?E.texture:[E.texture],z=E.width,K=E.height,ee=16384,ae=[],A=E.stencilBuffer?33306:36096,q=n.get(E),F=E.isWebGLMultipleRenderTargets===!0;if(F)for(let oe=0;oe0&&e.has("WEBGL_multisampled_render_to_texture")===!0&&y.__useRenderToTexture!==!1}function ke(E){let y=o.render.frame;g.get(E)!==y&&(g.set(E,y),E.update())}function at(E,y){let z=E.encoding,K=E.format,ee=E.type;return E.isCompressedTexture===!0||E.isVideoTexture===!0||E.format===ia||z!==Dn&&(z===He?a===!1?e.has("EXT_sRGB")===!0&&K===Bt?(E.format=ia,E.minFilter=yt,E.generateMipmaps=!1):y=Js.sRGBToLinear(y):(K!==Bt||ee!==Ln)&&console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture encoding:",z)),y}this.allocateTextureUnit=Y,this.resetTextureUnits=$,this.setTexture2D=ne,this.setTexture2DArray=_e,this.setTexture3D=ce,this.setTextureCube=V,this.rebindTextures=Te,this.setupRenderTarget=qe,this.updateRenderTargetMipmap=We,this.updateMultisampleRenderTarget=$e,this.setupDepthRenderbuffer=me,this.setupFrameBufferTexture=ie,this.useMultisampledRTT=Oe}function Yp(s,e,t){let n=t.isWebGL2;function i(r,o=null){let a;if(r===Ln)return 5121;if(r===Dc)return 32819;if(r===Uc)return 32820;if(r===Pc)return 5120;if(r===Rc)return 5122;if(r===Pl)return 5123;if(r===Lc)return 5124;if(r===Cn)return 5125;if(r===In)return 5126;if(r===Yi)return n?5131:(a=e.get("OES_texture_half_float"),a!==null?a.HALF_FLOAT_OES:null);if(r===Nc)return 6406;if(r===Bt)return 6408;if(r===Oc)return 6409;if(r===Fc)return 6410;if(r===Rn)return 6402;if(r===vi)return 34041;if(r===ia)return a=e.get("EXT_sRGB"),a!==null?a.SRGB_ALPHA_EXT:null;if(r===Bc)return 6403;if(r===zc)return 36244;if(r===kc)return 33319;if(r===Vc)return 33320;if(r===Hc)return 36249;if(r===Mr||r===Sr||r===wr||r===Er)if(o===He)if(a=e.get("WEBGL_compressed_texture_s3tc_srgb"),a!==null){if(r===Mr)return a.COMPRESSED_SRGB_S3TC_DXT1_EXT;if(r===Sr)return a.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;if(r===wr)return a.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;if(r===Er)return a.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}else return null;else if(a=e.get("WEBGL_compressed_texture_s3tc"),a!==null){if(r===Mr)return a.COMPRESSED_RGB_S3TC_DXT1_EXT;if(r===Sr)return a.COMPRESSED_RGBA_S3TC_DXT1_EXT;if(r===wr)return a.COMPRESSED_RGBA_S3TC_DXT3_EXT;if(r===Er)return a.COMPRESSED_RGBA_S3TC_DXT5_EXT}else return null;if(r===ao||r===oo||r===lo||r===co)if(a=e.get("WEBGL_compressed_texture_pvrtc"),a!==null){if(r===ao)return a.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;if(r===oo)return a.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;if(r===lo)return a.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;if(r===co)return a.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG}else return null;if(r===Gc)return a=e.get("WEBGL_compressed_texture_etc1"),a!==null?a.COMPRESSED_RGB_ETC1_WEBGL:null;if(r===ho||r===uo)if(a=e.get("WEBGL_compressed_texture_etc"),a!==null){if(r===ho)return o===He?a.COMPRESSED_SRGB8_ETC2:a.COMPRESSED_RGB8_ETC2;if(r===uo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:a.COMPRESSED_RGBA8_ETC2_EAC}else return null;if(r===fo||r===po||r===mo||r===go||r===_o||r===xo||r===yo||r===vo||r===bo||r===Mo||r===So||r===wo||r===Eo||r===To)if(a=e.get("WEBGL_compressed_texture_astc"),a!==null){if(r===fo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:a.COMPRESSED_RGBA_ASTC_4x4_KHR;if(r===po)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:a.COMPRESSED_RGBA_ASTC_5x4_KHR;if(r===mo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:a.COMPRESSED_RGBA_ASTC_5x5_KHR;if(r===go)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:a.COMPRESSED_RGBA_ASTC_6x5_KHR;if(r===_o)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:a.COMPRESSED_RGBA_ASTC_6x6_KHR;if(r===xo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:a.COMPRESSED_RGBA_ASTC_8x5_KHR;if(r===yo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:a.COMPRESSED_RGBA_ASTC_8x6_KHR;if(r===vo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:a.COMPRESSED_RGBA_ASTC_8x8_KHR;if(r===bo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:a.COMPRESSED_RGBA_ASTC_10x5_KHR;if(r===Mo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:a.COMPRESSED_RGBA_ASTC_10x6_KHR;if(r===So)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:a.COMPRESSED_RGBA_ASTC_10x8_KHR;if(r===wo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:a.COMPRESSED_RGBA_ASTC_10x10_KHR;if(r===Eo)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:a.COMPRESSED_RGBA_ASTC_12x10_KHR;if(r===To)return o===He?a.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:a.COMPRESSED_RGBA_ASTC_12x12_KHR}else return null;if(r===Tr)if(a=e.get("EXT_texture_compression_bptc"),a!==null){if(r===Tr)return o===He?a.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT:a.COMPRESSED_RGBA_BPTC_UNORM_EXT}else return null;if(r===Wc||r===Ao||r===Co||r===Io)if(a=e.get("EXT_texture_compression_rgtc"),a!==null){if(r===Tr)return a.COMPRESSED_RED_RGTC1_EXT;if(r===Ao)return a.COMPRESSED_SIGNED_RED_RGTC1_EXT;if(r===Co)return a.COMPRESSED_RED_GREEN_RGTC2_EXT;if(r===Io)return a.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT}else return null;return r===pi?n?34042:(a=e.get("WEBGL_depth_texture"),a!==null?a.UNSIGNED_INT_24_8_WEBGL:null):s[r]!==void 0?s[r]:null}return{convert:i}}var _a=class extends Ct{constructor(e=[]){super(),this.isArrayCamera=!0,this.cameras=e}},Dt=class extends pt{constructor(){super(),this.isGroup=!0,this.type="Group"}},Zp={type:"move"},Xi=class{constructor(){this._targetRay=null,this._grip=null,this._hand=null}getHandSpace(){return this._hand===null&&(this._hand=new Dt,this._hand.matrixAutoUpdate=!1,this._hand.visible=!1,this._hand.joints={},this._hand.inputState={pinching:!1}),this._hand}getTargetRaySpace(){return this._targetRay===null&&(this._targetRay=new Dt,this._targetRay.matrixAutoUpdate=!1,this._targetRay.visible=!1,this._targetRay.hasLinearVelocity=!1,this._targetRay.linearVelocity=new C,this._targetRay.hasAngularVelocity=!1,this._targetRay.angularVelocity=new C),this._targetRay}getGripSpace(){return this._grip===null&&(this._grip=new Dt,this._grip.matrixAutoUpdate=!1,this._grip.visible=!1,this._grip.hasLinearVelocity=!1,this._grip.linearVelocity=new C,this._grip.hasAngularVelocity=!1,this._grip.angularVelocity=new C),this._grip}dispatchEvent(e){return this._targetRay!==null&&this._targetRay.dispatchEvent(e),this._grip!==null&&this._grip.dispatchEvent(e),this._hand!==null&&this._hand.dispatchEvent(e),this}connect(e){if(e&&e.hand){let t=this._hand;if(t)for(let n of e.hand.values())this._getHandJoint(t,n)}return this.dispatchEvent({type:"connected",data:e}),this}disconnect(e){return this.dispatchEvent({type:"disconnected",data:e}),this._targetRay!==null&&(this._targetRay.visible=!1),this._grip!==null&&(this._grip.visible=!1),this._hand!==null&&(this._hand.visible=!1),this}update(e,t,n){let i=null,r=null,o=null,a=this._targetRay,c=this._grip,l=this._hand;if(e&&t.session.visibilityState!=="visible-blurred"){if(l&&e.hand){o=!0;for(let _ of e.hand.values()){let m=t.getJointPose(_,n),u=this._getHandJoint(l,_);m!==null&&(u.matrix.fromArray(m.transform.matrix),u.matrix.decompose(u.position,u.rotation,u.scale),u.jointRadius=m.radius),u.visible=m!==null}let h=l.joints["index-finger-tip"],f=l.joints["thumb-tip"],d=h.position.distanceTo(f.position),p=.02,g=.005;l.inputState.pinching&&d>p+g?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:e.handedness,target:this})):!l.inputState.pinching&&d<=p-g&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:e.handedness,target:this}))}else c!==null&&e.gripSpace&&(r=t.getPose(e.gripSpace,n),r!==null&&(c.matrix.fromArray(r.transform.matrix),c.matrix.decompose(c.position,c.rotation,c.scale),r.linearVelocity?(c.hasLinearVelocity=!0,c.linearVelocity.copy(r.linearVelocity)):c.hasLinearVelocity=!1,r.angularVelocity?(c.hasAngularVelocity=!0,c.angularVelocity.copy(r.angularVelocity)):c.hasAngularVelocity=!1));a!==null&&(i=t.getPose(e.targetRaySpace,n),i===null&&r!==null&&(i=r),i!==null&&(a.matrix.fromArray(i.transform.matrix),a.matrix.decompose(a.position,a.rotation,a.scale),i.linearVelocity?(a.hasLinearVelocity=!0,a.linearVelocity.copy(i.linearVelocity)):a.hasLinearVelocity=!1,i.angularVelocity?(a.hasAngularVelocity=!0,a.angularVelocity.copy(i.angularVelocity)):a.hasAngularVelocity=!1,this.dispatchEvent(Zp)))}return a!==null&&(a.visible=i!==null),c!==null&&(c.visible=r!==null),l!==null&&(l.visible=o!==null),this}_getHandJoint(e,t){if(e.joints[t.jointName]===void 0){let n=new Dt;n.matrixAutoUpdate=!1,n.visible=!1,e.joints[t.jointName]=n,e.add(n)}return e.joints[t.jointName]}},xa=class extends bt{constructor(e,t,n,i,r,o,a,c,l,h){if(h=h!==void 0?h:Rn,h!==Rn&&h!==vi)throw new Error("DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat");n===void 0&&h===Rn&&(n=Cn),n===void 0&&h===vi&&(n=pi),super(null,i,r,o,a,c,h,n,l),this.isDepthTexture=!0,this.image={width:e,height:t},this.magFilter=a!==void 0?a:st,this.minFilter=c!==void 0?c:st,this.flipY=!1,this.generateMipmaps=!1}},ya=class extends Yt{constructor(e,t){super();let n=this,i=null,r=1,o=null,a="local-floor",c=1,l=null,h=null,f=null,d=null,p=null,g=null,_=t.getContextAttributes(),m=null,u=null,w=[],v=[],M=new Set,S=new Map,P=new Ct;P.layers.enable(1),P.viewport=new rt;let R=new Ct;R.layers.enable(2),R.viewport=new rt;let D=[P,R],x=new _a;x.layers.enable(1),x.layers.enable(2);let T=null,W=null;this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getController=function(V){let Z=w[V];return Z===void 0&&(Z=new Xi,w[V]=Z),Z.getTargetRaySpace()},this.getControllerGrip=function(V){let Z=w[V];return Z===void 0&&(Z=new Xi,w[V]=Z),Z.getGripSpace()},this.getHand=function(V){let Z=w[V];return Z===void 0&&(Z=new Xi,w[V]=Z),Z.getHandSpace()};function J(V){let Z=v.indexOf(V.inputSource);if(Z===-1)return;let re=w[Z];re!==void 0&&re.dispatchEvent({type:V.type,data:V.inputSource})}function U(){i.removeEventListener("select",J),i.removeEventListener("selectstart",J),i.removeEventListener("selectend",J),i.removeEventListener("squeeze",J),i.removeEventListener("squeezestart",J),i.removeEventListener("squeezeend",J),i.removeEventListener("end",U),i.removeEventListener("inputsourceschange",O);for(let V=0;V=0&&(v[le]=null,w[le].disconnect(re))}for(let Z=0;Z=v.length){v.push(re),le=Se;break}else if(v[Se]===null){v[Se]=re,le=Se;break}if(le===-1)break}let B=w[le];B&&B.connect(re)}}let k=new C,te=new C;function $(V,Z,re){k.setFromMatrixPosition(Z.matrixWorld),te.setFromMatrixPosition(re.matrixWorld);let le=k.distanceTo(te),B=Z.projectionMatrix.elements,Se=re.projectionMatrix.elements,be=B[14]/(B[10]-1),ie=B[14]/(B[10]+1),ye=(B[9]+1)/B[5],Fe=(B[9]-1)/B[5],me=(B[8]-1)/B[0],Te=(Se[8]+1)/Se[0],qe=be*me,We=be*Te,$e=le/(-me+Te),Ye=$e*-me;Z.matrixWorld.decompose(V.position,V.quaternion,V.scale),V.translateX(Ye),V.translateZ($e),V.matrixWorld.compose(V.position,V.quaternion,V.scale),V.matrixWorldInverse.copy(V.matrixWorld).invert();let Oe=be+$e,ke=ie+$e,at=qe-Ye,E=We+(le-Ye),y=ye*ie/ke*Oe,z=Fe*ie/ke*Oe;V.projectionMatrix.makePerspective(at,E,y,z,Oe,ke),V.projectionMatrixInverse.copy(V.projectionMatrix).invert()}function Y(V,Z){Z===null?V.matrixWorld.copy(V.matrix):V.matrixWorld.multiplyMatrices(Z.matrixWorld,V.matrix),V.matrixWorldInverse.copy(V.matrixWorld).invert()}this.updateCamera=function(V){if(i===null)return;x.near=R.near=P.near=V.near,x.far=R.far=P.far=V.far,(T!==x.near||W!==x.far)&&(i.updateRenderState({depthNear:x.near,depthFar:x.far}),T=x.near,W=x.far);let Z=V.parent,re=x.cameras;Y(x,Z);for(let le=0;leB&&(S.set(le,le.lastChangedTime),n.dispatchEvent({type:"planechanged",data:le}))}}g=null}let ce=new Fl;ce.setAnimationLoop(_e),this.setAnimationLoop=function(V){ne=V},this.dispose=function(){}}};function Jp(s,e){function t(m,u){m.matrixAutoUpdate===!0&&m.updateMatrix(),u.value.copy(m.matrix)}function n(m,u){u.color.getRGB(m.fogColor.value,Ol(s)),u.isFog?(m.fogNear.value=u.near,m.fogFar.value=u.far):u.isFogExp2&&(m.fogDensity.value=u.density)}function i(m,u,w,v,M){u.isMeshBasicMaterial||u.isMeshLambertMaterial?r(m,u):u.isMeshToonMaterial?(r(m,u),f(m,u)):u.isMeshPhongMaterial?(r(m,u),h(m,u)):u.isMeshStandardMaterial?(r(m,u),d(m,u),u.isMeshPhysicalMaterial&&p(m,u,M)):u.isMeshMatcapMaterial?(r(m,u),g(m,u)):u.isMeshDepthMaterial?r(m,u):u.isMeshDistanceMaterial?(r(m,u),_(m,u)):u.isMeshNormalMaterial?r(m,u):u.isLineBasicMaterial?(o(m,u),u.isLineDashedMaterial&&a(m,u)):u.isPointsMaterial?c(m,u,w,v):u.isSpriteMaterial?l(m,u):u.isShadowMaterial?(m.color.value.copy(u.color),m.opacity.value=u.opacity):u.isShaderMaterial&&(u.uniformsNeedUpdate=!1)}function r(m,u){m.opacity.value=u.opacity,u.color&&m.diffuse.value.copy(u.color),u.emissive&&m.emissive.value.copy(u.emissive).multiplyScalar(u.emissiveIntensity),u.map&&(m.map.value=u.map,t(u.map,m.mapTransform)),u.alphaMap&&(m.alphaMap.value=u.alphaMap,t(u.alphaMap,m.alphaMapTransform)),u.bumpMap&&(m.bumpMap.value=u.bumpMap,t(u.bumpMap,m.bumpMapTransform),m.bumpScale.value=u.bumpScale,u.side===St&&(m.bumpScale.value*=-1)),u.normalMap&&(m.normalMap.value=u.normalMap,t(u.normalMap,m.normalMapTransform),m.normalScale.value.copy(u.normalScale),u.side===St&&m.normalScale.value.negate()),u.displacementMap&&(m.displacementMap.value=u.displacementMap,t(u.displacementMap,m.displacementMapTransform),m.displacementScale.value=u.displacementScale,m.displacementBias.value=u.displacementBias),u.emissiveMap&&(m.emissiveMap.value=u.emissiveMap,t(u.emissiveMap,m.emissiveMapTransform)),u.specularMap&&(m.specularMap.value=u.specularMap,t(u.specularMap,m.specularMapTransform)),u.alphaTest>0&&(m.alphaTest.value=u.alphaTest);let w=e.get(u).envMap;if(w&&(m.envMap.value=w,m.flipEnvMap.value=w.isCubeTexture&&w.isRenderTargetTexture===!1?-1:1,m.reflectivity.value=u.reflectivity,m.ior.value=u.ior,m.refractionRatio.value=u.refractionRatio),u.lightMap){m.lightMap.value=u.lightMap;let v=s.useLegacyLights===!0?Math.PI:1;m.lightMapIntensity.value=u.lightMapIntensity*v,t(u.lightMap,m.lightMapTransform)}u.aoMap&&(m.aoMap.value=u.aoMap,m.aoMapIntensity.value=u.aoMapIntensity,t(u.aoMap,m.aoMapTransform))}function o(m,u){m.diffuse.value.copy(u.color),m.opacity.value=u.opacity,u.map&&(m.map.value=u.map,t(u.map,m.mapTransform))}function a(m,u){m.dashSize.value=u.dashSize,m.totalSize.value=u.dashSize+u.gapSize,m.scale.value=u.scale}function c(m,u,w,v){m.diffuse.value.copy(u.color),m.opacity.value=u.opacity,m.size.value=u.size*w,m.scale.value=v*.5,u.map&&(m.map.value=u.map,t(u.map,m.uvTransform)),u.alphaMap&&(m.alphaMap.value=u.alphaMap),u.alphaTest>0&&(m.alphaTest.value=u.alphaTest)}function l(m,u){m.diffuse.value.copy(u.color),m.opacity.value=u.opacity,m.rotation.value=u.rotation,u.map&&(m.map.value=u.map,t(u.map,m.mapTransform)),u.alphaMap&&(m.alphaMap.value=u.alphaMap),u.alphaTest>0&&(m.alphaTest.value=u.alphaTest)}function h(m,u){m.specular.value.copy(u.specular),m.shininess.value=Math.max(u.shininess,1e-4)}function f(m,u){u.gradientMap&&(m.gradientMap.value=u.gradientMap)}function d(m,u){m.metalness.value=u.metalness,u.metalnessMap&&(m.metalnessMap.value=u.metalnessMap,t(u.metalnessMap,m.metalnessMapTransform)),m.roughness.value=u.roughness,u.roughnessMap&&(m.roughnessMap.value=u.roughnessMap,t(u.roughnessMap,m.roughnessMapTransform)),e.get(u).envMap&&(m.envMapIntensity.value=u.envMapIntensity)}function p(m,u,w){m.ior.value=u.ior,u.sheen>0&&(m.sheenColor.value.copy(u.sheenColor).multiplyScalar(u.sheen),m.sheenRoughness.value=u.sheenRoughness,u.sheenColorMap&&(m.sheenColorMap.value=u.sheenColorMap,t(u.sheenColorMap,m.sheenColorMapTransform)),u.sheenRoughnessMap&&(m.sheenRoughnessMap.value=u.sheenRoughnessMap,t(u.sheenRoughnessMap,m.sheenRoughnessMapTransform))),u.clearcoat>0&&(m.clearcoat.value=u.clearcoat,m.clearcoatRoughness.value=u.clearcoatRoughness,u.clearcoatMap&&(m.clearcoatMap.value=u.clearcoatMap,t(u.clearcoatMap,m.clearcoatMapTransform)),u.clearcoatRoughnessMap&&(m.clearcoatRoughnessMap.value=u.clearcoatRoughnessMap,t(u.clearcoatRoughnessMap,m.clearcoatRoughnessMapTransform)),u.clearcoatNormalMap&&(m.clearcoatNormalMap.value=u.clearcoatNormalMap,t(u.clearcoatNormalMap,m.clearcoatNormalMapTransform),m.clearcoatNormalScale.value.copy(u.clearcoatNormalScale),u.side===St&&m.clearcoatNormalScale.value.negate())),u.iridescence>0&&(m.iridescence.value=u.iridescence,m.iridescenceIOR.value=u.iridescenceIOR,m.iridescenceThicknessMinimum.value=u.iridescenceThicknessRange[0],m.iridescenceThicknessMaximum.value=u.iridescenceThicknessRange[1],u.iridescenceMap&&(m.iridescenceMap.value=u.iridescenceMap,t(u.iridescenceMap,m.iridescenceMapTransform)),u.iridescenceThicknessMap&&(m.iridescenceThicknessMap.value=u.iridescenceThicknessMap,t(u.iridescenceThicknessMap,m.iridescenceThicknessMapTransform))),u.transmission>0&&(m.transmission.value=u.transmission,m.transmissionSamplerMap.value=w.texture,m.transmissionSamplerSize.value.set(w.width,w.height),u.transmissionMap&&(m.transmissionMap.value=u.transmissionMap,t(u.transmissionMap,m.transmissionMapTransform)),m.thickness.value=u.thickness,u.thicknessMap&&(m.thicknessMap.value=u.thicknessMap,t(u.thicknessMap,m.thicknessMapTransform)),m.attenuationDistance.value=u.attenuationDistance,m.attenuationColor.value.copy(u.attenuationColor)),m.specularIntensity.value=u.specularIntensity,m.specularColor.value.copy(u.specularColor),u.specularColorMap&&(m.specularColorMap.value=u.specularColorMap,t(u.specularColorMap,m.specularColorMapTransform)),u.specularIntensityMap&&(m.specularIntensityMap.value=u.specularIntensityMap,t(u.specularIntensityMap,m.specularIntensityMapTransform))}function g(m,u){u.matcap&&(m.matcap.value=u.matcap)}function _(m,u){let w=e.get(u).light;m.referencePosition.value.setFromMatrixPosition(w.matrixWorld),m.nearDistance.value=w.shadow.camera.near,m.farDistance.value=w.shadow.camera.far}return{refreshFogUniforms:n,refreshMaterialUniforms:i}}function $p(s,e,t,n){let i={},r={},o=[],a=t.isWebGL2?s.getParameter(35375):0;function c(w,v){let M=v.program;n.uniformBlockBinding(w,M)}function l(w,v){let M=i[w.id];M===void 0&&(g(w),M=h(w),i[w.id]=M,w.addEventListener("dispose",m));let S=v.program;n.updateUBOMapping(w,S);let P=e.render.frame;r[w.id]!==P&&(d(w),r[w.id]=P)}function h(w){let v=f();w.__bindingPointIndex=v;let M=s.createBuffer(),S=w.__size,P=w.usage;return s.bindBuffer(35345,M),s.bufferData(35345,S,P),s.bindBuffer(35345,null),s.bindBufferBase(35345,v,M),M}function f(){for(let w=0;w0){P=M%S;let J=S-P;P!==0&&J-T.boundary<0&&(M+=S-P,x.__offset=M)}M+=T.storage}return P=M%S,P>0&&(M+=S-P),w.__size=M,w.__cache={},this}function _(w){let v={boundary:0,storage:0};return typeof w=="number"?(v.boundary=4,v.storage=4):w.isVector2?(v.boundary=8,v.storage=8):w.isVector3||w.isColor?(v.boundary=16,v.storage=12):w.isVector4?(v.boundary=16,v.storage=16):w.isMatrix3?(v.boundary=48,v.storage=48):w.isMatrix4?(v.boundary=64,v.storage=64):w.isTexture?console.warn("THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group."):console.warn("THREE.WebGLRenderer: Unsupported uniform value type.",w),v}function m(w){let v=w.target;v.removeEventListener("dispose",m);let M=o.indexOf(v.__bindingPointIndex);o.splice(M,1),s.deleteBuffer(i[v.id]),delete i[v.id],delete r[v.id]}function u(){for(let w in i)s.deleteBuffer(i[w]);o=[],i={},r={}}return{bind:c,update:l,dispose:u}}function jp(){let s=Zs("canvas");return s.style.display="block",s}var Ki=class{constructor(e={}){let{canvas:t=jp(),context:n=null,depth:i=!0,stencil:r=!0,alpha:o=!1,antialias:a=!1,premultipliedAlpha:c=!0,preserveDrawingBuffer:l=!1,powerPreference:h="default",failIfMajorPerformanceCaveat:f=!1}=e;this.isWebGLRenderer=!0;let d;n!==null?d=n.getContextAttributes().alpha:d=o;let p=null,g=null,_=[],m=[];this.domElement=t,this.debug={checkShaderErrors:!0,onShaderError:null},this.autoClear=!0,this.autoClearColor=!0,this.autoClearDepth=!0,this.autoClearStencil=!0,this.sortObjects=!0,this.clippingPlanes=[],this.localClippingEnabled=!1,this.outputEncoding=Dn,this.useLegacyLights=!0,this.toneMapping=rn,this.toneMappingExposure=1;let u=this,w=!1,v=0,M=0,S=null,P=-1,R=null,D=new rt,x=new rt,T=null,W=t.width,J=t.height,U=1,O=null,k=null,te=new rt(0,0,W,J),$=new rt(0,0,W,J),Y=!1,j=new ji,ne=!1,_e=!1,ce=null,V=new Qe,Z=new C,re={background:null,fog:null,environment:null,overrideMaterial:null,isScene:!0};function le(){return S===null?U:1}let B=n;function Se(b,N){for(let H=0;H0?g=m[m.length-1]:g=null,_.pop(),_.length>0?p=_[_.length-1]:p=null};function ot(b,N,H,L){if(b.visible===!1)return;if(b.layers.test(N.layers)){if(b.isGroup)H=b.renderOrder;else if(b.isLOD)b.autoUpdate===!0&&b.update(N);else if(b.isLight)g.pushLight(b),b.castShadow&&g.pushShadow(b);else if(b.isSprite){if(!b.frustumCulled||j.intersectsSprite(b)){L&&Z.setFromMatrixPosition(b.matrixWorld).applyMatrix4(V);let Me=Oe.update(b),Ee=b.material;Ee.visible&&p.push(b,Me,Ee,H,Z.z,null)}}else if((b.isMesh||b.isLine||b.isPoints)&&(b.isSkinnedMesh&&b.skeleton.frame!==Fe.render.frame&&(b.skeleton.update(),b.skeleton.frame=Fe.render.frame),!b.frustumCulled||j.intersectsObject(b))){L&&Z.setFromMatrixPosition(b.matrixWorld).applyMatrix4(V);let Me=Oe.update(b),Ee=b.material;if(Array.isArray(Ee)){let Ae=Me.groups;for(let Pe=0,Re=Ae.length;Pe0&&Je(X,xe,N,H),L&&ye.viewport(D.copy(L)),X.length>0&&Rt(X,N,H),xe.length>0&&Rt(xe,N,H),Me.length>0&&Rt(Me,N,H),ye.buffers.depth.setTest(!0),ye.buffers.depth.setMask(!0),ye.buffers.color.setMask(!0),ye.setPolygonOffset(!1)}function Je(b,N,H,L){if(ce===null){let Ee=ie.isWebGL2;ce=new on(1024,1024,{generateMipmaps:!0,type:be.has("EXT_color_buffer_half_float")?Yi:Ln,minFilter:qi,samples:Ee&&a===!0?4:0})}let X=u.getRenderTarget();u.setRenderTarget(ce),u.clear();let xe=u.toneMapping;u.toneMapping=rn,Rt(b,H,L),Te.updateMultisampleRenderTarget(ce),Te.updateRenderTargetMipmap(ce);let Me=!1;for(let Ee=0,Ae=N.length;Ee0&&Te.useMultisampledRTT(b)===!1?X=me.get(b).__webglMultisampledFramebuffer:X=Re,D.copy(b.viewport),x.copy(b.scissor),T=b.scissorTest}else D.copy(te).multiplyScalar(U).floor(),x.copy($).multiplyScalar(U).floor(),T=Y;if(ye.bindFramebuffer(36160,X)&&ie.drawBuffers&&L&&ye.drawBuffers(b,X),ye.viewport(D),ye.scissor(x),ye.setScissorTest(T),xe){let Ae=me.get(b.texture);B.framebufferTexture2D(36160,36064,34069+N,Ae.__webglTexture,H)}else if(Me){let Ae=me.get(b.texture),Pe=N||0;B.framebufferTextureLayer(36160,36064,Ae.__webglTexture,H||0,Pe)}P=-1},this.readRenderTargetPixels=function(b,N,H,L,X,xe,Me){if(!(b&&b.isWebGLRenderTarget)){console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");return}let Ee=me.get(b).__webglFramebuffer;if(b.isWebGLCubeRenderTarget&&Me!==void 0&&(Ee=Ee[Me]),Ee){ye.bindFramebuffer(36160,Ee);try{let Ae=b.texture,Pe=Ae.format,Re=Ae.type;if(Pe!==Bt&&F.convert(Pe)!==B.getParameter(35739)){console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");return}let Le=Re===Yi&&(be.has("EXT_color_buffer_half_float")||ie.isWebGL2&&be.has("EXT_color_buffer_float"));if(Re!==Ln&&F.convert(Re)!==B.getParameter(35738)&&!(Re===In&&(ie.isWebGL2||be.has("OES_texture_float")||be.has("WEBGL_color_buffer_float")))&&!Le){console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");return}N>=0&&N<=b.width-L&&H>=0&&H<=b.height-X&&B.readPixels(N,H,L,X,F.convert(Pe),F.convert(Re),xe)}finally{let Ae=S!==null?me.get(S).__webglFramebuffer:null;ye.bindFramebuffer(36160,Ae)}}},this.copyFramebufferToTexture=function(b,N,H=0){let L=Math.pow(2,-H),X=Math.floor(N.image.width*L),xe=Math.floor(N.image.height*L);Te.setTexture2D(N,0),B.copyTexSubImage2D(3553,H,0,0,b.x,b.y,X,xe),ye.unbindTexture()},this.copyTextureToTexture=function(b,N,H,L=0){let X=N.image.width,xe=N.image.height,Me=F.convert(H.format),Ee=F.convert(H.type);Te.setTexture2D(H,0),B.pixelStorei(37440,H.flipY),B.pixelStorei(37441,H.premultiplyAlpha),B.pixelStorei(3317,H.unpackAlignment),N.isDataTexture?B.texSubImage2D(3553,L,b.x,b.y,X,xe,Me,Ee,N.image.data):N.isCompressedTexture?B.compressedTexSubImage2D(3553,L,b.x,b.y,N.mipmaps[0].width,N.mipmaps[0].height,Me,N.mipmaps[0].data):B.texSubImage2D(3553,L,b.x,b.y,Me,Ee,N.image),L===0&&H.generateMipmaps&&B.generateMipmap(3553),ye.unbindTexture()},this.copyTextureToTexture3D=function(b,N,H,L,X=0){if(u.isWebGL1Renderer){console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.");return}let xe=b.max.x-b.min.x+1,Me=b.max.y-b.min.y+1,Ee=b.max.z-b.min.z+1,Ae=F.convert(L.format),Pe=F.convert(L.type),Re;if(L.isData3DTexture)Te.setTexture3D(L,0),Re=32879;else if(L.isDataArrayTexture)Te.setTexture2DArray(L,0),Re=35866;else{console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");return}B.pixelStorei(37440,L.flipY),B.pixelStorei(37441,L.premultiplyAlpha),B.pixelStorei(3317,L.unpackAlignment);let Le=B.getParameter(3314),Be=B.getParameter(32878),mt=B.getParameter(3316),Ut=B.getParameter(3315),vn=B.getParameter(32877),je=H.isCompressedTexture?H.mipmaps[0]:H.image;B.pixelStorei(3314,je.width),B.pixelStorei(32878,je.height),B.pixelStorei(3316,b.min.x),B.pixelStorei(3315,b.min.y),B.pixelStorei(32877,b.min.z),H.isDataTexture||H.isData3DTexture?B.texSubImage3D(Re,X,N.x,N.y,N.z,xe,Me,Ee,Ae,Pe,je.data):H.isCompressedArrayTexture?(console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture."),B.compressedTexSubImage3D(Re,X,N.x,N.y,N.z,xe,Me,Ee,Ae,je.data)):B.texSubImage3D(Re,X,N.x,N.y,N.z,xe,Me,Ee,Ae,Pe,je),B.pixelStorei(3314,Le),B.pixelStorei(32878,Be),B.pixelStorei(3316,mt),B.pixelStorei(3315,Ut),B.pixelStorei(32877,vn),X===0&&L.generateMipmaps&&B.generateMipmap(Re),ye.unbindTexture()},this.initTexture=function(b){b.isCubeTexture?Te.setTextureCube(b,0):b.isData3DTexture?Te.setTexture3D(b,0):b.isDataArrayTexture||b.isCompressedArrayTexture?Te.setTexture2DArray(b,0):Te.setTexture2D(b,0),ye.unbindTexture()},this.resetState=function(){v=0,M=0,S=null,ye.reset(),oe.reset()},typeof __THREE_DEVTOOLS__<"u"&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}get physicallyCorrectLights(){return console.warn("THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead."),!this.useLegacyLights}set physicallyCorrectLights(e){console.warn("THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead."),this.useLegacyLights=!e}},va=class extends Ki{};va.prototype.isWebGL1Renderer=!0;var tr=class extends pt{constructor(){super(),this.isScene=!0,this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.backgroundBlurriness=0,this.backgroundIntensity=1,this.overrideMaterial=null,typeof __THREE_DEVTOOLS__<"u"&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(e,t){return super.copy(e,t),e.background!==null&&(this.background=e.background.clone()),e.environment!==null&&(this.environment=e.environment.clone()),e.fog!==null&&(this.fog=e.fog.clone()),this.backgroundBlurriness=e.backgroundBlurriness,this.backgroundIntensity=e.backgroundIntensity,e.overrideMaterial!==null&&(this.overrideMaterial=e.overrideMaterial.clone()),this.matrixAutoUpdate=e.matrixAutoUpdate,this}toJSON(e){let t=super.toJSON(e);return this.fog!==null&&(t.object.fog=this.fog.toJSON()),this.backgroundBlurriness>0&&(t.object.backgroundBlurriness=this.backgroundBlurriness),this.backgroundIntensity!==1&&(t.object.backgroundIntensity=this.backgroundIntensity),t}get autoUpdate(){return console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate}set autoUpdate(e){console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate=e}},Qi=class{constructor(e,t){this.isInterleavedBuffer=!0,this.array=e,this.stride=t,this.count=e!==void 0?e.length/t:0,this.usage=na,this.updateRange={offset:0,count:-1},this.version=0,this.uuid=an()}onUploadCallback(){}set needsUpdate(e){e===!0&&this.version++}setUsage(e){return this.usage=e,this}copy(e){return this.array=new e.array.constructor(e.array),this.count=e.count,this.stride=e.stride,this.usage=e.usage,this}copyAt(e,t,n){e*=this.stride,n*=t.stride;for(let i=0,r=this.stride;ie.far||t.push({distance:c,point:Bi.clone(),uv:Pn.getInterpolation(Bi,ks,ki,Vs,gl,Jr,_l,new ve),face:null,object:this})}copy(e,t){return super.copy(e,t),e.center!==void 0&&this.center.copy(e.center),this.material=e.material,this}};function Hs(s,e,t,n,i,r){hi.subVectors(s,t).addScalar(.5).multiply(n),i!==void 0?(zi.x=r*hi.x-i*hi.y,zi.y=i*hi.x+r*hi.y):zi.copy(hi),s.copy(e),s.x+=zi.x,s.y+=zi.y,s.applyMatrix4(Hl)}var yn=class extends ln{constructor(e){super(),this.isLineBasicMaterial=!0,this.type="LineBasicMaterial",this.color=new De(16777215),this.map=null,this.linewidth=1,this.linecap="round",this.linejoin="round",this.fog=!0,this.setValues(e)}copy(e){return super.copy(e),this.color.copy(e.color),this.map=e.map,this.linewidth=e.linewidth,this.linecap=e.linecap,this.linejoin=e.linejoin,this.fog=e.fog,this}},xl=new C,yl=new C,vl=new Qe,$r=new Ji,Gs=new bi,ba=class extends pt{constructor(e=new Mt,t=new yn){super(),this.isLine=!0,this.type="Line",this.geometry=e,this.material=t,this.updateMorphTargets()}copy(e,t){return super.copy(e,t),this.material=e.material,this.geometry=e.geometry,this}computeLineDistances(){let e=this.geometry;if(e.index===null){let t=e.attributes.position,n=[0];for(let i=1,r=t.count;ic)continue;d.applyMatrix4(this.matrixWorld);let D=e.ray.origin.distanceTo(d);De.far||t.push({distance:D,point:f.clone().applyMatrix4(this.matrixWorld),index:v,face:null,faceIndex:null,object:this})}}else{let u=Math.max(0,o.start),w=Math.min(m.count,o.start+o.count);for(let v=u,M=w-1;vc)continue;d.applyMatrix4(this.matrixWorld);let P=e.ray.origin.distanceTo(d);Pe.far||t.push({distance:P,point:f.clone().applyMatrix4(this.matrixWorld),index:v,face:null,faceIndex:null,object:this})}}}updateMorphTargets(){let t=this.geometry.morphAttributes,n=Object.keys(t);if(n.length>0){let i=t[n[0]];if(i!==void 0){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let r=0,o=i.length;r=r)){let a=t[1];e=r)break e}o=n,n=0;break t}break n}for(;n>>1;et;)--o;if(++o,r!==0||o!==i){r>=o&&(o=Math.max(o,1),r=o-1);let a=this.getValueSize();this.times=gn(n,r,o),this.values=gn(this.values,r*a,o*a)}return this}validate(){let e=!0,t=this.getValueSize();t-Math.floor(t)!==0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),e=!1);let n=this.times,i=this.values,r=n.length;r===0&&(console.error("THREE.KeyframeTrack: Track is empty.",this),e=!1);let o=null;for(let a=0;a!==r;a++){let c=n[a];if(typeof c=="number"&&isNaN(c)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,a,c),e=!1;break}if(o!==null&&o>c){console.error("THREE.KeyframeTrack: Out of order keys.",this,a,c,o),e=!1;break}o=c}if(i!==void 0&&Gl(i))for(let a=0,c=i.length;a!==c;++a){let l=i[a];if(isNaN(l)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,a,l),e=!1;break}}return e}optimize(){let e=gn(this.times),t=gn(this.values),n=this.getValueSize(),i=this.getInterpolation()===Ar,r=e.length-1,o=1;for(let a=1;a0){e[o]=e[r];for(let a=r*n,c=o*n,l=0;l!==n;++l)t[c+l]=t[a+l];++o}return o!==e.length?(this.times=gn(e,0,o),this.values=gn(t,0,o*n)):(this.times=e,this.values=t),this}clone(){let e=gn(this.times,0),t=gn(this.values,0),n=this.constructor,i=new n(this.name,e,t);return i.createInterpolant=this.createInterpolant,i}};Vt.prototype.TimeBufferType=Float32Array;Vt.prototype.ValueBufferType=Float32Array;Vt.prototype.DefaultInterpolation=Ys;var Fn=class extends Vt{};Fn.prototype.ValueTypeName="bool";Fn.prototype.ValueBufferType=Array;Fn.prototype.DefaultInterpolation=qs;Fn.prototype.InterpolantFactoryMethodLinear=void 0;Fn.prototype.InterpolantFactoryMethodSmooth=void 0;var Ea=class extends Vt{};Ea.prototype.ValueTypeName="color";var Ta=class extends Vt{};Ta.prototype.ValueTypeName="number";var Aa=class extends Ci{constructor(e,t,n,i){super(e,t,n,i)}interpolate_(e,t,n,i){let r=this.resultBuffer,o=this.sampleValues,a=this.valueSize,c=(n-t)/(i-t),l=e*a;for(let h=l+a;l!==h;l+=4)zt.slerpFlat(r,0,o,l-a,o,l,c);return r}},ts=class extends Vt{InterpolantFactoryMethodLinear(e){return new Aa(this.times,this.values,this.getValueSize(),e)}};ts.prototype.ValueTypeName="quaternion";ts.prototype.DefaultInterpolation=Ys;ts.prototype.InterpolantFactoryMethodSmooth=void 0;var Bn=class extends Vt{};Bn.prototype.ValueTypeName="string";Bn.prototype.ValueBufferType=Array;Bn.prototype.DefaultInterpolation=qs;Bn.prototype.InterpolantFactoryMethodLinear=void 0;Bn.prototype.InterpolantFactoryMethodSmooth=void 0;var Ca=class extends Vt{};Ca.prototype.ValueTypeName="vector";var Sl={enabled:!1,files:{},add:function(s,e){this.enabled!==!1&&(this.files[s]=e)},get:function(s){if(this.enabled!==!1)return this.files[s]},remove:function(s){delete this.files[s]},clear:function(){this.files={}}},Ia=class{constructor(e,t,n){let i=this,r=!1,o=0,a=0,c,l=[];this.onStart=void 0,this.onLoad=e,this.onProgress=t,this.onError=n,this.itemStart=function(h){a++,r===!1&&i.onStart!==void 0&&i.onStart(h,o,a),r=!0},this.itemEnd=function(h){o++,i.onProgress!==void 0&&i.onProgress(h,o,a),o===a&&(r=!1,i.onLoad!==void 0&&i.onLoad())},this.itemError=function(h){i.onError!==void 0&&i.onError(h)},this.resolveURL=function(h){return c?c(h):h},this.setURLModifier=function(h){return c=h,this},this.addHandler=function(h,f){return l.push(h,f),this},this.removeHandler=function(h){let f=l.indexOf(h);return f!==-1&&l.splice(f,2),this},this.getHandler=function(h){for(let f=0,d=l.length;f"u"&&console.warn("THREE.ImageBitmapLoader: createImageBitmap() not supported."),typeof fetch>"u"&&console.warn("THREE.ImageBitmapLoader: fetch() not supported."),this.options={premultiplyAlpha:"none"}}setOptions(e){return this.options=e,this}load(e,t,n,i){e===void 0&&(e=""),this.path!==void 0&&(e=this.path+e),e=this.manager.resolveURL(e);let r=this,o=Sl.get(e);if(o!==void 0)return r.manager.itemStart(e),setTimeout(function(){t&&t(o),r.manager.itemEnd(e)},0),o;let a={};a.credentials=this.crossOrigin==="anonymous"?"same-origin":"include",a.headers=this.requestHeader,fetch(e,a).then(function(c){return c.blob()}).then(function(c){return createImageBitmap(c,Object.assign(r.options,{colorSpaceConversion:"none"}))}).then(function(c){Sl.add(e,c),t&&t(c),r.manager.itemEnd(e)}).catch(function(c){i&&i(c),r.manager.itemError(e),r.manager.itemEnd(e)}),r.manager.itemStart(e)}};var ka="\\[\\]\\.:\\/",Qp=new RegExp("["+ka+"]","g"),Va="[^"+ka+"]",em="[^"+ka.replace("\\.","")+"]",tm=/((?:WC+[\/:])*)/.source.replace("WC",Va),nm=/(WCOD+)?/.source.replace("WCOD",em),im=/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC",Va),sm=/\.(WC+)(?:\[(.+)\])?/.source.replace("WC",Va),rm=new RegExp("^"+tm+nm+im+sm+"$"),am=["material","materials","bones","map"],Da=class{constructor(e,t,n){let i=n||Ge.parseTrackName(t);this._targetGroup=e,this._bindings=e.subscribe_(t,i)}getValue(e,t){this.bind();let n=this._targetGroup.nCachedObjects_,i=this._bindings[n];i!==void 0&&i.getValue(e,t)}setValue(e,t){let n=this._bindings;for(let i=this._targetGroup.nCachedObjects_,r=n.length;i!==r;++i)n[i].setValue(e,t)}bind(){let e=this._bindings;for(let t=this._targetGroup.nCachedObjects_,n=e.length;t!==n;++t)e[t].bind()}unbind(){let e=this._bindings;for(let t=this._targetGroup.nCachedObjects_,n=e.length;t!==n;++t)e[t].unbind()}},Ge=class s{constructor(e,t,n){this.path=t,this.parsedPath=n||s.parseTrackName(t),this.node=s.findNode(e,this.parsedPath.nodeName),this.rootNode=e,this.getValue=this._getValue_unbound,this.setValue=this._setValue_unbound}static create(e,t,n){return e&&e.isAnimationObjectGroup?new s.Composite(e,t,n):new s(e,t,n)}static sanitizeNodeName(e){return e.replace(/\s/g,"_").replace(Qp,"")}static parseTrackName(e){let t=rm.exec(e);if(t===null)throw new Error("PropertyBinding: Cannot parse trackName: "+e);let n={nodeName:t[2],objectName:t[3],objectIndex:t[4],propertyName:t[5],propertyIndex:t[6]},i=n.nodeName&&n.nodeName.lastIndexOf(".");if(i!==void 0&&i!==-1){let r=n.nodeName.substring(i+1);am.indexOf(r)!==-1&&(n.nodeName=n.nodeName.substring(0,i),n.objectName=r)}if(n.propertyName===null||n.propertyName.length===0)throw new Error("PropertyBinding: can not parse propertyName from trackName: "+e);return n}static findNode(e,t){if(t===void 0||t===""||t==="."||t===-1||t===e.name||t===e.uuid)return e;if(e.skeleton){let n=e.skeleton.getBoneByName(t);if(n!==void 0)return n}if(e.children){let n=function(r){for(let o=0;oMath.PI&&(we-=pe),Ie<-Math.PI?Ie+=pe:Ie>Math.PI&&(Ie-=pe),we<=Ie?a.theta=Math.max(we,Math.min(Ie,a.theta)):a.theta=a.theta>(we+Ie)/2?Math.max(we,a.theta):Math.min(Ie,a.theta)),a.phi=Math.max(n.minPolarAngle,Math.min(n.maxPolarAngle,a.phi)),a.makeSafe(),a.radius*=l,a.radius=Math.max(n.minDistance,Math.min(n.maxDistance,a.radius)),n.enableDamping===!0?n.target.addScaledVector(h,n.dampingFactor):n.target.add(h),A.setFromSpherical(a),A.applyQuaternion(F),de.copy(n.target).add(A),n.object.lookAt(n.target),n.enableDamping===!0?(c.theta*=1-n.dampingFactor,c.phi*=1-n.dampingFactor,h.multiplyScalar(1-n.dampingFactor)):(c.set(0,0,0),h.set(0,0,0)),l=1,f||oe.distanceToSquared(n.object.position)>o||8*(1-fe.dot(n.object.quaternion))>o?(n.dispatchEvent(Xl),oe.copy(n.object.position),fe.copy(n.object.quaternion),f=!1,!0):!1}})(),this.dispose=function(){n.domElement.removeEventListener("contextmenu",y),n.domElement.removeEventListener("pointerdown",Te),n.domElement.removeEventListener("pointercancel",We),n.domElement.removeEventListener("wheel",Oe),n.domElement.removeEventListener("pointermove",qe),n.domElement.removeEventListener("pointerup",We),n._domElementKeyEvents!==null&&(n._domElementKeyEvents.removeEventListener("keydown",ke),n._domElementKeyEvents=null)};let n=this,i={NONE:-1,ROTATE:0,DOLLY:1,PAN:2,TOUCH_ROTATE:3,TOUCH_PAN:4,TOUCH_DOLLY_PAN:5,TOUCH_DOLLY_ROTATE:6},r=i.NONE,o=1e-6,a=new is,c=new is,l=1,h=new C,f=!1,d=new ve,p=new ve,g=new ve,_=new ve,m=new ve,u=new ve,w=new ve,v=new ve,M=new ve,S=[],P={};function R(){return 2*Math.PI/60/60*n.autoRotateSpeed}function D(){return Math.pow(.95,n.zoomSpeed)}function x(A){c.theta-=A}function T(A){c.phi-=A}let W=(function(){let A=new C;return function(F,oe){A.setFromMatrixColumn(oe,0),A.multiplyScalar(-F),h.add(A)}})(),J=(function(){let A=new C;return function(F,oe){n.screenSpacePanning===!0?A.setFromMatrixColumn(oe,1):(A.setFromMatrixColumn(oe,0),A.crossVectors(n.object.up,A)),A.multiplyScalar(F),h.add(A)}})(),U=(function(){let A=new C;return function(F,oe){let fe=n.domElement;if(n.object.isPerspectiveCamera){let pe=n.object.position;A.copy(pe).sub(n.target);let he=A.length();he*=Math.tan(n.object.fov/2*Math.PI/180),W(2*F*he/fe.clientHeight,n.object.matrix),J(2*oe*he/fe.clientHeight,n.object.matrix)}else n.object.isOrthographicCamera?(W(F*(n.object.right-n.object.left)/n.object.zoom/fe.clientWidth,n.object.matrix),J(oe*(n.object.top-n.object.bottom)/n.object.zoom/fe.clientHeight,n.object.matrix)):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."),n.enablePan=!1)}})();function O(A){n.object.isPerspectiveCamera?l/=A:n.object.isOrthographicCamera?(n.object.zoom=Math.max(n.minZoom,Math.min(n.maxZoom,n.object.zoom*A)),n.object.updateProjectionMatrix(),f=!0):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),n.enableZoom=!1)}function k(A){n.object.isPerspectiveCamera?l*=A:n.object.isOrthographicCamera?(n.object.zoom=Math.max(n.minZoom,Math.min(n.maxZoom,n.object.zoom/A)),n.object.updateProjectionMatrix(),f=!0):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),n.enableZoom=!1)}function te(A){d.set(A.clientX,A.clientY)}function $(A){w.set(A.clientX,A.clientY)}function Y(A){_.set(A.clientX,A.clientY)}function j(A){p.set(A.clientX,A.clientY),g.subVectors(p,d).multiplyScalar(n.rotateSpeed);let q=n.domElement;x(2*Math.PI*g.x/q.clientHeight),T(2*Math.PI*g.y/q.clientHeight),d.copy(p),n.update()}function ne(A){v.set(A.clientX,A.clientY),M.subVectors(v,w),M.y>0?O(D()):M.y<0&&k(D()),w.copy(v),n.update()}function _e(A){m.set(A.clientX,A.clientY),u.subVectors(m,_).multiplyScalar(n.panSpeed),U(u.x,u.y),_.copy(m),n.update()}function ce(A){A.deltaY<0?k(D()):A.deltaY>0&&O(D()),n.update()}function V(A){let q=!1;switch(A.code){case n.keys.UP:A.ctrlKey||A.metaKey||A.shiftKey?T(2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):U(0,n.keyPanSpeed),q=!0;break;case n.keys.BOTTOM:A.ctrlKey||A.metaKey||A.shiftKey?T(-2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):U(0,-n.keyPanSpeed),q=!0;break;case n.keys.LEFT:A.ctrlKey||A.metaKey||A.shiftKey?x(2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):U(n.keyPanSpeed,0),q=!0;break;case n.keys.RIGHT:A.ctrlKey||A.metaKey||A.shiftKey?x(-2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):U(-n.keyPanSpeed,0),q=!0;break}q&&(A.preventDefault(),n.update())}function Z(){if(S.length===1)d.set(S[0].pageX,S[0].pageY);else{let A=.5*(S[0].pageX+S[1].pageX),q=.5*(S[0].pageY+S[1].pageY);d.set(A,q)}}function re(){if(S.length===1)_.set(S[0].pageX,S[0].pageY);else{let A=.5*(S[0].pageX+S[1].pageX),q=.5*(S[0].pageY+S[1].pageY);_.set(A,q)}}function le(){let A=S[0].pageX-S[1].pageX,q=S[0].pageY-S[1].pageY,F=Math.sqrt(A*A+q*q);w.set(0,F)}function B(){n.enableZoom&&le(),n.enablePan&&re()}function Se(){n.enableZoom&&le(),n.enableRotate&&Z()}function be(A){if(S.length==1)p.set(A.pageX,A.pageY);else{let F=ae(A),oe=.5*(A.pageX+F.x),fe=.5*(A.pageY+F.y);p.set(oe,fe)}g.subVectors(p,d).multiplyScalar(n.rotateSpeed);let q=n.domElement;x(2*Math.PI*g.x/q.clientHeight),T(2*Math.PI*g.y/q.clientHeight),d.copy(p)}function ie(A){if(S.length===1)m.set(A.pageX,A.pageY);else{let q=ae(A),F=.5*(A.pageX+q.x),oe=.5*(A.pageY+q.y);m.set(F,oe)}u.subVectors(m,_).multiplyScalar(n.panSpeed),U(u.x,u.y),_.copy(m)}function ye(A){let q=ae(A),F=A.pageX-q.x,oe=A.pageY-q.y,fe=Math.sqrt(F*F+oe*oe);v.set(0,fe),M.set(0,Math.pow(v.y/w.y,n.zoomSpeed)),O(M.y),w.copy(v)}function Fe(A){n.enableZoom&&ye(A),n.enablePan&&ie(A)}function me(A){n.enableZoom&&ye(A),n.enableRotate&&be(A)}function Te(A){n.enabled!==!1&&(S.length===0&&(n.domElement.setPointerCapture(A.pointerId),n.domElement.addEventListener("pointermove",qe),n.domElement.addEventListener("pointerup",We)),z(A),A.pointerType==="touch"?at(A):$e(A))}function qe(A){n.enabled!==!1&&(A.pointerType==="touch"?E(A):Ye(A))}function We(A){K(A),S.length===0&&(n.domElement.releasePointerCapture(A.pointerId),n.domElement.removeEventListener("pointermove",qe),n.domElement.removeEventListener("pointerup",We)),n.dispatchEvent(ql),r=i.NONE}function $e(A){let q;switch(A.button){case 0:q=n.mouseButtons.LEFT;break;case 1:q=n.mouseButtons.MIDDLE;break;case 2:q=n.mouseButtons.RIGHT;break;default:q=-1}switch(q){case zn.DOLLY:if(n.enableZoom===!1)return;$(A),r=i.DOLLY;break;case zn.ROTATE:if(A.ctrlKey||A.metaKey||A.shiftKey){if(n.enablePan===!1)return;Y(A),r=i.PAN}else{if(n.enableRotate===!1)return;te(A),r=i.ROTATE}break;case zn.PAN:if(A.ctrlKey||A.metaKey||A.shiftKey){if(n.enableRotate===!1)return;te(A),r=i.ROTATE}else{if(n.enablePan===!1)return;Y(A),r=i.PAN}break;default:r=i.NONE}r!==i.NONE&&n.dispatchEvent(Ha)}function Ye(A){switch(r){case i.ROTATE:if(n.enableRotate===!1)return;j(A);break;case i.DOLLY:if(n.enableZoom===!1)return;ne(A);break;case i.PAN:if(n.enablePan===!1)return;_e(A);break}}function Oe(A){n.enabled===!1||n.enableZoom===!1||r!==i.NONE||(A.preventDefault(),n.dispatchEvent(Ha),ce(A),n.dispatchEvent(ql))}function ke(A){n.enabled===!1||n.enablePan===!1||V(A)}function at(A){switch(ee(A),S.length){case 1:switch(n.touches.ONE){case kn.ROTATE:if(n.enableRotate===!1)return;Z(),r=i.TOUCH_ROTATE;break;case kn.PAN:if(n.enablePan===!1)return;re(),r=i.TOUCH_PAN;break;default:r=i.NONE}break;case 2:switch(n.touches.TWO){case kn.DOLLY_PAN:if(n.enableZoom===!1&&n.enablePan===!1)return;B(),r=i.TOUCH_DOLLY_PAN;break;case kn.DOLLY_ROTATE:if(n.enableZoom===!1&&n.enableRotate===!1)return;Se(),r=i.TOUCH_DOLLY_ROTATE;break;default:r=i.NONE}break;default:r=i.NONE}r!==i.NONE&&n.dispatchEvent(Ha)}function E(A){switch(ee(A),r){case i.TOUCH_ROTATE:if(n.enableRotate===!1)return;be(A),n.update();break;case i.TOUCH_PAN:if(n.enablePan===!1)return;ie(A),n.update();break;case i.TOUCH_DOLLY_PAN:if(n.enableZoom===!1&&n.enablePan===!1)return;Fe(A),n.update();break;case i.TOUCH_DOLLY_ROTATE:if(n.enableZoom===!1&&n.enableRotate===!1)return;me(A),n.update();break;default:r=i.NONE}}function y(A){n.enabled!==!1&&A.preventDefault()}function z(A){S.push(A)}function K(A){delete P[A.pointerId];for(let q=0;q>24}readUint8(e){return this.bytes_[e]}readInt16(e){return this.readUint16(e)<<16>>16}readUint16(e){return this.bytes_[e]|this.bytes_[e+1]<<8}readInt32(e){return this.bytes_[e]|this.bytes_[e+1]<<8|this.bytes_[e+2]<<16|this.bytes_[e+3]<<24}readUint32(e){return this.readInt32(e)>>>0}readInt64(e){return BigInt.asIntN(64,BigInt(this.readUint32(e))+(BigInt(this.readUint32(e+4))<>8}writeUint16(e,t){this.bytes_[e]=t,this.bytes_[e+1]=t>>8}writeInt32(e,t){this.bytes_[e]=t,this.bytes_[e+1]=t>>8,this.bytes_[e+2]=t>>16,this.bytes_[e+3]=t>>24}writeUint32(e,t){this.bytes_[e]=t,this.bytes_[e+1]=t>>8,this.bytes_[e+2]=t>>16,this.bytes_[e+3]=t>>24}writeInt64(e,t){this.writeInt32(e,Number(BigInt.asIntN(32,t))),this.writeInt32(e+4,Number(BigInt.asIntN(32,t>>BigInt(32))))}writeUint64(e,t){this.writeUint32(e,Number(BigInt.asUintN(32,t))),this.writeUint32(e+4,Number(BigInt.asUintN(32,t>>BigInt(32))))}writeFloat32(e,t){mr[0]=t,this.writeInt32(e,Zt[0])}writeFloat64(e,t){gr[0]=t,this.writeInt32(e,Zt[Ri?0:1]),this.writeInt32(e+4,Zt[Ri?1:0])}getBufferIdentifier(){if(this.bytes_.length=0;n--)e.addOffset(t[n]);return e.endVector()}static startSamplersVector(e,t){e.startVector(4,t,4)}static endExpMaterial(e){return e.endObject()}static createExpMaterial(e,t,n,i,r,o,a){return s.startExpMaterial(e),s.addName(e,t),s.addShaderName(e,n),s.addDisableCulling(e,i),s.addTransparency(e,r),s.addDepthTest(e,o),s.addSamplers(e,a),s.endExpMaterial(e)}};var ds=class{bb=null;bb_pos=0;__init(e,t){return this.bb_pos=e,this.bb=t,this}index(){return this.bb.readUint8(this.bb_pos)}type(){return this.bb.readUint8(this.bb_pos+1)}usage(){return this.bb.readUint8(this.bb_pos+2)}count(){return this.bb.readUint8(this.bb_pos+3)}offset(){return this.bb.readUint8(this.bb_pos+4)}byteSize(){return this.bb.readUint8(this.bb_pos+5)}normalized(){return!!this.bb.readInt8(this.bb_pos+6)}static sizeOf(){return 7}static createExpVertexFormatElement(e,t,n,i,r,o,a,c){return e.prep(1,7),e.writeInt8(+!!c),e.writeInt8(a),e.writeInt8(o),e.writeInt8(r),e.writeInt8(i),e.writeInt8(n),e.writeInt8(t),e.offset()}};var ps=class s{bb=null;bb_pos=0;__init(e,t){return this.bb_pos=e,this.bb=t,this}static getRootAsExpVertexFormat(e,t){return(t||new s).__init(e.readInt32(e.position())+e.position(),e)}static getSizePrefixedRootAsExpVertexFormat(e,t){return e.setPosition(e.position()+4),(t||new s).__init(e.readInt32(e.position())+e.position(),e)}elements(e,t){let n=this.bb.__offset(this.bb_pos,4);return n?(t||new ds).__init(this.bb.__vector(this.bb_pos+n)+e*7,this.bb):null}elementsLength(){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.__vector_len(this.bb_pos+e):0}vertexSize(){let e=this.bb.__offset(this.bb_pos,6);return e?this.bb.readUint8(this.bb_pos+e):0}static startExpVertexFormat(e){e.startObject(2)}static addElements(e,t){e.addFieldOffset(0,t,0)}static startElementsVector(e,t){e.startVector(7,t,1)}static addVertexSize(e,t){e.addFieldInt8(1,t,0)}static endExpVertexFormat(e){return e.endObject()}static createExpVertexFormat(e,t,n){return s.startExpVertexFormat(e),s.addElements(e,t),s.addVertexSize(e,n),s.endExpVertexFormat(e)}};var ms=class s{bb=null;bb_pos=0;__init(e,t){return this.bb_pos=e,this.bb=t,this}static getRootAsExpMesh(e,t){return(t||new s).__init(e.readInt32(e.position())+e.position(),e)}static getSizePrefixedRootAsExpMesh(e,t){return e.setPosition(e.position()+4),(t||new s).__init(e.readInt32(e.position())+e.position(),e)}material(e){let t=this.bb.__offset(this.bb_pos,4);return t?(e||new fs).__init(this.bb.__indirect(this.bb_pos+t),this.bb):null}vertexFormat(e){let t=this.bb.__offset(this.bb_pos,6);return t?(e||new ps).__init(this.bb.__indirect(this.bb_pos+t),this.bb):null}primitiveType(){let e=this.bb.__offset(this.bb_pos,8);return e?this.bb.readUint8(this.bb_pos+e):0}indexBuffer(e){let t=this.bb.__offset(this.bb_pos,10);return t?this.bb.readUint8(this.bb.__vector(this.bb_pos+t)+e):0}indexBufferLength(){let e=this.bb.__offset(this.bb_pos,10);return e?this.bb.__vector_len(this.bb_pos+e):0}indexBufferArray(){let e=this.bb.__offset(this.bb_pos,10);return e?new Uint8Array(this.bb.bytes().buffer,this.bb.bytes().byteOffset+this.bb.__vector(this.bb_pos+e),this.bb.__vector_len(this.bb_pos+e)):null}indexType(){let e=this.bb.__offset(this.bb_pos,12);return e?this.bb.readUint8(this.bb_pos+e):0}indexCount(){let e=this.bb.__offset(this.bb_pos,14);return e?this.bb.readUint32(this.bb_pos+e):0}vertexBuffer(e){let t=this.bb.__offset(this.bb_pos,16);return t?this.bb.readUint8(this.bb.__vector(this.bb_pos+t)+e):0}vertexBufferLength(){let e=this.bb.__offset(this.bb_pos,16);return e?this.bb.__vector_len(this.bb_pos+e):0}vertexBufferArray(){let e=this.bb.__offset(this.bb_pos,16);return e?new Uint8Array(this.bb.bytes().buffer,this.bb.bytes().byteOffset+this.bb.__vector(this.bb_pos+e),this.bb.__vector_len(this.bb_pos+e)):null}static startExpMesh(e){e.startObject(7)}static addMaterial(e,t){e.addFieldOffset(0,t,0)}static addVertexFormat(e,t){e.addFieldOffset(1,t,0)}static addPrimitiveType(e,t){e.addFieldInt8(2,t,0)}static addIndexBuffer(e,t){e.addFieldOffset(3,t,0)}static createIndexBufferVector(e,t){e.startVector(1,t.length,1);for(let n=t.length-1;n>=0;n--)e.addInt8(t[n]);return e.endVector()}static startIndexBufferVector(e,t){e.startVector(1,t,1)}static addIndexType(e,t){e.addFieldInt8(4,t,0)}static addIndexCount(e,t){e.addFieldInt32(5,t,0)}static addVertexBuffer(e,t){e.addFieldOffset(6,t,0)}static createVertexBufferVector(e,t){e.startVector(1,t.length,1);for(let n=t.length-1;n>=0;n--)e.addInt8(t[n]);return e.endVector()}static startVertexBufferVector(e,t){e.startVector(1,t,1)}static endExpMesh(e){return e.endObject()}};var gs=class s{bb=null;bb_pos=0;__init(e,t){return this.bb_pos=e,this.bb=t,this}static getRootAsExpScene(e,t){return(t||new s).__init(e.readInt32(e.position())+e.position(),e)}static getSizePrefixedRootAsExpScene(e,t){return e.setPosition(e.position()+4),(t||new s).__init(e.readInt32(e.position())+e.position(),e)}camera(e){let t=this.bb.__offset(this.bb_pos,4);return t?(e||new hs).__init(this.bb_pos+t,this.bb):null}meshes(e,t){let n=this.bb.__offset(this.bb_pos,6);return n?(t||new ms).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+n)+e*4),this.bb):null}meshesLength(){let e=this.bb.__offset(this.bb_pos,6);return e?this.bb.__vector_len(this.bb_pos+e):0}animatedTextures(e,t){let n=this.bb.__offset(this.bb_pos,8);return n?(t||new cs).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+n)+e*4),this.bb):null}animatedTexturesLength(){let e=this.bb.__offset(this.bb_pos,8);return e?this.bb.__vector_len(this.bb_pos+e):0}static startExpScene(e){e.startObject(3)}static addCamera(e,t){e.addFieldStruct(0,t,0)}static addMeshes(e,t){e.addFieldOffset(1,t,0)}static createMeshesVector(e,t){e.startVector(4,t.length,4);for(let n=t.length-1;n>=0;n--)e.addOffset(t[n]);return e.endVector()}static startMeshesVector(e,t){e.startVector(4,t,4)}static addAnimatedTextures(e,t){e.addFieldOffset(2,t,0)}static createAnimatedTexturesVector(e,t){e.startVector(4,t.length,4);for(let n=t.length-1;n>=0;n--)e.addOffset(t[n]);return e.endVector()}static startAnimatedTexturesVector(e,t){e.startVector(4,t,4)}static endExpScene(e){return e.endObject()}static finishExpSceneBuffer(e,t){e.finish(t)}static finishSizePrefixedExpSceneBuffer(e,t){e.finish(t,void 0,!0)}static createExpScene(e,t,n,i){return s.startExpScene(e),s.addCamera(e,t),s.addMeshes(e,n),s.addAnimatedTextures(e,i),s.endExpScene(e)}};function Ga(s,e){return new e(s.buffer,s.byteOffset,s.byteLength/e.BYTES_PER_ELEMENT)}function Wa(s){let e=s.vertexBufferArray(),t=s.indexBufferArray();if(!e||!t){console.warn("Missing vertex or index buffer build-data");return}console.debug("Vertex buffer size: %d, Index buffer size: %d",e.byteLength,t.byteLength);let n=s.vertexFormat();if(!n){console.warn("Missing vertex format");return}console.debug("Vertex format, stride=%d",n.vertexSize());let i=new Mt;if(s.indexType()==0){let r=Ga(t,Uint32Array);i.setIndex(new Si(r,1,!1))}else if(s.indexType()==1){let r=Ga(t,Uint16Array);i.setIndex(new xn(r,1,!1))}else{console.warn("Unknown index type: %o",s.indexType());return}for(let r=0;r0)continue;let a;switch(o.usage()){case 0:a="position";break;case 1:a="normal";break;case 2:a="color";break;case 3:a="uv";break;default:console.warn("Unsupported vertex format attribute usage: %o",o.usage());continue}console.debug("Element %s, Count=%o, Offset=%o, Normalized=%o",a,o.count(),o.offset(),o.normalized());let c;switch(o.type()){case 0:c=Float32Array;break;case 1:c=Uint8Array;break;case 2:c=Int8Array;break;case 3:c=Uint16Array;break;case 4:c=Int16Array;break;case 5:c=Uint32Array;break;case 6:c=Int32Array;break;default:console.warn("Unsupported element type: %o",o.type());continue}let l=c.BYTES_PER_ELEMENT,h=Ga(e,c),f=new Qi(h,n.vertexSize()/l);i.setAttribute(a,new Ti(f,o.count(),o.offset()/l,o.normalized()))}return i}var fm={rendertype_solid:{lighting:"lightmap",vertexColor:!0,textured:!0,alphaTest:null},rendertype_cutout:{lighting:"lightmap",alphaTest:.1,vertexColor:!0,textured:!0},rendertype_cutout_mipped:{lighting:"lightmap",alphaTest:.5,vertexColor:!0,textured:!0},rendertype_translucent:{lighting:"lightmap",alphaTest:null,vertexColor:!0,textured:!0},rendertype_tripwire:{lighting:"lightmap",alphaTest:.1,vertexColor:!0,textured:!0},rendertype_entity_solid:{lighting:"diffuse",alphaTest:null,vertexColor:!0,textured:!0},rendertype_entity_cutout:{lighting:"diffuse",alphaTest:.1,vertexColor:!0,textured:!0},position_color:{lighting:"none",alphaTest:0,vertexColor:!0,textured:!1},rendertype_entity_cutout_no_cull:{lighting:"diffuse",alphaTest:.1,vertexColor:!0,textured:!0},rendertype_entity_translucent_cull:{lighting:"diffuse",alphaTest:.1,vertexColor:!0,textured:!0},rendertype_text:{lighting:"none",alphaTest:.1,vertexColor:!0,textured:!0}},Jl=fm;function dm(s,e){switch(s.transparency()){case 0:e.blending=qt;break;case 1:e.blending=Xs;break;case 2:e.blending=Ii,e.blendSrc=ss,e.blendDst=Vn;break;case 3:e.blending=Ii,e.blendSrc=hr,e.blendDst=Vn,e.blendSrcAlpha=cr,e.blendDstAlpha=Vn;break;case 4:e.blending=Ii,e.blendSrc=Na,e.blendDst=hr,e.blendSrcAlpha=Vn,e.blendDstAlpha=cr;break;case 5:e.blending=Ii,e.blendSrc=ss,e.blendDst=rs,e.blendSrcAlpha=Vn,e.blendDstAlpha=rs;break}}async function Xa(s,e,t){let n=[];for(let l=0;l"u"){let r=(await import("./decompressFallback-VGYIC7XH.js")).default,o=await r(e);return new Response(o)}let t=new DecompressionStream("gzip"),n=e.stream().pipeThrough(t);return new Response(n)}async function pm(s){let e=await s.blob();s=await qa(e);let t=await s.arrayBuffer();return console.debug("Loaded %s, %d byte compressed, %d byte uncompressed",s.url,e.size,t.byteLength),t}async function Ya(s,e,t){let n=await fetch(e,{signal:t});if(!n.ok)throw n;let i=await pm(n),r=new Uint8Array(i),o=new Li(r),a=new Dt,c=new Map,l=gs.getRootAsExpScene(o);for(let p=0;pR.status==="fulfilled"?new bt(R.value):null),S=c.get(g.textureId()??"")??[];if(!S.length)continue;let P=[];for(let R=0;R {: title="Item Quote" color="#61b75d" iconItem="minecraft:emerald" } > 头部使用物品图标。 +```markdown +> {: title="PNG Quote" color="#c79d3e" iconPng="./diamond.png" } +> 从指南资源加载的 PNG 图标。 +``` + +> {: title="PNG Quote" color="#c79d3e" iconPng="./diamond.png" } +> 从指南资源加载的 PNG 图标。 + ## 列表 Markdown: @@ -492,6 +500,8 @@ mindmap +固定尺寸的运行时 Mermaid 视口: + 带有富文本标签和显式节点内容的思维导图: @@ -513,6 +523,16 @@ mindmap +## Mermaid 流程图 + +展示所有形状、扩展属性、图标和配方节点的流程图: + + + + + + + ## 脚注 脚注引用[^one]