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.HIDEventTracker;
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.
21 * Usually it is used as a part of {@link ViewFrame}.
23 public class ViewPanel extends JPanel implements ComponentListener {
24 private static final long serialVersionUID = 1683277888885045387L;
25 private final HIDEventTracker HIDEventTracker = new HIDEventTracker(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 public Color backgroundColor = Color.BLACK;
33 * Stores milliseconds when last frame was updated. This is needed to calculate time delta between frames.
34 * Time delta is used to calculate smooth animation.
36 private long lastUpdateMillis = 0;
39 * Timer that is used to update canvas at target FPS rate.
41 private Timer canvasUpdateTimer;
43 private ViewUpdateTimerTask canvasUpdateTimerTask;
44 private RenderingContext renderingContext = null;
47 * Currently target frames per second rate for this view. Target FPS can be changed at runtime.
48 * 3D engine tries to be smart and only repaints screen when there are visible changes.
50 private int targetFPS = 60;
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;
59 viewRenderListeners.add(avatar);
60 viewRenderListeners.add(HIDEventTracker);
62 keyboardFocusStack = new KeyboardFocusStack(this);
64 initializePanelLayout();
66 setFrameRate(targetFPS);
68 addComponentListener(this);
71 public Avatar getAvatar() {
75 public KeyboardFocusStack getKeyboardFocusStack() {
76 return keyboardFocusStack;
79 public ShapeCollection getRootShapeCollection() {
80 return rootShapeCollection;
83 public HIDEventTracker getHIDInputTracker() {
84 return HIDEventTracker;
87 public void addViewUpdateListener(final ViewRenderListener listener) {
88 viewRenderListeners.add(listener);
92 public void componentHidden(final ComponentEvent e) {
97 public void componentMoved(final ComponentEvent e) {
102 public void componentResized(final ComponentEvent e) {
103 viewRepaintNeeded = true;
107 public void componentShown(final ComponentEvent e) {
108 viewRepaintNeeded = true;
112 public Dimension getMaximumSize() {
113 return getPreferredSize();
117 public Dimension getMinimumSize() {
118 return getPreferredSize();
122 public java.awt.Dimension getPreferredSize() {
123 return new java.awt.Dimension(640, 480);
126 public RenderingContext getRenderingContext() {
127 return renderingContext;
130 private void initializePanelLayout() {
131 setFocusCycleRoot(true);
134 setDoubleBuffered(false);
136 requestFocusInWindow();
139 private void renderFrame() {
140 // paint root geometry collection to the offscreen render buffer
142 rootShapeCollection.paint(this, renderingContext);
144 // draw rendered offscreen buffer to visible screen
145 final Graphics graphics = getGraphics();
146 if (graphics != null)
147 graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
150 private void clearCanvas() {
151 renderingContext.graphics.setColor(backgroundColor);
152 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
156 * Calling these methods tells 3D engine that current 3D view needs to be
157 * repainted on first opportunity.
159 public void repaintDuringNextViewUpdate() {
160 viewRepaintNeeded = true;
164 * Set target frames per second rate for this view. Target FPS can be changed at runtime.
165 * @param frameRate target frames per second rate for this view.
167 public void setFrameRate(final int frameRate) {
168 if (canvasUpdateTimerTask != null) {
169 canvasUpdateTimerTask.cancel();
170 canvasUpdateTimerTask = null;
172 canvasUpdateTimer.cancel();
173 canvasUpdateTimer = null;
176 targetFPS = frameRate;
178 if (frameRate <= 0) return;
180 canvasUpdateTimer = new Timer();
181 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
183 // schedule timer task to run in frequency according to defined frame rate
184 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
189 * Stops rendering of this view.
196 * This method is executed by periodic timer task, in frequency according to
197 * defined frame rate.
199 * It tells view to update itself. View can decide if actual re-rendering of
200 * graphics is needed.
202 void ensureThatViewIsUpToDate() {
203 maintainRenderingContext();
205 final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate();
207 boolean renderFrame = notifyViewRenderListeners(millisecondsPassedSinceLastUpdate);
209 if (viewRepaintNeeded) {
210 viewRepaintNeeded = false;
214 // abort rendering if window size is invalid
215 if ((getWidth() > 0) && (getHeight() > 0) && renderFrame) {
217 viewRepaintNeeded = renderingContext.handlePossibleComponentMouseEvent();
222 private void maintainRenderingContext() {
223 int panelWidth = getWidth();
224 int panelHeight = getHeight();
226 if (panelWidth <= 0 || panelHeight <= 0) {
227 renderingContext = null;
231 // create new rendering context if window size has changed
232 if ((renderingContext == null)
233 || (renderingContext.width != panelWidth)
234 || (renderingContext.height != panelHeight)) {
235 renderingContext = new RenderingContext(panelWidth, panelHeight);
238 renderingContext.prepareForNewFrameRendering();
241 private boolean notifyViewRenderListeners(int millisecondsPassedSinceLastUpdate) {
242 boolean reRenderFrame = false;
243 for (final ViewRenderListener listener : viewRenderListeners)
244 if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate))
245 reRenderFrame = true;
246 return reRenderFrame;
249 private int getMillisecondsPassedSinceLastUpdate() {
250 final long currentTime = System.currentTimeMillis();
252 if (lastUpdateMillis == 0)
253 lastUpdateMillis = currentTime;
255 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
256 lastUpdateMillis = currentTime;
257 return millisecondsPassedSinceLastUpdate;
260 public void addViewRenderListener(ViewRenderListener viewRenderListener) {
261 viewRenderListeners.add(viewRenderListener);
264 public void removeViewRenderListener(ViewRenderListener viewRenderListener) {
265 viewRenderListeners.remove(viewRenderListener);