From 9b63b5175981a4cd540a194ba03e0206df0fc2f5 Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Thu, 12 Mar 2026 22:16:28 +0200 Subject: [PATCH] refactor(gui): replace JPanel with AWT Canvas for rendering Convert ViewPanel from Swing JPanel to AWT Canvas to use BufferStrategy for page-flipping rendering. This provides better control over the rendering pipeline and avoids Swing's double-buffering overhead. Also fixes a typo in Line.java (greenWithAplha -> greenWithAlpha) and makes Color.a field final. --- .../svjatoslav/sixth/e3d/gui/ViewPanel.java | 116 ++++++++++-------- .../e3d/gui/humaninput/InputManager.java | 14 +-- .../sixth/e3d/renderer/raster/Color.java | 2 +- .../raster/shapes/basic/line/Line.java | 4 +- 4 files changed, 76 insertions(+), 60 deletions(-) diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewPanel.java index 25a73f5..cca1c1b 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewPanel.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewPanel.java @@ -8,27 +8,29 @@ import eu.svjatoslav.sixth.e3d.gui.humaninput.InputManager; import eu.svjatoslav.sixth.e3d.gui.humaninput.KeyboardFocusStack; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; -import javax.swing.*; import java.awt.*; +import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; -import java.awt.event.ComponentListener; +import java.awt.image.BufferStrategy; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** - * Java Swing panel that provides a 3D rendering canvas with built-in camera navigation. + * AWT Canvas that provides a 3D rendering surface with built-in camera navigation. * *

{@code ViewPanel} is the primary entry point for embedding the Sixth 3D engine into - * a Java Swing application. It manages the render loop, maintains a scene graph + * a Java application. It manages the render loop, maintains a scene graph * ({@link ShapeCollection}), and handles user input for camera navigation.

* + *

Uses {@link BufferStrategy} for efficient page-flipping and tear-free rendering.

+ * *

Quick start - creating a 3D view in a window:

