X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=blobdiff_plain;f=src%2Fmain%2Fjava%2Feu%2Fsvjatoslav%2Fsixth%2Fe3d%2Fgui%2FViewPanel.java;h=e068bf50f670c49f2e2fdbf9446d37d22bbcbc84;hb=HEAD;hp=87f0dd39fefedae9976b0b54e46ad2e7822e9fa9;hpb=96f76bab30b47db83a1c9061b8916c69657f1e13;p=sixth-3d.git 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 87f0dd3..e068bf5 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewPanel.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewPanel.java @@ -1,57 +1,89 @@ /* - * Sixth 3D engine. Copyright ©2012-2018, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of version 3 of the GNU Lesser General Public License - * or later as published by the Free Software Foundation. - * + * Sixth 3D engine. Author: Svjatoslav Agejenko. + * This project is released under Creative Commons Zero (CC0) license. */ - package eu.svjatoslav.sixth.e3d.gui; -import eu.svjatoslav.sixth.e3d.gui.humaninput.MouseInteractionController; +import eu.svjatoslav.sixth.e3d.gui.humaninput.HIDEventTracker; +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.ComponentEvent; import java.awt.event.ComponentListener; -import java.util.ArrayList; -import java.util.List; +import java.util.Set; import java.util.Timer; +import java.util.concurrent.ConcurrentHashMap; +/** + * Java Swing GUI panel that contains canvas for 3D rendering. + * Usually it is used as a part of {@link ViewFrame}. + */ public class ViewPanel extends JPanel implements ComponentListener { - private static final long serialVersionUID = 1683277888885045387L; - private final List viewRenderListeners = new ArrayList<>(); - private final ViewContext context = new ViewContext(this); + private final HIDEventTracker HIDEventTracker = new HIDEventTracker(this); + private final KeyboardFocusStack keyboardFocusStack; + private final Avatar avatar = new Avatar(); + private final ShapeCollection rootShapeCollection = new ShapeCollection(); + private final Set viewRenderListeners = ConcurrentHashMap.newKeySet(); + public Color backgroundColor = Color.BLACK; + /** - * Last time this view was updated. + * Stores milliseconds when last frame was updated. This is needed to calculate time delta between frames. + * Time delta is used to calculate smooth animation. */ private long lastUpdateMillis = 0; + + /** + * Timer that is used to update canvas at target FPS rate. + */ private Timer canvasUpdateTimer; + private ViewUpdateTimerTask canvasUpdateTimerTask; private RenderingContext renderingContext = null; - private MouseInteractionController currentMouseOverComponent; + + /** + * Currently target frames per second rate for this view. Target FPS can be changed at runtime. + * 3D engine tries to be smart and only repaints screen when there are visible changes. + */ + private int targetFPS = 60; + /** - * Currently target FPS for this view. It might change at runtime. + * Set to true if it is known than next frame reeds to be painted. Flag is cleared + * immediately after frame got updated. */ - private int targetFramerate = 30; - private boolean repaintDuringNextViewUpdate = true; + private boolean viewRepaintNeeded = true; public ViewPanel() { - viewRenderListeners.add(context.getAvatar()); + viewRenderListeners.add(avatar); + viewRenderListeners.add(HIDEventTracker); - // initialize input tracker - context.getUserInputTracker().bind(this); - viewRenderListeners.add(context.getUserInputTracker()); + keyboardFocusStack = new KeyboardFocusStack(this); initializePanelLayout(); - setFrameRate(targetFramerate); + setFrameRate(targetFPS); addComponentListener(this); } + public Avatar getAvatar() { + return avatar; + } + + public KeyboardFocusStack getKeyboardFocusStack() { + return keyboardFocusStack; + } + + public ShapeCollection getRootShapeCollection() { + return rootShapeCollection; + } + + public HIDEventTracker getHIDInputTracker() { + return HIDEventTracker; + } + public void addViewUpdateListener(final ViewRenderListener listener) { viewRenderListeners.add(listener); } @@ -68,16 +100,12 @@ public class ViewPanel extends JPanel implements ComponentListener { @Override public void componentResized(final ComponentEvent e) { - repaintDuringNextViewUpdate = true; + viewRepaintNeeded = true; } @Override public void componentShown(final ComponentEvent e) { - repaintDuringNextViewUpdate = true; - } - - public ViewContext getContext() { - return context; + viewRepaintNeeded = true; } @Override @@ -95,40 +123,10 @@ public class ViewPanel extends JPanel implements ComponentListener { return new java.awt.Dimension(640, 480); } - public RenderingContext getRenderBuffer() { - return renderingContext; - } - public RenderingContext getRenderingContext() { return renderingContext; } - private void handleDetectedComponentMouseEvents() { - if (renderingContext.clickedItem != null) { - if (renderingContext.mouseClick.button == 0) { - // mouse over - if (currentMouseOverComponent == null) { - currentMouseOverComponent = renderingContext.clickedItem; - currentMouseOverComponent.mouseEntered(); - repaintDuringNextViewUpdate = true; - } else if (currentMouseOverComponent != renderingContext.clickedItem) { - currentMouseOverComponent.mouseExited(); - currentMouseOverComponent = renderingContext.clickedItem; - currentMouseOverComponent.mouseEntered(); - repaintDuringNextViewUpdate = true; - } - } else { - // mouse click - renderingContext.clickedItem.mouseClicked(); - repaintDuringNextViewUpdate = true; - } - } else if (currentMouseOverComponent != null) { - currentMouseOverComponent.mouseExited(); - repaintDuringNextViewUpdate = true; - currentMouseOverComponent = null; - } - } - private void initializePanelLayout() { setFocusCycleRoot(true); setOpaque(true); @@ -138,37 +136,34 @@ public class ViewPanel extends JPanel implements ComponentListener { requestFocusInWindow(); } - public void renderFrame() { - // build new render buffer if needed, this happens when window was just - // created or resized - if ((renderingContext == null) - || (renderingContext.width != getWidth()) - || (renderingContext.height != getHeight())) - renderingContext = new RenderingContext(getWidth(), getHeight()); - - // clear drawing area - { - renderingContext.graphics.setColor(Color.BLACK); - renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight()); - } - + private void renderFrame() { // paint root geometry collection to the offscreen render buffer - context.getRootShapeCollection().paint(context, renderingContext); + clearCanvas(); + rootShapeCollection.paint(this, renderingContext); - // draw rendered offscreen image to visible screen + // draw rendered offscreen buffer to visible screen final Graphics graphics = getGraphics(); if (graphics != null) graphics.drawImage(renderingContext.bufferedImage, 0, 0, null); } + private void clearCanvas() { + renderingContext.graphics.setColor(backgroundColor); + renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight()); + } + /** - * Calling this methods tells 3D engine that current 3D view needs to be + * Calling these methods tells 3D engine that current 3D view needs to be * repainted on first opportunity. */ public void repaintDuringNextViewUpdate() { - repaintDuringNextViewUpdate = true; + viewRepaintNeeded = true; } + /** + * Set target frames per second rate for this view. Target FPS can be changed at runtime. + * @param frameRate target frames per second rate for this view. + */ public void setFrameRate(final int frameRate) { if (canvasUpdateTimerTask != null) { canvasUpdateTimerTask.cancel(); @@ -178,27 +173,23 @@ public class ViewPanel extends JPanel implements ComponentListener { canvasUpdateTimer = null; } - targetFramerate = frameRate; + targetFPS = frameRate; - if (frameRate > 0) { - canvasUpdateTimer = new Timer(); - canvasUpdateTimerTask = new ViewUpdateTimerTask(this); + if (frameRate <= 0) return; - canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0, - 1000 / frameRate); - } + canvasUpdateTimer = new Timer(); + canvasUpdateTimerTask = new ViewUpdateTimerTask(this); + + // schedule timer task to run in frequency according to defined frame rate + canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0, + 1000 / frameRate); } + /** + * Stops rendering of this view. + */ public void stop() { - if (canvasUpdateTimerTask != null) { - canvasUpdateTimerTask.cancel(); - canvasUpdateTimerTask = null; - } - - if (canvasUpdateTimer != null) { - canvasUpdateTimer.cancel(); - canvasUpdateTimer = null; - } + setFrameRate(0); } /** @@ -208,43 +199,70 @@ public class ViewPanel extends JPanel implements ComponentListener { * It tells view to update itself. View can decide if actual re-rendering of * graphics is needed. */ - public void updateView() { - if (renderingContext != null){ - renderingContext.mouseClick = null; - renderingContext.clickedItem = null; + void ensureThatViewIsUpToDate() { + maintainRenderingContext(); + + final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate(); + + boolean renderFrame = notifyViewRenderListeners(millisecondsPassedSinceLastUpdate); + + if (viewRepaintNeeded) { + viewRepaintNeeded = false; + renderFrame = true; } - // compute time passed since last view update - final long currentTime = System.currentTimeMillis(); + // abort rendering if window size is invalid + if ((getWidth() > 0) && (getHeight() > 0) && renderFrame) { + renderFrame(); + viewRepaintNeeded = renderingContext.handlePossibleComponentMouseEvent(); + } - if (lastUpdateMillis == 0) { - lastUpdateMillis = currentTime; + } + + private void maintainRenderingContext() { + int panelWidth = getWidth(); + int panelHeight = getHeight(); + + if (panelWidth <= 0 || panelHeight <= 0) { + renderingContext = null; return; } - final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis); - lastUpdateMillis = currentTime; + // create new rendering context if window size has changed + if ((renderingContext == null) + || (renderingContext.width != panelWidth) + || (renderingContext.height != panelHeight)) { + renderingContext = new RenderingContext(panelWidth, panelHeight); + } - // notify update listeners - boolean reRenderFrame = false; + renderingContext.prepareForNewFrameRendering(); + } + private boolean notifyViewRenderListeners(int millisecondsPassedSinceLastUpdate) { + boolean reRenderFrame = false; for (final ViewRenderListener listener : viewRenderListeners) - if (listener.beforeRender(context, - millisecondsPassedSinceLastUpdate)) + if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate)) reRenderFrame = true; + return reRenderFrame; + } - // abort rendering if window size is invalid - if ((getWidth() <= 0) || (getHeight() <= 0)) - return; + private int getMillisecondsPassedSinceLastUpdate() { + final long currentTime = System.currentTimeMillis(); - if (repaintDuringNextViewUpdate) { - repaintDuringNextViewUpdate = false; - reRenderFrame = true; - } + if (lastUpdateMillis == 0) + lastUpdateMillis = currentTime; - if (reRenderFrame) { - renderFrame(); - handleDetectedComponentMouseEvents(); - } + final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis); + lastUpdateMillis = currentTime; + return millisecondsPassedSinceLastUpdate; } + + public void addViewRenderListener(ViewRenderListener viewRenderListener) { + viewRenderListeners.add(viewRenderListener); + } + + public void removeViewRenderListener(ViewRenderListener viewRenderListener) { + viewRenderListeners.remove(viewRenderListener); + } + }