2 * Sixth 3D engine. Copyright ©2012-2018, 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.gui.humaninput.MouseInteractionController;
15 import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
19 import java.awt.event.ComponentEvent;
20 import java.awt.event.ComponentListener;
22 import java.util.Timer;
23 import java.util.concurrent.ConcurrentHashMap;
26 * Java Swing GUI panel that contains canvas for 3D rendering.
28 public class ViewPanel extends JPanel implements ComponentListener {
29 private static final long serialVersionUID = 1683277888885045387L;
30 public Color backgroundColor = Color.BLACK;
31 private final HIDInputTracker HIDInputTracker = new HIDInputTracker(this);
32 private final KeyboardFocusStack keyboardFocusStack;
33 private final Avatar avatar = new Avatar();
34 private final ShapeCollection rootShapeCollection = new ShapeCollection();
35 private final Set<ViewRenderListener> viewRenderListeners = ConcurrentHashMap.newKeySet();
37 * Last time this view was updated.
39 private long lastUpdateMillis = 0;
40 private Timer canvasUpdateTimer;
41 private ViewUpdateTimerTask canvasUpdateTimerTask;
42 private RenderingContext renderingContext = null;
44 * UI component that mouse is currently hovering over.
46 private MouseInteractionController currentMouseOverComponent;
48 * Currently target FPS for this view. It can be changed at runtime. Also when nothing
49 * changes in the view, then frames are not really repainted.
51 private int targetFPS = 30;
53 * Set to true if it is known than next frame reeds to be painted. Flag is cleared
54 * immediately after frame got updated.
56 private boolean viewRepaintNeeded = true;
58 viewRenderListeners.add(avatar);
59 viewRenderListeners.add(HIDInputTracker);
61 keyboardFocusStack = new KeyboardFocusStack(this);
63 initializePanelLayout();
65 setFrameRate(targetFPS);
67 addComponentListener(this);
70 public Avatar getAvatar() {
74 public KeyboardFocusStack getKeyboardFocusStack() {
75 return keyboardFocusStack;
78 public ShapeCollection getRootShapeCollection() {
79 return rootShapeCollection;
82 public HIDInputTracker getHIDInputTracker() {
83 return HIDInputTracker;
86 public void addViewUpdateListener(final ViewRenderListener listener) {
87 viewRenderListeners.add(listener);
91 public void componentHidden(final ComponentEvent e) {
96 public void componentMoved(final ComponentEvent e) {
101 public void componentResized(final ComponentEvent e) {
102 viewRepaintNeeded = true;
106 public void componentShown(final ComponentEvent e) {
107 viewRepaintNeeded = true;
111 public Dimension getMaximumSize() {
112 return getPreferredSize();
116 public Dimension getMinimumSize() {
117 return getPreferredSize();
121 public java.awt.Dimension getPreferredSize() {
122 return new java.awt.Dimension(640, 480);
125 public RenderingContext getRenderingContext() {
126 return renderingContext;
129 private void handleDetectedComponentMouseEvents() {
130 if (renderingContext.objectUnderMouse != null) {
131 if (renderingContext.mouseEvent.button == 0) {
133 if (currentMouseOverComponent == null) {
134 currentMouseOverComponent = renderingContext.objectUnderMouse;
135 viewRepaintNeeded |= currentMouseOverComponent.mouseEntered();
136 } else if (currentMouseOverComponent != renderingContext.objectUnderMouse) {
137 viewRepaintNeeded |= currentMouseOverComponent.mouseExited();
138 currentMouseOverComponent = renderingContext.objectUnderMouse;
139 viewRepaintNeeded |= currentMouseOverComponent.mouseEntered();
143 viewRepaintNeeded |= renderingContext.objectUnderMouse.mouseClicked();
145 } else if (currentMouseOverComponent != null) {
146 viewRepaintNeeded |= currentMouseOverComponent.mouseExited();
147 currentMouseOverComponent = null;
151 private void initializePanelLayout() {
152 setFocusCycleRoot(true);
155 setDoubleBuffered(false);
157 requestFocusInWindow();
160 private void renderFrame() {
162 // paint root geometry collection to the offscreen render buffer
164 rootShapeCollection.paint(this, renderingContext);
166 // draw rendered offscreen buffer to visible screen
167 final Graphics graphics = getGraphics();
168 if (graphics != null)
169 graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
172 private void clearCanvas() {
173 renderingContext.graphics.setColor(backgroundColor);
174 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
178 * Calling this methods tells 3D engine that current 3D view needs to be
179 * repainted on first opportunity.
181 public void repaintDuringNextViewUpdate() {
182 viewRepaintNeeded = true;
185 public void setFrameRate(final int frameRate) {
186 if (canvasUpdateTimerTask != null) {
187 canvasUpdateTimerTask.cancel();
188 canvasUpdateTimerTask = null;
190 canvasUpdateTimer.cancel();
191 canvasUpdateTimer = null;
194 targetFPS = frameRate;
197 canvasUpdateTimer = new Timer();
198 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
200 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
206 if (canvasUpdateTimerTask != null) {
207 canvasUpdateTimerTask.cancel();
208 canvasUpdateTimerTask = null;
211 if (canvasUpdateTimer != null) {
212 canvasUpdateTimer.cancel();
213 canvasUpdateTimer = null;
218 * This method is executed by periodic timer task, in frequency according to
219 * defined frame rate.
221 * It tells view to update itself. View can decide if actual re-rendering of
222 * graphics is needed.
225 maintainRenderingContext();
227 final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate();
229 boolean renderFrame = notifyViewRenderListeners(millisecondsPassedSinceLastUpdate);
231 if (viewRepaintNeeded) {
232 viewRepaintNeeded = false;
236 // abort rendering if window size is invalid
237 if ((getWidth() > 0) && (getHeight() > 0) && renderFrame) {
239 handleDetectedComponentMouseEvents();
244 private void maintainRenderingContext() {
245 int panelWidth = getWidth();
246 int panelHeight = getHeight();
248 if (panelWidth <= 0 || panelHeight <=0){
249 renderingContext = null;
253 if ((renderingContext == null)
254 || (renderingContext.width != panelWidth)
255 || (renderingContext.height != panelHeight)) {
256 renderingContext = new RenderingContext(panelWidth, panelHeight);
259 renderingContext.mouseEvent = null;
260 renderingContext.objectUnderMouse = null;
263 private boolean notifyViewRenderListeners(int millisecondsPassedSinceLastUpdate) {
264 boolean reRenderFrame = false;
265 for (final ViewRenderListener listener : viewRenderListeners)
266 if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate))
267 reRenderFrame = true;
268 return reRenderFrame;
271 private int getMillisecondsPassedSinceLastUpdate() {
272 final long currentTime = System.currentTimeMillis();
274 if (lastUpdateMillis == 0)
275 lastUpdateMillis = currentTime;
277 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
278 lastUpdateMillis = currentTime;
279 return millisecondsPassedSinceLastUpdate;
282 public void addViewRenderListener(ViewRenderListener viewRenderListener) {
283 viewRenderListeners.add(viewRenderListener);
286 public void removeViewRenderListener(ViewRenderListener viewRenderListener) {
287 viewRenderListeners.remove(viewRenderListener);