diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java b/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java index b5b247369..a356892a4 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java @@ -17,10 +17,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.ResourceLocation; import org.lwjgl.opengl.GL11; -/** - * Implementation of GuiNPCDiagram for displaying Magic nodes and their connections. - * Magic objects are wrapped as DiagramIcons, and each Magic's weaknesses generate connections. - */ public class GuiMagicMap extends GuiNPCDiagram { private GuiScreen parent; @@ -35,6 +31,7 @@ public class GuiMagicMap extends GuiNPCDiagram { for (Magic magic : MagicController.getInstance().magics.values()) { magics.add(magic); } + setLayout(DiagramLayout.CIRCULAR); } @Override diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNPCDiagram.java b/src/main/java/noppes/npcs/client/gui/global/GuiNPCDiagram.java index 5b15a04b3..5513febe1 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiNPCDiagram.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNPCDiagram.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Random; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; @@ -18,7 +19,7 @@ import org.lwjgl.opengl.GL11; * Abstract GUI class for drawing diagrams that consist of icons (nodes) and connections (arrows). * * This class provides: - * - A standard circular layout of nodes. + * - Multiple layout algorithms for positioning nodes (Circular, Square, Tree, Generated). * - Panning and zooming functionality. * - Standard drawing of a node's "box": a background and border that can be customized. * - Connection drawing (including arrow heads). @@ -30,33 +31,66 @@ import org.lwjgl.opengl.GL11; */ public abstract class GuiNPCDiagram extends Gui { - // The area where the diagram is drawn. - protected int x, y, width, height; - // Panning offsets. - protected float panX = 0, panY = 0; - // Zoom factor. - protected float zoom = 1.0f; - // For mouse dragging. - protected boolean dragging = false; - protected int lastDragX, lastDragY; + // --------------- + // Diagram Area & Controls + // --------------- + protected int x, y, width, height; // The drawing area. + protected float panX = 0, panY = 0; // Panning offsets. + protected float zoom = 1.0f; // Zoom factor. + protected boolean dragging = false; // Drag flag. + protected int lastDragX, lastDragY; // Last mouse positions. - // The size of the icon image (e.g. 16 pixels square). - protected int iconSize = 16; - // Extra padding for the node background (box). - protected int slotPadding = 4; - // The overall size of the node's box (icon plus padding). - protected int slotSize = iconSize + slotPadding; + // --------------- + // Node (Icon) Sizing + // --------------- + protected int iconSize = 16; // Icon image size (square). + protected int slotPadding = 4; // Extra padding for the node's box. + protected int slotSize = iconSize + slotPadding; // Overall size of the node box. - // Standard colors for the node background and border. - protected int iconBackgroundColor = 0xFF999999; - protected int iconBorderColor = 0xFF555555; - // Thickness of the border (in pixels). We draw the border lines inside the box so nothing clips. - protected int iconBorderThickness = 1; + // --------------- + // Node Box Appearance + // --------------- + protected int iconBackgroundColor = 0xFF999999; // Background color. + protected int iconBorderColor = 0xFF555555; // Border color. + protected int iconBorderThickness = 1; // Border thickness in pixels. - // Flags for drawing arrow heads and using color scaling on connections. - protected boolean showArrowHeads = true; - protected boolean useColorScaling = true; + // --------------- + // Connection Options + // --------------- + protected boolean showArrowHeads = true; // Whether to draw arrow heads. + protected boolean useColorScaling = true; // Whether to scale connection color based on a percentage. + // --------------- + // Layout Options + // --------------- + public enum DiagramLayout { CIRCULAR, SQUARE, TREE, GENERATED } + protected DiagramLayout layout = DiagramLayout.CIRCULAR; // Default layout. + + /** + * Allows setting the desired layout type. + * This will invalidate the cached positions. + */ + public void setLayout(DiagramLayout layout) { + this.layout = layout; + invalidateCache(); + } + + // --------------- + // Caching + // --------------- + // Cache for positions (computed once for any given icon set & layout). + protected Map cachedPositions = null; + + /** + * Clears the cached positions. + */ + public void invalidateCache() { + cachedPositions = null; + } + + // --------------- + // Constructor + // --------------- public GuiNPCDiagram(int x, int y, int width, int height) { this.x = x; this.y = y; @@ -64,58 +98,21 @@ public abstract class GuiNPCDiagram extends Gui { this.height = height; } - // ------------- - // Data Providers - // ------------- - /** - * Must return the list of icons (nodes) to be drawn. - */ + // --------------- + // Data Providers (abstract methods to be implemented by child classes) + // --------------- protected abstract List getIcons(); - - /** - * Must return the list of connections (arrows) between nodes. - */ protected abstract List getConnections(); - - /** - * Subclass must implement rendering of the icon image itself (without drawing the background box). - * - * @param icon The icon data. - * @param posX The center X position where the icon is drawn. - * @param posY The center Y position. - * @param highlighted Whether this node is currently highlighted. - */ protected abstract void renderIcon(DiagramIcon icon, int posX, int posY, boolean highlighted); - - /** - * Optionally supply tooltip text for an icon. - */ - protected List getIconTooltip(DiagramIcon icon) { - return null; - } - - /** - * Default tooltip for a connection is based on its hoverText. - *

- * Also, if a reciprocal connection exists (i.e. A→B and B→A) then the tooltip will include two lines. - * For example: - *

-     *   A --> Hover Text
-     *   B --> Hover Text
-     * 
- * To display the names of the endpoints, getIconName() is used. - */ + protected List getIconTooltip(DiagramIcon icon) { return null; } protected List getConnectionTooltip(DiagramConnection conn) { List tooltip = new ArrayList<>(); - // Get the display names for the endpoints. DiagramIcon iconFrom = getIconById(conn.idFrom); DiagramIcon iconTo = getIconById(conn.idTo); String nameFrom = iconFrom != null ? getIconName(iconFrom) : "Unknown"; String nameTo = iconTo != null ? getIconName(iconTo) : "Unknown"; - // Add tooltip for the current connection. tooltip.add(nameFrom + " > " + nameTo + ":"); tooltip.add(conn.hoverText); - // Check if there is a reciprocal connection (B→A). DiagramConnection reverse = getConnectionByIds(conn.idTo, conn.idFrom); if (reverse != null) { tooltip.add(nameTo + " > " + nameFrom + ":"); @@ -123,127 +120,237 @@ public abstract class GuiNPCDiagram extends Gui { } return tooltip; } + protected String getIconName(DiagramIcon icon) { return "Icon " + icon.id; } + // --------------- + // Layout Calculation – choose algorithm based on layout type. + // --------------- /** - * Returns a display name for the given icon. - * Child classes may override this method to provide a more meaningful name. - * By default, it returns "Icon ". - */ - protected String getIconName(DiagramIcon icon) { - return "Icon " + icon.id; - } - - // ------------- - // Layout Calculation - // ------------- - /** - * Arranges icons in a circle centered in the diagram area. - * Returns a mapping from an icon’s id to its (x,y) center position. + * Returns a mapping from each icon's id to its (x,y) center position. + * Uses caching so that positions are computed only once (unless invalidated). */ protected Map calculatePositions() { + if (cachedPositions != null) { + return cachedPositions; + } + switch (layout) { + case CIRCULAR: + cachedPositions = calculateCircularPositions(); + break; + case SQUARE: + cachedPositions = calculateSquarePositions(); + break; + case TREE: + cachedPositions = calculateTreePositions(); + break; + case GENERATED: + cachedPositions = calculateGeneratedPositions(); + break; + default: + cachedPositions = calculateCircularPositions(); + break; + } + return cachedPositions; + } + + protected Map calculateCircularPositions() { List icons = getIcons(); Map positions = new HashMap<>(); int count = icons.size(); - // Calculate the center of the diagram area. int centerX = x + width / 2; int centerY = y + height / 2; - // Determine the radius of the circle, leaving room for icons. int radius = Math.min(width, height) / 2 - iconSize - 10; for (int i = 0; i < count; i++) { - // Angle for this icon in radians. double angle = 2 * Math.PI * i / count; - int posX = centerX + (int) (radius * Math.cos(angle)); - int posY = centerY + (int) (radius * Math.sin(angle)); + int posX = centerX + (int)(radius * Math.cos(angle)); + int posY = centerY + (int)(radius * Math.sin(angle)); positions.put(icons.get(i).id, new Point(posX, posY)); } return positions; } - // ------------- - // Standard Icon Box (Background and Border) - // ------------- + protected Map calculateSquarePositions() { + List icons = getIcons(); + Map positions = new HashMap<>(); + int count = icons.size(); + int n = (int)Math.ceil(Math.sqrt(count)); + int gridWidth = width - 20; + int gridHeight = height - 20; + int cellWidth = gridWidth / n; + int cellHeight = gridHeight / n; + int startX = x + (width - gridWidth) / 2; + int startY = y + (height - gridHeight) / 2; + for (int i = 0; i < count; i++) { + int row = i / n; + int col = i % n; + int posX = startX + col * cellWidth + cellWidth / 2; + int posY = startY + row * cellHeight + cellHeight / 2; + positions.put(icons.get(i).id, new Point(posX, posY)); + } + return positions; + } + + protected Map calculateTreePositions() { + List icons = getIcons(); + Map positions = new HashMap<>(); + int count = icons.size(); + int levels = (int)Math.ceil(Math.log(count + 1) / Math.log(2)); + int levelHeight = height / (levels + 1); + int index = 0; + for (int level = 0; level < levels && index < count; level++) { + int nodesThisLevel = (int)Math.min(Math.pow(2, level), count - index); + int levelWidth = width - 20; + int cellWidth = levelWidth / nodesThisLevel; + for (int i = 0; i < nodesThisLevel && index < count; i++, index++) { + int posX = x + 10 + i * cellWidth + cellWidth / 2; + int posY = y + (level + 1) * levelHeight; + positions.put(icons.get(index).id, new Point(posX, posY)); + } + } + return positions; + } + /** - * Draws the background box (with optional highlighting) and border for an icon. - * The box is drawn at (slotX, slotY) with the given slotSize. - * - * @param slotX The top-left x-coordinate of the box. - * @param slotY The top-left y-coordinate of the box. - * @param slotSize The width/height of the box. - * @param highlighted Whether the box should appear highlighted. + * Generated layout: uses a force-directed algorithm to position nodes. + * Computed only once using a fixed seed for determinism. */ + protected Map calculateGeneratedPositions() { + List icons = getIcons(); + Map positions = new HashMap<>(); + int count = icons.size(); + int margin = 20; // margin inside diagram area + + // Fixed seed for deterministic initial placement. + Random rand = new Random(100); + // Initialize nodes at random positions (with margin). + for (DiagramIcon icon : icons) { + int posX = x + margin + rand.nextInt(Math.max(1, width - 2 * margin)); + int posY = y + margin + rand.nextInt(Math.max(1, height - 2 * margin)); + positions.put(icon.id, new Point(posX, posY)); + } + + // Fruchterman–Reingold parameters. + double area = width * height; + // Ideal distance between nodes. + double k = Math.sqrt(area / (double) count); + int iterations = 100; + double temperature = width / 10.0; // initial max displacement + double cooling = temperature / (iterations + 1); + double minDistance = iconSize + slotPadding; // enforce minimal separation + + // Get the list of connections (only actual edges). + List connections = getConnections(); + + for (int iter = 0; iter < iterations; iter++) { + // Initialize displacement for each node. + Map disp = new HashMap<>(); + for (DiagramIcon v : icons) { + disp.put(v.id, new double[] {0.0, 0.0}); + } + + // Repulsive forces: for every pair of nodes. + for (int i = 0; i < icons.size(); i++) { + DiagramIcon v = icons.get(i); + Point posV = positions.get(v.id); + for (int j = i + 1; j < icons.size(); j++) { + DiagramIcon u = icons.get(j); + Point posU = positions.get(u.id); + double dx = posV.x - posU.x; + double dy = posV.y - posU.y; + double distance = Math.sqrt(dx * dx + dy * dy); + if (distance < minDistance) distance = minDistance; + double repForce = (k * k) / distance; + double[] dispV = disp.get(v.id); + double[] dispU = disp.get(u.id); + dispV[0] += (dx / distance) * repForce; + dispV[1] += (dy / distance) * repForce; + dispU[0] -= (dx / distance) * repForce; + dispU[1] -= (dy / distance) * repForce; + } + } + + // Attractive forces: only for nodes that are connected. + for (DiagramConnection conn : connections) { + Point posV = positions.get(conn.idFrom); + Point posU = positions.get(conn.idTo); + double dx = posV.x - posU.x; + double dy = posV.y - posU.y; + double distance = Math.sqrt(dx * dx + dy * dy); + if (distance < 0.01) distance = 0.01; + double attrForce = (distance * distance) / k; + double[] dispV = disp.get(conn.idFrom); + double[] dispU = disp.get(conn.idTo); + dispV[0] -= (dx / distance) * attrForce; + dispV[1] -= (dy / distance) * attrForce; + dispU[0] += (dx / distance) * attrForce; + dispU[1] += (dy / distance) * attrForce; + } + + // Update positions based on displacement vectors. + for (DiagramIcon v : icons) { + double[] d = disp.get(v.id); + double dispLength = Math.sqrt(d[0] * d[0] + d[1] * d[1]); + if (dispLength < 0.01) dispLength = 0.01; + int newX = positions.get(v.id).x + (int)((d[0] / dispLength) * Math.min(dispLength, temperature)); + int newY = positions.get(v.id).y + (int)((d[1] / dispLength) * Math.min(dispLength, temperature)); + newX = Math.max(x + margin, Math.min(x + width - margin, newX)); + newY = Math.max(y + margin, Math.min(y + height - margin, newY)); + positions.get(v.id).x = newX; + positions.get(v.id).y = newY; + } + temperature -= cooling; + } + return positions; + } + + // --------------- + // Standard Icon Box Drawing (Background + Border) + // --------------- protected void drawIconBox(int slotX, int slotY, int slotSize, boolean highlighted) { - // If highlighted, mix the background color with white a little. int bg = highlighted ? mixColors(iconBackgroundColor, 0xFFFFFFFF, 0.2f) : iconBackgroundColor; - // Draw the box background. drawRect(slotX, slotY, slotX + slotSize, slotY + slotSize, bg); - // Draw the border lines inside the box. for (int i = 0; i < iconBorderThickness; i++) { - // Draw horizontal top and bottom borders. drawHorizontalLine(slotX + i, slotX + slotSize - i, slotY + i, iconBorderColor); drawHorizontalLine(slotX + i, slotX + slotSize - i, slotY + slotSize - 1 - i, iconBorderColor); - // Draw vertical left and right borders. drawVerticalLine(slotX + i, slotY + i, slotY + slotSize - i, iconBorderColor); drawVerticalLine(slotX + slotSize - 1 - i, slotY + i, slotY + slotSize - i, iconBorderColor); } } - /** - * Helper to mix two ARGB colors by a given ratio. - * If ratio is 0, returns color1; if 1, returns color2. - */ protected int mixColors(int color1, int color2, float ratio) { int a1 = (color1 >> 24) & 0xFF, r1 = (color1 >> 16) & 0xFF, g1 = (color1 >> 8) & 0xFF, b1 = color1 & 0xFF; int a2 = (color2 >> 24) & 0xFF, r2 = (color2 >> 16) & 0xFF, g2 = (color2 >> 8) & 0xFF, b2 = color2 & 0xFF; - int a = (int) (a1 + (a2 - a1) * ratio); - int r = (int) (r1 + (r2 - r1) * ratio); - int g = (int) (g1 + (g2 - g1) * ratio); - int b = (int) (b1 + (b2 - b1) * ratio); + int a = (int)(a1 + (a2 - a1) * ratio); + int r = (int)(r1 + (r2 - r1) * ratio); + int g = (int)(g1 + (g2 - g1) * ratio); + int b = (int)(b1 + (b2 - b1) * ratio); return (a << 24) | (r << 16) | (g << 8) | b; } - // ------------- + // --------------- // Main Draw Method - // ------------- - /** - * Draws the entire diagram: nodes (with their standard boxes) and connections (with arrow heads). - * Also performs hit testing to determine which icon or connection is being hovered, - * so that appropriate highlighting and tooltips can be shown. - * - * @param mouseX The current mouse x-coordinate. - * @param mouseY The current mouse y-coordinate. - */ + // --------------- public void drawDiagram(int mouseX, int mouseY) { - // Handle zoom changes from the scroll wheel. handleMouseScroll(Mouse.getDWheel()); Minecraft mc = Minecraft.getMinecraft(); ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); int factor = sr.getScaleFactor(); - // Calculate positions for all icons. Map positions = calculatePositions(); - // Determine the center of the diagram. int centerX = x + width / 2, centerY = y + height / 2; - // Transform mouse coordinates to account for zoom (for hit testing). - int tMouseX = centerX + (int) ((mouseX - centerX) / zoom); - int tMouseY = centerY + (int) ((mouseY - centerY) / zoom); + int tMouseX = centerX + (int)((mouseX - centerX) / zoom); + int tMouseY = centerY + (int)((mouseY - centerY) / zoom); - // ------------- - // Hit Testing & Selection - // ------------- Integer hoveredIconId = null; int hoveredConnFrom = -1, hoveredConnTo = -1; - // This set will contain IDs of icons that should be drawn highlighted. HashSet selectedIconIds = new HashSet<>(); - // Check if the mouse is over any icon. for (DiagramIcon icon : getIcons()) { Point pos = positions.get(icon.id); if (pos == null) continue; - // Calculate the top-left corner of the icon box. - int slotX = pos.x + (int) panX - slotSize / 2; - int slotY = pos.y + (int) panY - slotSize / 2; - // If the transformed mouse is within the box... + int slotX = pos.x + (int)panX - slotSize / 2; + int slotY = pos.y + (int)panY - slotSize / 2; if (tMouseX >= slotX && tMouseX < slotX + slotSize && tMouseY >= slotY && tMouseY < slotY + slotSize) { hoveredIconId = icon.id; @@ -251,7 +358,6 @@ public abstract class GuiNPCDiagram extends Gui { break; } } - // If an icon is hovered, also mark all icons connected to it. if (hoveredIconId != null) { for (DiagramConnection conn : getConnections()) { if (conn.idFrom == hoveredIconId || conn.idTo == hoveredIconId) { @@ -260,7 +366,6 @@ public abstract class GuiNPCDiagram extends Gui { } } } else { - // If no icon is hovered, check if the mouse is near any connection line. final double threshold = 5.0; outer: for (DiagramConnection conn : getConnections()) { @@ -279,60 +384,39 @@ public abstract class GuiNPCDiagram extends Gui { } } - // ------------- - // Clipping & Background - // ------------- GL11.glEnable(GL11.GL_SCISSOR_TEST); - // Set the scissor rectangle to the diagram area. GL11.glScissor(x * factor, (sr.getScaledHeight() - (y + height)) * factor, width * factor, height * factor); - // Draw the overall diagram background. drawRect(x, y, x + width, y + height, 0xFF333333); - // ------------- - // Zoom Transformation - // ------------- GL11.glPushMatrix(); GL11.glTranslatef(centerX, centerY, 0); GL11.glScalef(zoom, zoom, 1.0f); GL11.glTranslatef(-centerX, -centerY, 0); - // ------------- - // Draw Connections (Arrows) - // ------------- for (DiagramConnection conn : getConnections()) { Point pFrom = positions.get(conn.idFrom); Point pTo = positions.get(conn.idTo); if (pFrom == null || pTo == null) continue; - int ax = pFrom.x + (int) panX; - int ay = pFrom.y + (int) panY; - int bx = pTo.x + (int) panX; - int by = pTo.y + (int) panY; - // Dim the connection if both endpoints are not selected. + int ax = pFrom.x + (int)panX; + int ay = pFrom.y + (int)panY; + int bx = pTo.x + (int)panX; + int by = pTo.y + (int)panY; boolean dim = !selectedIconIds.isEmpty() && !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo)); drawConnectionLine(ax, ay, bx, by, conn, dim); } - // ------------- - // Draw Icons (Nodes) - // ------------- for (DiagramIcon icon : getIcons()) { Point pos = positions.get(icon.id); if (pos == null) continue; - int slotX = pos.x + (int) panX - slotSize / 2; - int slotY = pos.y + (int) panY - slotSize / 2; - // If this icon's id is in the selected set, consider it highlighted. + int slotX = pos.x + (int)panX - slotSize / 2; + int slotY = pos.y + (int)panY - slotSize / 2; boolean highlighted = !selectedIconIds.isEmpty() && selectedIconIds.contains(icon.id); - // First draw the standardized icon box (background and border). drawIconBox(slotX, slotY, slotSize, highlighted); - // Then let the subclass render the icon image. - renderIcon(icon, pos.x + (int) panX, pos.y + (int) panY, highlighted); + renderIcon(icon, pos.x + (int)panX, pos.y + (int)panY, highlighted); } GL11.glPopMatrix(); - // ------------- - // Draw Arrow Heads (if enabled) - // ------------- if (showArrowHeads) { GL11.glPushMatrix(); GL11.glTranslatef(centerX, centerY, 0); @@ -342,22 +426,18 @@ public abstract class GuiNPCDiagram extends Gui { Point pFrom = positions.get(conn.idFrom); Point pTo = positions.get(conn.idTo); if (pFrom == null || pTo == null) continue; - int ax = pFrom.x + (int) panX; - int ay = pFrom.y + (int) panY; - int bx = pTo.x + (int) panX; - int by = pTo.y + (int) panY; + int ax = pFrom.x + (int)panX; + int ay = pFrom.y + (int)panY; + int bx = pTo.x + (int)panX; + int by = pTo.y + (int)panY; boolean dim = !selectedIconIds.isEmpty() && !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo)); drawArrowHead(ax, ay, bx, by, conn, dim); } GL11.glPopMatrix(); } - GL11.glDisable(GL11.GL_SCISSOR_TEST); - // ------------- - // Draw Tooltips - // ------------- if (hoveredIconId != null) { DiagramIcon icon = getIconById(hoveredIconId); List tooltip = getIconTooltip(icon); @@ -373,12 +453,6 @@ public abstract class GuiNPCDiagram extends Gui { } } - // ------------- - // Helper Methods for Hit Testing & Drawing - // ------------- - /** - * Returns the DiagramIcon with the given id, or null if not found. - */ protected DiagramIcon getIconById(int id) { for (DiagramIcon icon : getIcons()) { if (icon.id == id) @@ -387,9 +461,6 @@ public abstract class GuiNPCDiagram extends Gui { return null; } - /** - * Returns the DiagramConnection connecting idFrom to idTo, or null if not found. - */ protected DiagramConnection getConnectionByIds(int idFrom, int idTo) { for (DiagramConnection conn : getConnections()) { if (conn.idFrom == idFrom && conn.idTo == idTo) @@ -398,32 +469,16 @@ public abstract class GuiNPCDiagram extends Gui { return null; } - /** - * Draws a connection line between two points. - * - * @param x1 Source x-coordinate. - * @param y1 Source y-coordinate. - * @param x2 Target x-coordinate. - * @param y2 Target y-coordinate. - * @param conn The connection data. - * @param dim Whether to draw a dimmed (less bright) version. - */ protected void drawConnectionLine(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) { int color = getConnectionColor(conn); - if (!useColorScaling) { - color = 0xFFFFFFFF; - } + if (!useColorScaling) { color = 0xFFFFFFFF; } GL11.glPushAttrib(GL11.GL_ENABLE_BIT); GL11.glDisable(GL11.GL_TEXTURE_2D); GL11.glLineWidth(2.0F); - float r = ((color >> 16) & 0xFF) / 255f; - float g = ((color >> 8) & 0xFF) / 255f; - float b = (color & 0xFF) / 255f; - if (dim) { - r *= 0.4f; - g *= 0.4f; - b *= 0.4f; - } + float r = ((color >> 16)&0xFF)/255f; + float g = ((color >> 8)&0xFF)/255f; + float b = (color&0xFF)/255f; + if (dim) { r*=0.4f; g*=0.4f; b*=0.4f; } GL11.glColor4f(r, g, b, 1f); GL11.glBegin(GL11.GL_LINES); GL11.glVertex2f(x1, y1); @@ -433,49 +488,27 @@ public abstract class GuiNPCDiagram extends Gui { GL11.glPopAttrib(); } - /** - * Draws an arrow head at the target end of a connection. - * - * @param x1 Source x-coordinate. - * @param y1 Source y-coordinate. - * @param x2 Target x-coordinate. - * @param y2 Target y-coordinate. - * @param conn The connection data. - * @param dim Whether to draw a dimmed version. - */ protected void drawArrowHead(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) { - float dx = x2 - x1; - float dy = y2 - y1; - float len = (float) Math.sqrt(dx * dx + dy * dy); - if (len == 0) return; - // Compute the unit vector from source to target. - float ux = dx / len, uy = dy / len; - // Offset from target center by half the slot size to get the edge of the icon box. - float defenderEdgeX = x2 - ux * (slotSize / 2f); - float defenderEdgeY = y2 - uy * (slotSize / 2f); - - int arrowSize = 6; // Adjustable arrow size. - double angle = Math.atan2(uy, ux); - // Calculate the left and right vertices of the arrow head triangle. - float leftX = (float) (defenderEdgeX - arrowSize * Math.cos(angle - Math.PI / 6)); - float leftY = (float) (defenderEdgeY - arrowSize * Math.sin(angle - Math.PI / 6)); - float rightX = (float) (defenderEdgeX - arrowSize * Math.cos(angle + Math.PI / 6)); - float rightY = (float) (defenderEdgeY - arrowSize * Math.sin(angle + Math.PI / 6)); - + float dx = x2 - x1, dy = y2 - y1; + float len = (float)Math.sqrt(dx*dx+dy*dy); + if (len==0)return; + float ux = dx/len, uy = dy/len; + float defenderEdgeX = x2 - ux*(slotSize/2f); + float defenderEdgeY = y2 - uy*(slotSize/2f); + int arrowSize = 6; + double angle = Math.atan2(uy,ux); + float leftX = (float)(defenderEdgeX - arrowSize * Math.cos(angle - Math.PI/6)); + float leftY = (float)(defenderEdgeY - arrowSize * Math.sin(angle - Math.PI/6)); + float rightX = (float)(defenderEdgeX - arrowSize * Math.cos(angle + Math.PI/6)); + float rightY = (float)(defenderEdgeY - arrowSize * Math.sin(angle + Math.PI/6)); int color = getConnectionColor(conn); - if (!useColorScaling) { - color = 0xFFFFFFFF; - } + if (!useColorScaling) { color = 0xFFFFFFFF; } GL11.glPushAttrib(GL11.GL_ENABLE_BIT); GL11.glDisable(GL11.GL_TEXTURE_2D); - float r = ((color >> 16) & 0xFF) / 255f; - float g = ((color >> 8) & 0xFF) / 255f; - float b = (color & 0xFF) / 255f; - if (dim) { - r *= 0.4f; - g *= 0.4f; - b *= 0.4f; - } + float r = ((color >> 16)&0xFF)/255f; + float g = ((color >> 8)&0xFF)/255f; + float b = (color&0xFF)/255f; + if(dim){ r*=0.4f; g*=0.4f; b*=0.4f; } GL11.glColor4f(r, g, b, 1f); GL11.glDisable(GL11.GL_CULL_FACE); GL11.glBegin(GL11.GL_TRIANGLES); @@ -488,55 +521,41 @@ public abstract class GuiNPCDiagram extends Gui { GL11.glPopAttrib(); } - /** - * Computes a color for a connection based on its percentage value. - * Uses different thresholds to return a base color, and interpolates for higher percentages. - */ protected int getConnectionColor(DiagramConnection conn) { float percent = conn.percent; - if (percent <= 0.10f) return 0xFF80FF80; // light green - else if (percent <= 0.40f) return 0xFFCCFF66; // green-yellow - else if (percent <= 0.50f) return 0xFFFFA500; // orange + if (percent<=0.10f)return 0xFF80FF80; + else if (percent<=0.40f)return 0xFFCCFF66; + else if (percent<=0.50f)return 0xFFFFA500; else { - float ratio = (percent - 0.50f) / 0.50f; + float ratio = (percent-0.50f)/0.50f; ratio = Math.min(1f, Math.max(0f, ratio)); int r1 = 0xFF, g1 = 0xA5, b1 = 0x00; int r2 = 0xFF, g2 = 0x00, b2 = 0x00; - int r = r1 + (int) ((r2 - r1) * ratio); - int g = g1 + (int) ((g2 - g1) * ratio); - int b = b1 + (int) ((b2 - b1) * ratio); + int r = r1 + (int)((r2-r1)*ratio); + int g = g1 + (int)((g2-g1)*ratio); + int b = b1 + (int)((b2-b1)*ratio); return (0xFF << 24) | (r << 16) | (g << 8) | b; } } - /** - * Utility method: returns the shortest distance from a point (px,py) to the line segment defined by (x1,y1)-(x2,y2). - */ private double pointLineDistance(double px, double py, double x1, double y1, double x2, double y2) { double A = px - x1, B = py - y1, C = x2 - x1, D = y2 - y1; double dot = A * C + B * D; double lenSq = C * C + D * D; - double param = (lenSq != 0) ? (dot / lenSq) : -1; + double param = (lenSq!=0)?(dot/lenSq):-1; double xx, yy; - if (param < 0) { - xx = x1; - yy = y1; - } else if (param > 1) { - xx = x2; - yy = y2; - } else { - xx = x1 + param * C; - yy = y1 + param * D; - } - double dx = px - xx, dy = py - yy; - return Math.sqrt(dx * dx + dy * dy); + if(param<0){ xx = x1; yy = y1; } + else if(param>1){ xx = x2; yy = y2; } + else { xx = x1+param*C; yy = y1+param*D; } + double dx = px-xx, dy = py-yy; + return Math.sqrt(dx*dx+dy*dy); } - // ------------- + // --------------- // Mouse Handling - // ------------- + // --------------- public boolean mouseClicked(int mouseX, int mouseY, int mouseButton) { - if (mouseButton == 0 && isWithin(mouseX, mouseY)) { + if (mouseButton==0 && isWithin(mouseX, mouseY)) { dragging = true; lastDragX = mouseX; lastDragY = mouseY; @@ -546,12 +565,11 @@ public abstract class GuiNPCDiagram extends Gui { } public void mouseClickMove(int mouseX, int mouseY, int mouseButton, long timeSinceLastClick) { - if (dragging) { - int dx = mouseX - lastDragX; - int dy = mouseY - lastDragY; - // Adjust panning speed relative to zoom. - panX += dx / zoom * 0.7f; - panY += dy / zoom * 0.7f; + if(dragging){ + int dx = mouseX-lastDragX; + int dy = mouseY-lastDragY; + panX += dx/zoom*0.7f; + panY += dy/zoom*0.7f; lastDragX = mouseX; lastDragY = mouseY; } @@ -562,29 +580,28 @@ public abstract class GuiNPCDiagram extends Gui { } protected boolean isWithin(int mouseX, int mouseY) { - return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; + return mouseX >= x && mouseX <= x+width && mouseY >= y && mouseY <= y+height; } /** - * Handles mouse wheel scrolling for zooming in/out. + * Handles zoom in/out based on mouse wheel scrolling. */ public void handleMouseScroll(int scrollDelta) { zoom += scrollDelta * 0.0009f; - if (zoom < 0.5f) zoom = 0.5f; - if (zoom > 2.0f) zoom = 2.0f; + if(zoom<0.5f) zoom = 0.5f; + if(zoom>2.0f) zoom = 2.0f; } /** - * Override this method if you want to render tooltips differently. - * By default, it does nothing. + * Override this method if you wish to render tooltips differently. */ protected void drawHoveringText(List text, int mouseX, int mouseY, FontRenderer fontRenderer) { // Default implementation is empty. } - // ------------- + // --------------- // Flag Setters - // ------------- + // --------------- public void setShowArrowHeads(boolean showArrowHeads) { this.showArrowHeads = showArrowHeads; } @@ -593,24 +610,14 @@ public abstract class GuiNPCDiagram extends Gui { this.useColorScaling = useColorScaling; } - // ------------- + // --------------- // Inner Classes for Data Wrapping - // ------------- - /** - * A simple wrapper class for a node/icon. - */ + // --------------- public static class DiagramIcon { public int id; - public DiagramIcon(int id) { - this.id = id; - } + public DiagramIcon(int id) { this.id = id; } } - /** - * A simple wrapper class for a connection (arrow) between nodes. - * Contains the source node id (idFrom), target node id (idTo), a percentage value (for color scaling), - * and a hover text. - */ public static class DiagramConnection { public int idFrom, idTo; public float percent;