2 * Sixth 3D engine. Copyright ©2012-2019, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public License
6 * or later as published by the Free Software Foundation.
10 package eu.svjatoslav.sixth.e3d.gui;
12 import eu.svjatoslav.sixth.e3d.gui.humaninput.HIDInputTracker;
13 import eu.svjatoslav.sixth.e3d.gui.humaninput.KeyboardFocusStack;
14 import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
18 import java.awt.event.ComponentEvent;
19 import java.awt.event.ComponentListener;
21 import java.util.Timer;
22 import java.util.concurrent.ConcurrentHashMap;
25 * Java Swing GUI panel that contains canvas for 3D rendering.
27 public class ViewPanel extends JPanel implements ComponentListener {
28 private static final long serialVersionUID = 1683277888885045387L;
29 public Color backgroundColor = Color.BLACK;
30 private final HIDInputTracker HIDInputTracker = new HIDInputTracker(this);
31 private final KeyboardFocusStack keyboardFocusStack;
32 private final Avatar avatar = new Avatar();
33 private final ShapeCollection rootShapeCollection = new ShapeCollection();
34 private final Set<ViewRenderListener> viewRenderListeners = ConcurrentHashMap.newKeySet();
36 * Last time this view was updated.
38 private long lastUpdateMillis = 0;
39 private Timer canvasUpdateTimer;
40 private ViewUpdateTimerTask canvasUpdateTimerTask;
41 private RenderingContext renderingContext = null;
43 * Currently target FPS for this view. It can be changed at runtime. Also when nothing
44 * changes in the view, then frames are not really repainted.
46 private int targetFPS = 30;
48 * Set to true if it is known than next frame reeds to be painted. Flag is cleared
49 * immediately after frame got updated.
51 private boolean viewRepaintNeeded = true;
54 viewRenderListeners.add(avatar);
55 viewRenderListeners.add(HIDInputTracker);
57 keyboardFocusStack = new KeyboardFocusStack(this);
59 initializePanelLayout();
61 setFrameRate(targetFPS);
63 addComponentListener(this);
66 public Avatar getAvatar() {
70 public KeyboardFocusStack getKeyboardFocusStack() {
71 return keyboardFocusStack;
74 public ShapeCollection getRootShapeCollection() {
75 return rootShapeCollection;
78 public HIDInputTracker getHIDInputTracker() {
79 return HIDInputTracker;
82 public void addViewUpdateListener(final ViewRenderListener listener) {
83 viewRenderListeners.add(listener);
87 public void componentHidden(final ComponentEvent e) {
92 public void componentMoved(final ComponentEvent e) {
97 public void componentResized(final ComponentEvent e) {
98 viewRepaintNeeded = true;
102 public void componentShown(final ComponentEvent e) {
103 viewRepaintNeeded = true;
107 public Dimension getMaximumSize() {
108 return getPreferredSize();
112 public Dimension getMinimumSize() {
113 return getPreferredSize();
117 public java.awt.Dimension getPreferredSize() {
118 return new java.awt.Dimension(640, 480);
121 public RenderingContext getRenderingContext() {
122 return renderingContext;
125 private void initializePanelLayout() {
126 setFocusCycleRoot(true);
129 setDoubleBuffered(false);
131 requestFocusInWindow();
134 private void renderFrame() {
135 // paint root geometry collection to the offscreen render buffer
137 rootShapeCollection.paint(this, renderingContext);
139 // draw rendered offscreen buffer to visible screen
140 final Graphics graphics = getGraphics();
141 if (graphics != null)
142 graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
145 private void clearCanvas() {
146 renderingContext.graphics.setColor(backgroundColor);
147 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
151 * Calling this methods tells 3D engine that current 3D view needs to be
152 * repainted on first opportunity.
154 public void repaintDuringNextViewUpdate() {
155 viewRepaintNeeded = true;
158 public void setFrameRate(final int frameRate) {
159 if (canvasUpdateTimerTask != null) {
160 canvasUpdateTimerTask.cancel();
161 canvasUpdateTimerTask = null;
163 canvasUpdateTimer.cancel();
164 canvasUpdateTimer = null;
167 targetFPS = frameRate;
170 canvasUpdateTimer = new Timer();
171 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
173 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
179 if (canvasUpdateTimerTask != null) {
180 canvasUpdateTimerTask.cancel();
181 canvasUpdateTimerTask = null;
184 if (canvasUpdateTimer != null) {
185 canvasUpdateTimer.cancel();
186 canvasUpdateTimer = null;
191 * This method is executed by periodic timer task, in frequency according to
192 * defined frame rate.
194 * It tells view to update itself. View can decide if actual re-rendering of
195 * graphics is needed.
198 maintainRenderingContext();
200 final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate();
202 boolean renderFrame = notifyViewRenderListeners(millisecondsPassedSinceLastUpdate);
204 if (viewRepaintNeeded) {
205 viewRepaintNeeded = false;
209 // abort rendering if window size is invalid
210 if ((getWidth() > 0) && (getHeight() > 0) && renderFrame) {
212 viewRepaintNeeded = renderingContext.handleDetectedComponentMouseEvents();
217 private void maintainRenderingContext() {
218 int panelWidth = getWidth();
219 int panelHeight = getHeight();
221 if (panelWidth <= 0 || panelHeight <=0){
222 renderingContext = null;
226 if ((renderingContext == null)
227 || (renderingContext.width != panelWidth)
228 || (renderingContext.height != panelHeight)) {
229 renderingContext = new RenderingContext(panelWidth, panelHeight);
232 renderingContext.prepareForNewFrameRendering();
235 private boolean notifyViewRenderListeners(int millisecondsPassedSinceLastUpdate) {
236 boolean reRenderFrame = false;
237 for (final ViewRenderListener listener : viewRenderListeners)
238 if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate))
239 reRenderFrame = true;
240 return reRenderFrame;
243 private int getMillisecondsPassedSinceLastUpdate() {
244 final long currentTime = System.currentTimeMillis();
246 if (lastUpdateMillis == 0)
247 lastUpdateMillis = currentTime;
249 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
250 lastUpdateMillis = currentTime;
251 return millisecondsPassedSinceLastUpdate;
254 public void addViewRenderListener(ViewRenderListener viewRenderListener) {
255 viewRenderListeners.add(viewRenderListener);
258 public void removeViewRenderListener(ViewRenderListener viewRenderListener) {
259 viewRenderListeners.remove(viewRenderListener);