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;
32 * Last time this view was updated.
34 private long lastUpdateMillis = 0;
35 private Timer canvasUpdateTimer;
36 private ViewUpdateTimerTask canvasUpdateTimerTask;
37 private RenderingContext renderingContext = null;
40 * Currently target frames per second rate for this view. Target FPS can be changed at runtime.
41 * 3D engine tries to be smart and only repaints screen when there are visible changes.
43 private int targetFPS = 60;
46 * Set to true if it is known than next frame reeds to be painted. Flag is cleared
47 * immediately after frame got updated.
49 private boolean viewRepaintNeeded = true;
52 viewRenderListeners.add(avatar);
53 viewRenderListeners.add(HIDEventTracker);
55 keyboardFocusStack = new KeyboardFocusStack(this);
57 initializePanelLayout();
59 setFrameRate(targetFPS);
61 addComponentListener(this);
64 public Avatar getAvatar() {
68 public KeyboardFocusStack getKeyboardFocusStack() {
69 return keyboardFocusStack;
72 public ShapeCollection getRootShapeCollection() {
73 return rootShapeCollection;
76 public HIDEventTracker getHIDInputTracker() {
77 return HIDEventTracker;
80 public void addViewUpdateListener(final ViewRenderListener listener) {
81 viewRenderListeners.add(listener);
85 public void componentHidden(final ComponentEvent e) {
90 public void componentMoved(final ComponentEvent e) {
95 public void componentResized(final ComponentEvent e) {
96 viewRepaintNeeded = true;
100 public void componentShown(final ComponentEvent e) {
101 viewRepaintNeeded = true;
105 public Dimension getMaximumSize() {
106 return getPreferredSize();
110 public Dimension getMinimumSize() {
111 return getPreferredSize();
115 public java.awt.Dimension getPreferredSize() {
116 return new java.awt.Dimension(640, 480);
119 public RenderingContext getRenderingContext() {
120 return renderingContext;
123 private void initializePanelLayout() {
124 setFocusCycleRoot(true);
127 setDoubleBuffered(false);
129 requestFocusInWindow();
132 private void renderFrame() {
133 // paint root geometry collection to the offscreen render buffer
135 rootShapeCollection.paint(this, renderingContext);
137 // draw rendered offscreen buffer to visible screen
138 final Graphics graphics = getGraphics();
139 if (graphics != null)
140 graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
143 private void clearCanvas() {
144 renderingContext.graphics.setColor(backgroundColor);
145 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
149 * Calling these methods tells 3D engine that current 3D view needs to be
150 * repainted on first opportunity.
152 public void repaintDuringNextViewUpdate() {
153 viewRepaintNeeded = true;
157 * Set target frames per second rate for this view. Target FPS can be changed at runtime.
158 * @param frameRate target frames per second rate for this view.
160 public void setFrameRate(final int frameRate) {
161 if (canvasUpdateTimerTask != null) {
162 canvasUpdateTimerTask.cancel();
163 canvasUpdateTimerTask = null;
165 canvasUpdateTimer.cancel();
166 canvasUpdateTimer = null;
169 targetFPS = frameRate;
171 if (frameRate <= 0) return;
173 canvasUpdateTimer = new Timer();
174 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
176 // schedule timer task to run in frequency according to defined frame rate
177 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
182 * Stops rendering of this view.
189 * This method is executed by periodic timer task, in frequency according to
190 * defined frame rate.
192 * It tells view to update itself. View can decide if actual re-rendering of
193 * graphics is needed.
195 void ensureThatViewIsUpToDate() {
196 maintainRenderingContext();
198 final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate();
200 boolean renderFrame = notifyViewRenderListeners(millisecondsPassedSinceLastUpdate);
202 if (viewRepaintNeeded) {
203 viewRepaintNeeded = false;
207 // abort rendering if window size is invalid
208 if ((getWidth() > 0) && (getHeight() > 0) && renderFrame) {
210 viewRepaintNeeded = renderingContext.handlePossibleComponentMouseEvent();
215 private void maintainRenderingContext() {
216 int panelWidth = getWidth();
217 int panelHeight = getHeight();
219 if (panelWidth <= 0 || panelHeight <= 0) {
220 renderingContext = null;
224 // create new rendering context if window size has changed
225 if ((renderingContext == null)
226 || (renderingContext.width != panelWidth)
227 || (renderingContext.height != panelHeight)) {
228 renderingContext = new RenderingContext(panelWidth, panelHeight);
231 renderingContext.prepareForNewFrameRendering();
234 private boolean notifyViewRenderListeners(int millisecondsPassedSinceLastUpdate) {
235 boolean reRenderFrame = false;
236 for (final ViewRenderListener listener : viewRenderListeners)
237 if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate))
238 reRenderFrame = true;
239 return reRenderFrame;
242 private int getMillisecondsPassedSinceLastUpdate() {
243 final long currentTime = System.currentTimeMillis();
245 if (lastUpdateMillis == 0)
246 lastUpdateMillis = currentTime;
248 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
249 lastUpdateMillis = currentTime;
250 return millisecondsPassedSinceLastUpdate;
253 public void addViewRenderListener(ViewRenderListener viewRenderListener) {
254 viewRenderListeners.add(viewRenderListener);
257 public void removeViewRenderListener(ViewRenderListener viewRenderListener) {
258 viewRenderListeners.remove(viewRenderListener);