2 * Sixth 3D engine. Author: Svjatoslav Agejenko.
3 * This project is released under Creative Commons Zero (CC0) license.
5 package eu.svjatoslav.sixth.e3d.gui;
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;
13 import java.awt.event.ComponentEvent;
14 import java.awt.event.ComponentListener;
16 import java.util.Timer;
17 import java.util.concurrent.ConcurrentHashMap;
20 * Java Swing GUI panel that contains canvas for 3D rendering.
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();
31 * Last time this view was updated.
33 private long lastUpdateMillis = 0;
34 private Timer canvasUpdateTimer;
35 private ViewUpdateTimerTask canvasUpdateTimerTask;
36 private RenderingContext renderingContext = null;
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.
41 private int targetFPS = 30;
43 * Set to true if it is known than next frame reeds to be painted. Flag is cleared
44 * immediately after frame got updated.
46 private boolean viewRepaintNeeded = true;
49 viewRenderListeners.add(avatar);
50 viewRenderListeners.add(HIDInputTracker);
52 keyboardFocusStack = new KeyboardFocusStack(this);
54 initializePanelLayout();
56 setFrameRate(targetFPS);
58 addComponentListener(this);
61 public Avatar getAvatar() {
65 public KeyboardFocusStack getKeyboardFocusStack() {
66 return keyboardFocusStack;
69 public ShapeCollection getRootShapeCollection() {
70 return rootShapeCollection;
73 public HIDInputTracker getHIDInputTracker() {
74 return HIDInputTracker;
77 public void addViewUpdateListener(final ViewRenderListener listener) {
78 viewRenderListeners.add(listener);
82 public void componentHidden(final ComponentEvent e) {
87 public void componentMoved(final ComponentEvent e) {
92 public void componentResized(final ComponentEvent e) {
93 viewRepaintNeeded = true;
97 public void componentShown(final ComponentEvent e) {
98 viewRepaintNeeded = true;
102 public Dimension getMaximumSize() {
103 return getPreferredSize();
107 public Dimension getMinimumSize() {
108 return getPreferredSize();
112 public java.awt.Dimension getPreferredSize() {
113 return new java.awt.Dimension(640, 480);
116 public RenderingContext getRenderingContext() {
117 return renderingContext;
120 private void initializePanelLayout() {
121 setFocusCycleRoot(true);
124 setDoubleBuffered(false);
126 requestFocusInWindow();
129 private void renderFrame() {
130 // paint root geometry collection to the offscreen render buffer
132 rootShapeCollection.paint(this, renderingContext);
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);
140 private void clearCanvas() {
141 renderingContext.graphics.setColor(backgroundColor);
142 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
146 * Calling this methods tells 3D engine that current 3D view needs to be
147 * repainted on first opportunity.
149 public void repaintDuringNextViewUpdate() {
150 viewRepaintNeeded = true;
153 public void setFrameRate(final int frameRate) {
154 if (canvasUpdateTimerTask != null) {
155 canvasUpdateTimerTask.cancel();
156 canvasUpdateTimerTask = null;
158 canvasUpdateTimer.cancel();
159 canvasUpdateTimer = null;
162 targetFPS = frameRate;
165 canvasUpdateTimer = new Timer();
166 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
168 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
174 if (canvasUpdateTimerTask != null) {
175 canvasUpdateTimerTask.cancel();
176 canvasUpdateTimerTask = null;
179 if (canvasUpdateTimer != null) {
180 canvasUpdateTimer.cancel();
181 canvasUpdateTimer = null;
186 * This method is executed by periodic timer task, in frequency according to
187 * defined frame rate.
189 * It tells view to update itself. View can decide if actual re-rendering of
190 * graphics is needed.
193 maintainRenderingContext();
195 final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate();
197 boolean renderFrame = notifyViewRenderListeners(millisecondsPassedSinceLastUpdate);
199 if (viewRepaintNeeded) {
200 viewRepaintNeeded = false;
204 // abort rendering if window size is invalid
205 if ((getWidth() > 0) && (getHeight() > 0) && renderFrame) {
207 viewRepaintNeeded = renderingContext.handleDetectedComponentMouseEvents();
212 private void maintainRenderingContext() {
213 int panelWidth = getWidth();
214 int panelHeight = getHeight();
216 if (panelWidth <= 0 || panelHeight <=0){
217 renderingContext = null;
221 if ((renderingContext == null)
222 || (renderingContext.width != panelWidth)
223 || (renderingContext.height != panelHeight)) {
224 renderingContext = new RenderingContext(panelWidth, panelHeight);
227 renderingContext.prepareForNewFrameRendering();
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;
238 private int getMillisecondsPassedSinceLastUpdate() {
239 final long currentTime = System.currentTimeMillis();
241 if (lastUpdateMillis == 0)
242 lastUpdateMillis = currentTime;
244 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
245 lastUpdateMillis = currentTime;
246 return millisecondsPassedSinceLastUpdate;
249 public void addViewRenderListener(ViewRenderListener viewRenderListener) {
250 viewRenderListeners.add(viewRenderListener);
253 public void removeViewRenderListener(ViewRenderListener viewRenderListener) {
254 viewRenderListeners.remove(viewRenderListener);