Formatting update
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / gui / ViewPanel.java
1 /*
2  * Sixth 3D engine. Author: Svjatoslav Agejenko. 
3  * This project is released under Creative Commons Zero (CC0) license.
4  */
5 package eu.svjatoslav.sixth.e3d.gui;
6
7 import eu.svjatoslav.sixth.e3d.gui.humaninput.HIDInputTracker;
8 import eu.svjatoslav.sixth.e3d.gui.humaninput.KeyboardFocusStack;
9 import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
10
11 import javax.swing.*;
12 import java.awt.*;
13 import java.awt.event.ComponentEvent;
14 import java.awt.event.ComponentListener;
15 import java.util.Set;
16 import java.util.Timer;
17 import java.util.concurrent.ConcurrentHashMap;
18
19 /**
20  * Java Swing GUI panel that contains canvas for 3D rendering.
21  */
22 public class ViewPanel extends JPanel implements ComponentListener {
23     private static final long serialVersionUID = 1683277888885045387L;
24     public Color backgroundColor = Color.BLACK;
25     private final HIDInputTracker HIDInputTracker = new HIDInputTracker(this);
26     private final KeyboardFocusStack keyboardFocusStack;
27     private final Avatar avatar = new Avatar();
28     private final ShapeCollection rootShapeCollection = new ShapeCollection();
29     private final Set<ViewRenderListener> viewRenderListeners = ConcurrentHashMap.newKeySet();
30     /**
31      * Last time this view was updated.
32      */
33     private long lastUpdateMillis = 0;
34     private Timer canvasUpdateTimer;
35     private ViewUpdateTimerTask canvasUpdateTimerTask;
36     private RenderingContext renderingContext = null;
37     /**
38      * Currently target FPS for this view. It can be changed at runtime. Also when nothing
39      * changes in the view, then frames are not really repainted.
40      */
41     private int targetFPS = 30;
42     /**
43      * Set to true if it is known than next frame reeds to be painted. Flag is cleared
44      * immediately after frame got updated.
45      */
46     private boolean viewRepaintNeeded = true;
47
48     public ViewPanel() {
49         viewRenderListeners.add(avatar);
50         viewRenderListeners.add(HIDInputTracker);
51
52         keyboardFocusStack = new KeyboardFocusStack(this);
53
54         initializePanelLayout();
55
56         setFrameRate(targetFPS);
57
58         addComponentListener(this);
59     }
60
61     public Avatar getAvatar() {
62         return avatar;
63     }
64
65     public KeyboardFocusStack getKeyboardFocusStack() {
66         return keyboardFocusStack;
67     }
68
69     public ShapeCollection getRootShapeCollection() {
70         return rootShapeCollection;
71     }
72
73     public HIDInputTracker getHIDInputTracker() {
74         return HIDInputTracker;
75     }
76
77     public void addViewUpdateListener(final ViewRenderListener listener) {
78         viewRenderListeners.add(listener);
79     }
80
81     @Override
82     public void componentHidden(final ComponentEvent e) {
83
84     }
85
86     @Override
87     public void componentMoved(final ComponentEvent e) {
88
89     }
90
91     @Override
92     public void componentResized(final ComponentEvent e) {
93         viewRepaintNeeded = true;
94     }
95
96     @Override
97     public void componentShown(final ComponentEvent e) {
98         viewRepaintNeeded = true;
99     }
100
101     @Override
102     public Dimension getMaximumSize() {
103         return getPreferredSize();
104     }
105
106     @Override
107     public Dimension getMinimumSize() {
108         return getPreferredSize();
109     }
110
111     @Override
112     public java.awt.Dimension getPreferredSize() {
113         return new java.awt.Dimension(640, 480);
114     }
115
116     public RenderingContext getRenderingContext() {
117         return renderingContext;
118     }
119
120     private void initializePanelLayout() {
121         setFocusCycleRoot(true);
122         setOpaque(true);
123         setFocusable(true);
124         setDoubleBuffered(false);
125         setVisible(true);
126         requestFocusInWindow();
127     }
128
129     private void renderFrame() {
130         // paint root geometry collection to the offscreen render buffer
131         clearCanvas();
132         rootShapeCollection.paint(this, renderingContext);
133
134         // draw rendered offscreen buffer to visible screen
135         final Graphics graphics = getGraphics();
136         if (graphics != null)
137             graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
138     }
139
140     private void clearCanvas() {
141         renderingContext.graphics.setColor(backgroundColor);
142         renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
143     }
144
145     /**
146      * Calling this methods tells 3D engine that current 3D view needs to be
147      * repainted on first opportunity.
148      */
149     public void repaintDuringNextViewUpdate() {
150         viewRepaintNeeded = true;
151     }
152
153     public void setFrameRate(final int frameRate) {
154         if (canvasUpdateTimerTask != null) {
155             canvasUpdateTimerTask.cancel();
156             canvasUpdateTimerTask = null;
157
158             canvasUpdateTimer.cancel();
159             canvasUpdateTimer = null;
160         }
161
162         targetFPS = frameRate;
163
164         if (frameRate > 0) {
165             canvasUpdateTimer = new Timer();
166             canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
167
168             canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
169                     1000 / frameRate);
170         }
171     }
172
173     public void stop() {
174         if (canvasUpdateTimerTask != null) {
175             canvasUpdateTimerTask.cancel();
176             canvasUpdateTimerTask = null;
177         }
178
179         if (canvasUpdateTimer != null) {
180             canvasUpdateTimer.cancel();
181             canvasUpdateTimer = null;
182         }
183     }
184
185     /**
186      * This method is executed by periodic timer task, in frequency according to
187      * defined frame rate.
188      * <p>
189      * It tells view to update itself. View can decide if actual re-rendering of
190      * graphics is needed.
191      */
192     void updateView() {
193         maintainRenderingContext();
194
195         final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate();
196
197         boolean renderFrame = notifyViewRenderListeners(millisecondsPassedSinceLastUpdate);
198
199         if (viewRepaintNeeded) {
200             viewRepaintNeeded = false;
201             renderFrame = true;
202         }
203
204         // abort rendering if window size is invalid
205         if ((getWidth() > 0) && (getHeight() > 0) && renderFrame) {
206             renderFrame();
207             viewRepaintNeeded = renderingContext.handleDetectedComponentMouseEvents();
208         }
209
210     }
211
212     private void maintainRenderingContext() {
213         int panelWidth = getWidth();
214         int panelHeight = getHeight();
215
216         if (panelWidth <= 0 || panelHeight <=0){
217             renderingContext = null;
218             return;
219         }
220
221         if ((renderingContext == null)
222                 || (renderingContext.width != panelWidth)
223                 || (renderingContext.height != panelHeight)) {
224             renderingContext = new RenderingContext(panelWidth, panelHeight);
225         }
226
227         renderingContext.prepareForNewFrameRendering();
228     }
229
230     private boolean notifyViewRenderListeners(int millisecondsPassedSinceLastUpdate) {
231         boolean reRenderFrame = false;
232         for (final ViewRenderListener listener : viewRenderListeners)
233             if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate))
234                 reRenderFrame = true;
235         return reRenderFrame;
236     }
237
238     private int getMillisecondsPassedSinceLastUpdate() {
239         final long currentTime = System.currentTimeMillis();
240
241         if (lastUpdateMillis == 0)
242             lastUpdateMillis = currentTime;
243
244         final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
245         lastUpdateMillis = currentTime;
246         return millisecondsPassedSinceLastUpdate;
247     }
248
249     public void addViewRenderListener(ViewRenderListener viewRenderListener) {
250         viewRenderListeners.add(viewRenderListener);
251     }
252
253     public void removeViewRenderListener(ViewRenderListener viewRenderListener) {
254         viewRenderListeners.remove(viewRenderListener);
255     }
256
257 }