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;
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.
42 private int targetFPS = 60;
45 * Set to true if it is known than next frame reeds to be painted. Flag is cleared
46 * immediately after frame got updated.
48 private boolean viewRepaintNeeded = true;
51 viewRenderListeners.add(avatar);
52 viewRenderListeners.add(HIDInputTracker);
54 keyboardFocusStack = new KeyboardFocusStack(this);
56 initializePanelLayout();
58 setFrameRate(targetFPS);
60 addComponentListener(this);
63 public Avatar getAvatar() {
67 public KeyboardFocusStack getKeyboardFocusStack() {
68 return keyboardFocusStack;
71 public ShapeCollection getRootShapeCollection() {
72 return rootShapeCollection;
75 public HIDInputTracker getHIDInputTracker() {
76 return HIDInputTracker;
79 public void addViewUpdateListener(final ViewRenderListener listener) {
80 viewRenderListeners.add(listener);
84 public void componentHidden(final ComponentEvent e) {
89 public void componentMoved(final ComponentEvent e) {
94 public void componentResized(final ComponentEvent e) {
95 viewRepaintNeeded = true;
99 public void componentShown(final ComponentEvent e) {
100 viewRepaintNeeded = true;
104 public Dimension getMaximumSize() {
105 return getPreferredSize();
109 public Dimension getMinimumSize() {
110 return getPreferredSize();
114 public java.awt.Dimension getPreferredSize() {
115 return new java.awt.Dimension(640, 480);
118 public RenderingContext getRenderingContext() {
119 return renderingContext;
122 private void initializePanelLayout() {
123 setFocusCycleRoot(true);
126 setDoubleBuffered(false);
128 requestFocusInWindow();
131 private void renderFrame() {
132 // paint root geometry collection to the offscreen render buffer
134 rootShapeCollection.paint(this, renderingContext);
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);
142 private void clearCanvas() {
143 renderingContext.graphics.setColor(backgroundColor);
144 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
148 * Calling this methods tells 3D engine that current 3D view needs to be
149 * repainted on first opportunity.
151 public void repaintDuringNextViewUpdate() {
152 viewRepaintNeeded = true;
155 public void setFrameRate(final int frameRate) {
156 if (canvasUpdateTimerTask != null) {
157 canvasUpdateTimerTask.cancel();
158 canvasUpdateTimerTask = null;
160 canvasUpdateTimer.cancel();
161 canvasUpdateTimer = null;
164 targetFPS = frameRate;
167 canvasUpdateTimer = new Timer();
168 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
170 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
176 if (canvasUpdateTimerTask != null) {
177 canvasUpdateTimerTask.cancel();
178 canvasUpdateTimerTask = null;
181 if (canvasUpdateTimer != null) {
182 canvasUpdateTimer.cancel();
183 canvasUpdateTimer = null;
188 * This method is executed by periodic timer task, in frequency according to
189 * defined frame rate.
191 * It tells view to update itself. View can decide if actual re-rendering of
192 * graphics is needed.
195 maintainRenderingContext();
197 final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate();
199 boolean renderFrame = notifyViewRenderListeners(millisecondsPassedSinceLastUpdate);
201 if (viewRepaintNeeded) {
202 viewRepaintNeeded = false;
206 // abort rendering if window size is invalid
207 if ((getWidth() > 0) && (getHeight() > 0) && renderFrame) {
209 viewRepaintNeeded = renderingContext.handlePossibleComponentMouseEvent();
214 private void maintainRenderingContext() {
215 int panelWidth = getWidth();
216 int panelHeight = getHeight();
218 if (panelWidth <= 0 || panelHeight <=0){
219 renderingContext = null;
223 if ((renderingContext == null)
224 || (renderingContext.width != panelWidth)
225 || (renderingContext.height != panelHeight)) {
226 renderingContext = new RenderingContext(panelWidth, panelHeight);
229 renderingContext.prepareForNewFrameRendering();
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;
240 private int getMillisecondsPassedSinceLastUpdate() {
241 final long currentTime = System.currentTimeMillis();
243 if (lastUpdateMillis == 0)
244 lastUpdateMillis = currentTime;
246 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
247 lastUpdateMillis = currentTime;
248 return millisecondsPassedSinceLastUpdate;
251 public void addViewRenderListener(ViewRenderListener viewRenderListener) {
252 viewRenderListeners.add(viewRenderListener);
255 public void removeViewRenderListener(ViewRenderListener viewRenderListener) {
256 viewRenderListeners.remove(viewRenderListener);