this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

improve map thumbnails

+112 -65
+45 -10
src/main/java/app/pane/AssetsPanel.java
··· 60 60 breadcrumbBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); 61 61 add(breadcrumbBar, "growx, wrap"); 62 62 63 - resultsPanel = new JPanel(new UniformGridLayout(80, 80, 0, 0)); 63 + resultsPanel = new JPanel(new UniformGridLayout(88, 88, 2, 2)); 64 64 resultsPanel.addMouseListener(new MouseAdapter() { 65 65 @Override 66 66 public void mouseClicked(MouseEvent e) ··· 213 213 214 214 JLabel icon = new JLabel(ThemedIcon.FOLDER_OPEN_24); 215 215 JLabel label = new JLabel(name); 216 - label.setPreferredSize(new Dimension(80, 20)); 216 + label.setFont(label.getFont().deriveFont(12f)); 217 217 218 - panel.add(icon, "wrap, align center"); 219 - panel.add(label, "align center, wmax 80"); 218 + panel.add(icon, "wrap, alignx center"); 219 + panel.add(label, "alignx center, aligny center, wmax 80"); 220 220 221 221 panel.addMouseListener(new MouseAdapter() { 222 222 @Override ··· 278 278 } 279 279 }.execute(); 280 280 281 - JLabel name = new JLabel(asset.getAssetName()); 282 - name.setPreferredSize(new Dimension(80, 20)); 281 + JLabel name = createShadowLabel(asset.getAssetName()); 283 282 284 - panel.add(icon, "wrap, align center"); 285 - panel.add(name, "align center, wmax 80"); 283 + panel.add(icon, "alignx center"); 284 + panel.add(name, "pos 0 null 100% (100%-4), alignx center"); 285 + panel.setComponentZOrder(name, 0); 286 286 287 287 String desc = asset.getAssetDescription(); 288 288 if (desc != null && !desc.isEmpty()) { ··· 321 321 322 322 private JPanel createItemPanel() 323 323 { 324 - JPanel panel = new JPanel(new MigLayout("ins 4, fill")) { 324 + JPanel panel = new JPanel(new MigLayout("ins 4, fill", "[grow]", "[grow]")) { 325 325 @Override 326 326 protected void paintComponent(Graphics g) 327 327 { ··· 347 347 super.paintComponent(g); 348 348 } 349 349 }; 350 - panel.setPreferredSize(new Dimension(80, 80)); 350 + panel.setPreferredSize(new Dimension(88, 88)); 351 351 panel.setOpaque(false); 352 352 return panel; 353 + } 354 + 355 + private JLabel createShadowLabel(String text) 356 + { 357 + return new JLabel(text) { 358 + @Override 359 + protected void paintComponent(Graphics g) 360 + { 361 + Graphics2D g2 = (Graphics2D) g.create(); 362 + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 363 + g2.setFont(getFont()); 364 + 365 + var fm = g2.getFontMetrics(); 366 + int textW = fm.stringWidth(getText()); 367 + int x = (getWidth() - textW) / 2; 368 + int y = fm.getAscent(); 369 + 370 + // Soft shadow: draw text at nearby offsets with low alpha 371 + int radius = 3; 372 + for (int dx = -radius; dx <= radius; dx++) { 373 + for (int dy = -radius; dy <= radius; dy++) { 374 + float dist = (float) Math.sqrt(dx * dx + dy * dy); 375 + if (dist > radius) 376 + continue; 377 + int alpha = (int)(40 * (1f - dist / radius)); 378 + g2.setColor(new Color(0, 0, 0, alpha)); 379 + g2.drawString(getText(), x + dx, y + dy + 1); 380 + } 381 + } 382 + 383 + g2.setColor(getForeground()); 384 + g2.drawString(getText(), x, y); 385 + g2.dispose(); 386 + } 387 + }; 353 388 } 354 389 355 390 private void openAsset(AssetHandle asset)
+4 -14
src/main/java/assets/ui/MapAsset.java
··· 2 2 3 3 import static app.Directories.PROJ_THUMBNAIL; 4 4 5 - import java.awt.GraphicsEnvironment; 6 5 import java.awt.Image; 7 6 import java.awt.image.BaseMultiResolutionImage; 8 7 import java.io.BufferedReader; ··· 113 112 return new BaseMultiResolutionImage(variants.toArray(new Image[0])); 114 113 } 115 114 115 + public static final int THUMBNAIL_SIZE = 80; 116 + 116 117 /** 117 118 * Generates thumbnails for all maps that don't already have one. 118 119 * Creates a MapEditor instance, so must not be called while one is open. 119 120 */ 120 - private static final int THUMBNAIL_SIZE = 64; 121 - 122 121 public static void generateMissingThumbnails() 123 122 { 124 - boolean hiDpi = GraphicsEnvironment.getLocalGraphicsEnvironment() 125 - .getDefaultScreenDevice().getDefaultConfiguration() 126 - .getDefaultTransform().getScaleX() > 1; 127 - 128 123 MapEditor editor = null; 129 124 130 125 try { 131 126 for (AssetHandle asset : AssetManager.getMapSources()) { 132 127 File thumbFile = new File(PROJ_THUMBNAIL + asset.assetPath + ".png"); 133 128 File thumb2xFile = new File(PROJ_THUMBNAIL + asset.assetPath + "@2x.png"); 134 - boolean need1x = !thumbFile.exists(); 135 - boolean need2x = hiDpi && !thumb2xFile.exists(); 136 - if (!need1x && !need2x) 129 + if (thumbFile.exists() && thumb2xFile.exists()) 137 130 continue; 138 131 Logger.log("Capturing thumbnail for " + asset.assetPath + "...", Priority.MILESTONE); 139 132 if (editor == null) 140 133 editor = new MapEditor(false); 141 - if (need1x) 142 - editor.generateThumbnail(asset, thumbFile, THUMBNAIL_SIZE); 143 - if (need2x) 144 - editor.generateThumbnail(asset, thumb2xFile, THUMBNAIL_SIZE * 2); 134 + editor.generateThumbnail(asset, thumbFile, thumb2xFile, THUMBNAIL_SIZE); 145 135 } 146 136 } 147 137 catch (Exception e) {
+63 -41
src/main/java/game/map/editor/MapEditor.java
··· 7 7 import static org.lwjgl.opengl.GL30.*; 8 8 9 9 import java.awt.Dimension; 10 + import java.awt.Graphics2D; 10 11 import java.awt.KeyEventDispatcher; 11 12 import java.awt.KeyboardFocusManager; 13 + import java.awt.RenderingHints; 12 14 import java.awt.Toolkit; 13 15 import java.awt.Window; 14 16 import java.awt.event.KeyEvent; ··· 735 737 736 738 private boolean thumbnailInitialized = false; 737 739 738 - public void generateThumbnail(File mapFile, File thumbFile, int size) 740 + /** Renders a thumbnail at 4x resolution and saves both the 2x and downsampled 1x images. */ 741 + public void generateThumbnail(File mapFile, File thumbFile1x, File thumbFile2x, int size) 739 742 { 740 743 Map newMap = Map.loadMap(mapFile); 741 744 if (newMap == null) 742 745 return; 743 746 747 + int renderSize = size * 4; 744 748 thumbnailMode = true; 745 - thumbnailSize = size; 749 + thumbnailSize = renderSize; 746 750 openMap(newMap, true); 747 751 for (MapObject obj : getCollisionMap().colliderTree) 748 752 obj.hidden = true; ··· 752 756 if (!thumbnailInitialized) 753 757 initThumbnail(); 754 758 755 - // Isometric camera: 45 degrees right, 45 degrees down 759 + // Isometric camera: 45 degrees right, 30 degrees down - like SMRPG 756 760 var camera = perspectiveView.camera; 757 761 camera.yaw = -45f; 758 - camera.pitch = 45f; 762 + camera.pitch = 30f; 759 763 760 - // Frame the map's model tree AABB 761 - BoundingBox aabb = map.modelTree.getRoot().getUserObject().AABB; 762 - Vector3f center = aabb.getCenter(); 763 - camera.setPosition(center); 764 - 765 - // Project all 8 AABB corners through the view rotation to find 766 - // the screen-space bounding box. View = Rx(pitch) * Ry(yaw) * (p - center). 767 - Vector3f bmin = aabb.getMin(); 768 - Vector3f bmax = aabb.getMax(); 769 - float[] xs = { bmin.x, bmax.x }; 770 - float[] ys = { bmin.y, bmax.y }; 771 - float[] zs = { bmin.z, bmax.z }; 772 - 773 - double yawRad = Math.toRadians(-45); 774 - double pitchRad = Math.toRadians(45); 764 + // Project all vertices through the view rotation to find the tight 765 + // screen-space bounding box. View = Rx(pitch) * Ry(yaw) * v. 766 + double yawRad = Math.toRadians(camera.yaw); 767 + double pitchRad = Math.toRadians(camera.pitch); 775 768 double cy = Math.cos(yawRad), sy = Math.sin(yawRad); 776 769 double cp = Math.cos(pitchRad), sp = Math.sin(pitchRad); 777 770 778 771 float screenMinX = Float.MAX_VALUE, screenMaxX = -Float.MAX_VALUE; 779 772 float screenMinY = Float.MAX_VALUE, screenMaxY = -Float.MAX_VALUE; 780 - for (float wx : xs) { 781 - for (float wy : ys) { 782 - for (float wz : zs) { 783 - // translate to camera-relative 784 - float px = wx - center.x; 785 - float py = wy - center.y; 786 - float pz = wz - center.z; 773 + for (Model mdl : map.modelTree) { 774 + if (!mdl.hasMesh.get()) 775 + continue; 776 + for (Triangle tri : mdl.getMesh()) { 777 + for (Vertex v : tri.vert) { 778 + float px = v.getCurrentX(); 779 + float py = v.getCurrentY(); 780 + float pz = v.getCurrentZ(); 787 781 // Ry(yaw) 788 782 float rx = (float)(px * cy + pz * sy); 789 783 float ry = py; ··· 791 785 // Rx(pitch) 792 786 float sx = rx; 793 787 float syt = (float)(ry * cp - rz * sp); 794 - // screen X = sx, screen Y = syt 795 788 screenMinX = Math.min(screenMinX, sx); 796 789 screenMaxX = Math.max(screenMaxX, sx); 797 790 screenMinY = Math.min(screenMinY, syt); ··· 800 793 } 801 794 } 802 795 796 + // Center the camera on the screen-space midpoint of the geometry. 797 + // Inverse rotation: world = Ry(-yaw) * Rx(-pitch) * screen 798 + float screenCX = (screenMinX + screenMaxX) / 2f; 799 + float screenCY = (screenMinY + screenMaxY) / 2f; 800 + // Rx(-pitch) 801 + float iz = (float)(-screenCY * sp); 802 + float iy = (float)(screenCY * cp); 803 + float ix = screenCX; 804 + // Ry(-yaw) 805 + float wx = (float)(ix * cy - iz * sy); 806 + float wy = iy; 807 + float wz = (float)(ix * sy + iz * cy); 808 + camera.setPosition(new Vector3f(wx, wy, wz)); 809 + 803 810 float halfW = (screenMaxX - screenMinX) / 2f; 804 811 float halfH = (screenMaxY - screenMinY) / 2f; 805 - thumbnailOrthoHalfHeight = Math.max(Math.max(halfW, halfH), 50f); 812 + thumbnailOrthoHalfHeight = Math.max(50f, Math.min(Math.max(halfW, halfH), 5000f)); 806 813 807 - perspectiveView.resize(0, 0, size, size); 814 + perspectiveView.resize(0, 0, renderSize, renderSize); 808 815 809 816 for (int i = 0; i < 2; i++) { 810 817 step(); 811 818 glCanvas.render(); 812 819 } 813 820 814 - renderThumbnail(thumbFile); 821 + renderThumbnail(thumbFile1x, thumbFile2x, size); 815 822 } 816 823 817 824 public void shutdownThumbnail() ··· 3658 3665 thumbnailMode = true; 3659 3666 } 3660 3667 3661 - private void renderThumbnail(File thumbFile) 3668 + private void renderThumbnail(File thumbFile1x, File thumbFile2x, int size) 3662 3669 { 3663 3670 runInContext(() -> { 3664 3671 glBindFramebuffer(GL_READ_FRAMEBUFFER, perspectiveView.getSceneFrameBuffer()); 3665 3672 glReadBuffer(GL_COLOR_ATTACHMENT0); 3666 - int size = thumbnailSize; 3673 + int renderSize = thumbnailSize; 3667 3674 int bpp = 4; 3668 - ByteBuffer buffer = BufferUtils.createByteBuffer(size * size * bpp); 3669 - glReadPixels(0, 0, size, size, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer); 3675 + ByteBuffer buffer = BufferUtils.createByteBuffer(renderSize * renderSize * bpp); 3676 + glReadPixels(0, 0, renderSize, renderSize, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer); 3670 3677 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); 3671 3678 3672 - BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); 3673 - for (int x = 0; x < size; x++) { 3674 - for (int y = 0; y < size; y++) { 3675 - int i = (x + (size * y)) * bpp; 3679 + var image4x = new BufferedImage(renderSize, renderSize, BufferedImage.TYPE_INT_ARGB); 3680 + for (int x = 0; x < renderSize; x++) { 3681 + for (int y = 0; y < renderSize; y++) { 3682 + int i = (x + (renderSize * y)) * bpp; 3676 3683 int r = buffer.get(i) & 0xFF; 3677 3684 int g = buffer.get(i + 1) & 0xFF; 3678 3685 int b = buffer.get(i + 2) & 0xFF; 3679 3686 int a = buffer.get(i + 3) & 0xFF; 3680 - image.setRGB(x, size - (y + 1), (a << 24) | (r << 16) | (g << 8) | b); 3687 + image4x.setRGB(x, renderSize - (y + 1), (a << 24) | (r << 16) | (g << 8) | b); 3681 3688 } 3682 3689 } 3683 3690 3691 + // Downsample to 2x and 1x with bilinear filtering 3692 + int size2x = size * 2; 3693 + var image2x = new BufferedImage(size2x, size2x, BufferedImage.TYPE_INT_ARGB); 3694 + Graphics2D g2 = image2x.createGraphics(); 3695 + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 3696 + g2.drawImage(image4x, 0, 0, size2x, size2x, null); 3697 + g2.dispose(); 3698 + 3699 + var image1x = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); 3700 + Graphics2D g1 = image1x.createGraphics(); 3701 + g1.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 3702 + g1.drawImage(image2x, 0, 0, size, size, null); 3703 + g1.dispose(); 3704 + 3684 3705 try { 3685 - FileUtils.forceMkdirParent(thumbFile); 3686 - ImageIO.write(image, "PNG", thumbFile); 3706 + FileUtils.forceMkdirParent(thumbFile1x); 3707 + ImageIO.write(image1x, "PNG", thumbFile1x); 3708 + ImageIO.write(image2x, "PNG", thumbFile2x); 3687 3709 } 3688 3710 catch (IOException e) { 3689 3711 Logger.printStackTrace(e);