Improved JavaDoc.
[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     /**
39      * Currently target frames per second rate for this view. Target FPS can be changed at runtime.
40      * 3D engine tries to be smart and only repaints screen when there are visible changes.
41      */
42     private int targetFPS = 60;
43
44     /**
45      * Set to true if it is known than next frame reeds to be painted. Flag is cleared
46      * immediately after frame got updated.
47      */
48     private boolean viewRepaintNeeded = true;
49
50     public ViewPanel() {
51         viewRenderListeners.add(avatar);
52         viewRenderListeners.add(HIDInputTracker);
53
54         keyboardFocusStack = new KeyboardFocusStack(this);
55
56         initializePanelLayout();
57
58         setFrameRate(targetFPS);
59
60         addComponentListener(this);
61     }
62
63     public Avatar getAvatar() {
64         return avatar;
65     }
66
67     public KeyboardFocusStack getKeyboardFocusStack() {
68         return keyboardFocusStack;
69     }
70
71     public ShapeCollection getRootShapeCollection() {
72         return rootShapeCollection;
73     }
74
75     public HIDInputTracker getHIDInputTracker() {
76         return HIDInputTracker;
77     }
78
79     public void addViewUpdateListener(final ViewRenderListener listener) {
80         viewRenderListeners.add(listener);
81     }
82
83     @Override
84     public void componentHidden(final ComponentEvent e) {
85
86     }
87
88     @Override
89     public void componentMoved(final ComponentEvent e) {
90
91     }
92
93     @Override
94     public void componentResized(final ComponentEvent e) {
95         viewRepaintNeeded = true;
96     }
97
98     @Override
99     public void componentShown(final ComponentEvent e) {
100         viewRepaintNeeded = true;
101     }
102
103     @Override
104     public Dimension getMaximumSize() {
105         return getPreferredSize();
106     }
107
108     @Override
109     public Dimension getMinimumSize() {
110         return getPreferredSize();
111     }
112
113     @Override
114     public java.awt.Dimension getPreferredSize() {
115         return new java.awt.Dimension(640, 480);
116     }
117
118     public RenderingContext getRenderingContext() {
119         return renderingContext;
120     }
121
122     private void initializePanelLayout() {
123         setFocusCycleRoot(true);
124         setOpaque(true);
125         setFocusable(true);
126         setDoubleBuffered(false);
127         setVisible(true);
128         requestFocusInWindow();
129     }
130
131     private void renderFrame() {
132         // paint root geometry collection to the offscreen render buffer
133         clearCanvas();
134         rootShapeCollection.paint(this, renderingContext);
135
136         // draw rendered offscreen buffer to visible screen
137         final Graphics graphics = getGraphics();
138         if (graphics != null)
139             graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
140     }
141
142     private void clearCanvas() {
143         renderingContext.graphics.setColor(backgroundColor);
144         renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
145     }
146
147     /**
148      * Calling this methods tells 3D engine that current 3D view needs to be
149      * repainted on first opportunity.
150      */
151     public void repaintDuringNextViewUpdate() {
152         viewRepaintNeeded = true;
153     }
154
155     public void setFrameRate(final int frameRate) {
156         if (canvasUpdateTimerTask != null) {
157             canvasUpdateTimerTask.cancel();
158             canvasUpdateTimerTask = null;
159
160             canvasUpdateTimer.cancel();
161             canvasUpdateTimer = null;
162         }
163
164         targetFPS = frameRate;
165
166         if (frameRate > 0) {
167             canvasUpdateTimer = new Timer();
168             canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
169
170             canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
171                     1000 / frameRate);
172         }
173     }
174
175     public void stop() {
176         if (canvasUpdateTimerTask != null) {
177             canvasUpdateTimerTask.cancel();
178             canvasUpdateTimerTask = null;
179         }
180
181         if (canvasUpdateTimer != null) {
182             canvasUpdateTimer.cancel();
183             canvasUpdateTimer = null;
184         }
185     }
186
187     /**
188      * This method is executed by periodic timer task, in frequency according to
189      * defined frame rate.
190      * <p>
191      * It tells view to update itself. View can decide if actual re-rendering of
192      * graphics is needed.
193      */
194     void updateView() {
195         maintainRenderingContext();
196
197         final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate();
198
199         boolean renderFrame = notifyViewRenderListeners(millisecondsPassedSinceLastUpdate);
200
201         if (viewRepaintNeeded) {
202             viewRepaintNeeded = false;
203             renderFrame = true;
204         }
205
206         // abort rendering if window size is invalid
207         if ((getWidth() > 0) && (getHeight() > 0) && renderFrame) {
208             renderFrame();
209             viewRepaintNeeded = renderingContext.handleDetectedComponentMouseEvents();
210         }
211
212     }
213
214     private void maintainRenderingContext() {
215         int panelWidth = getWidth();
216         int panelHeight = getHeight();
217
218         if (panelWidth <= 0 || panelHeight <=0){
219             renderingContext = null;
220             return;
221         }
222
223         if ((renderingContext == null)
224                 || (renderingContext.width != panelWidth)
225                 || (renderingContext.height != panelHeight)) {
226             renderingContext = new RenderingContext(panelWidth, panelHeight);
227         }
228
229         renderingContext.prepareForNewFrameRendering();
230     }
231
232     private boolean notifyViewRenderListeners(int millisecondsPassedSinceLastUpdate) {
233         boolean reRenderFrame = false;
234         for (final ViewRenderListener listener : viewRenderListeners)
235             if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate))
236                 reRenderFrame = true;
237         return reRenderFrame;
238     }
239
240     private int getMillisecondsPassedSinceLastUpdate() {
241         final long currentTime = System.currentTimeMillis();
242
243         if (lastUpdateMillis == 0)
244             lastUpdateMillis = currentTime;
245
246         final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
247         lastUpdateMillis = currentTime;
248         return millisecondsPassedSinceLastUpdate;
249     }
250
251     public void addViewRenderListener(ViewRenderListener viewRenderListener) {
252         viewRenderListeners.add(viewRenderListener);
253     }
254
255     public void removeViewRenderListener(ViewRenderListener viewRenderListener) {
256         viewRenderListeners.remove(viewRenderListener);
257     }
258
259 }