*
{@code
  * // Option 1: Use ViewFrame (creates a maximized JFrame for you)
  * ViewFrame frame = new ViewFrame();
  * ViewPanel viewPanel = frame.getViewPanel();
  *
- * // Option 2: Embed ViewPanel in your own Swing layout
+ * // Option 2: Embed ViewPanel in your own window
  * JFrame frame = new JFrame("My 3D App");
  * ViewPanel viewPanel = new ViewPanel();
  * frame.add(viewPanel);
@@ -66,8 +68,10 @@ import java.util.concurrent.ConcurrentHashMap;
  * @see Camera the camera/viewer
  * @see FrameListener for per-frame callbacks
  */
-public class ViewPanel extends JPanel implements ComponentListener {
+public class ViewPanel extends Canvas {
     private static final long serialVersionUID = 1683277888885045387L;
+    private static final int NUM_BUFFERS = 2;
+
     private final InputManager inputManager = new InputManager(this);
     private final KeyboardFocusStack keyboardFocusStack;
     private final Camera camera = new Camera();
@@ -107,25 +111,33 @@ public class ViewPanel extends JPanel implements ComponentListener {
 
     private long nextFrameTime;
 
+    private BufferStrategy bufferStrategy;
+
+    private boolean bufferStrategyInitialized = false;
+
     public ViewPanel() {
         frameListeners.add(camera);
         frameListeners.add(inputManager);
 
         keyboardFocusStack = new KeyboardFocusStack(this);
 
-        initializePanelLayout();
+        initializeCanvas();
 
         startRenderThread();
 
-        addComponentListener(this);
+        addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentResized(final ComponentEvent e) {
+                viewRepaintNeeded = true;
+            }
+
+            @Override
+            public void componentShown(final ComponentEvent e) {
+                viewRepaintNeeded = true;
+            }
+        });
     }
 
-    /**
-     * Returns the camera that represents the viewer's position and
-     * orientation in the 3D world. Use this to programmatically move the camera.
-     *
-     * @return the camera for this view
-     */
     public Camera getCamera() {
         return camera;
     }
@@ -181,28 +193,8 @@ public class ViewPanel extends JPanel implements ComponentListener {
     }
 
     @Override
-    public void componentHidden(final ComponentEvent e) {
-
-    }
-
-    @Override
-    public void componentMoved(final ComponentEvent e) {
-
-    }
-
-    @Override
-    public void componentResized(final ComponentEvent e) {
-        viewRepaintNeeded = true;
-    }
-
-    @Override
-    public void componentShown(final ComponentEvent e) {
-        viewRepaintNeeded = true;
-    }
-
-    @Override
-    public Dimension getMaximumSize() {
-        return getPreferredSize();
+    public Dimension getPreferredSize() {
+        return new Dimension(640, 480);
     }
 
     @Override
@@ -211,32 +203,57 @@ public class ViewPanel extends JPanel implements ComponentListener {
     }
 
     @Override
-    public java.awt.Dimension getPreferredSize() {
-        return new java.awt.Dimension(640, 480);
+    public Dimension getMaximumSize() {
+        return getPreferredSize();
     }
 
     public RenderingContext getRenderingContext() {
         return renderingContext;
     }
 
-    private void initializePanelLayout() {
-        setFocusCycleRoot(true);
-        setOpaque(true);
+    private void initializeCanvas() {
         setFocusable(true);
-        setDoubleBuffered(false);
         setVisible(true);
-        requestFocusInWindow();
+        requestFocus();
+    }
+
+    private void ensureBufferStrategy() {
+        if (bufferStrategyInitialized && bufferStrategy != null)
+            return;
+
+        if (!isDisplayable())
+            return;
+
+        try {
+            createBufferStrategy(NUM_BUFFERS);
+            bufferStrategy = getBufferStrategy();
+            bufferStrategyInitialized = true;
+        } catch (final Exception e) {
+            bufferStrategy = null;
+            bufferStrategyInitialized = false;
+        }
     }
 
     private void renderFrame() {
-        // paint root geometry collection to the offscreen render buffer
+        ensureBufferStrategy();
+
+        if (bufferStrategy == null)
+            return;
+
         clearCanvas();
         rootShapeCollection.paint(this, renderingContext);
 
-        // draw rendered offscreen buffer to visible screen
-        final Graphics graphics = getGraphics();
-        if (graphics != null)
-            graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
+        Graphics2D g = null;
+        try {
+            g = (Graphics2D) bufferStrategy.getDrawGraphics();
+            g.drawImage(renderingContext.bufferedImage, 0, 0, null);
+        } finally {
+            if (g != null)
+                g.dispose();
+        }
+
+        if (!bufferStrategy.contentsLost())
+            bufferStrategy.show();
     }
 
     private void clearCanvas() {
@@ -354,7 +371,6 @@ public class ViewPanel extends JPanel implements ComponentListener {
             renderFrame();
             viewRepaintNeeded = renderingContext.handlePossibleComponentMouseEvent();
         }
-
     }
 
     private void maintainRenderingContext() {
@@ -404,4 +420,4 @@ public class ViewPanel extends JPanel implements ComponentListener {
         frameListeners.remove(frameListener);
     }
 
-}
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.java
index 9fb4e16..a13a5cf 100644
--- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.java
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.java
@@ -10,7 +10,7 @@ import eu.svjatoslav.sixth.e3d.gui.FrameListener;
 import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
 import eu.svjatoslav.sixth.e3d.math.Rotation;
 
-import javax.swing.*;
+import java.awt.*;
 import java.awt.event.*;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -20,7 +20,7 @@ import java.util.Map;
 /**
  * Manages mouse and keyboard input for the 3D view.
  *
- * 

Handles Swing mouse/keyboard events, tracks pressed keys and mouse state, + *

Handles mouse/keyboard events, tracks pressed keys and mouse state, * and forwards events to the appropriate handlers. Also provides default camera * control via mouse dragging (look around) and mouse wheel (vertical movement).

* @@ -54,11 +54,11 @@ public class InputManager implements return viewUpdateNeeded; } - private void bind(final JPanel panel) { - panel.addMouseMotionListener(this); - panel.addKeyListener(this); - panel.addMouseListener(this); - panel.addMouseWheelListener(this); + private void bind(final Component component) { + component.addMouseMotionListener(this); + component.addKeyListener(this); + component.addMouseListener(this); + component.addMouseWheelListener(this); } private boolean handleKeyboardEvents() { diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/Color.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/Color.java index e7cb25f..dfa42eb 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/Color.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/Color.java @@ -84,7 +84,7 @@ public final class Color { * 0 - transparent. * 255 - opaque. */ - public int a; + public final int a; private java.awt.Color cachedAwtColor; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java index 079f0ab..7550d09 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java @@ -164,7 +164,7 @@ public class Line extends AbstractCoordinateShape { final int backgroundAlpha = 255 - alpha; final int redWithAlpha = color.r * alpha; - final int greenWithAplha = color.g * alpha; + final int greenWithAlpha = color.g * alpha; final int blueWithAlpha = color.b * alpha; for (int relativeX = 0; relativeX <= lineWidth; relativeX++) { @@ -182,7 +182,7 @@ public class Line extends AbstractCoordinateShape { final int destB = dest & 0xff; final int newR = ((destR * backgroundAlpha) + redWithAlpha) / 256; - final int newG = ((destG * backgroundAlpha) + greenWithAplha) / 256; + final int newG = ((destG * backgroundAlpha) + greenWithAlpha) / 256; final int newB = ((destB * backgroundAlpha) + blueWithAlpha) / 256; pixels[offset] = (newR << 16) | (newG << 8) | newB; -- 2.20.1