Fixed in room navigation.
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / gui / humaninput / HIDInputTracker.java
1 /*
2  * Sixth 3D engine. Copyright ©2012-2018, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
3  *
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.
7  *
8  */
9
10 package eu.svjatoslav.sixth.e3d.gui.humaninput;
11
12 import eu.svjatoslav.sixth.e3d.geometry.Point2D;
13 import eu.svjatoslav.sixth.e3d.gui.Avatar;
14 import eu.svjatoslav.sixth.e3d.gui.RenderingContext;
15 import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
16 import eu.svjatoslav.sixth.e3d.gui.ViewRenderListener;
17
18 import javax.swing.*;
19 import java.awt.event.*;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24
25 public class HIDInputTracker implements
26         MouseMotionListener, KeyListener, MouseListener, MouseWheelListener, ViewRenderListener {
27
28     /**
29      * <pre>
30      * Key is keyboard key code.
31      * Value is system milliseconds when key was pressed.
32      *
33      * So by reading the map one can determine currently pressed keys as well as duration.
34      * </pre>
35      */
36     private final Map<Integer, Long> pressedKeysToPressedTimeMap = new HashMap<>();
37     private final List<MouseClick> detectedMouseClicks = new ArrayList<>();
38     private final List<KeyEvent> detectedKeyEvents = new ArrayList<>();
39     private int wheelMovedDirection = 0;
40     private Point2D mouseDraggedDirection = new Point2D();
41     private Point2D oldMouseCoordinatesWhenDragging;
42     private ViewPanel viewPanel;
43     private Point2D currentMouseLocation;
44     private boolean mouseMoved;
45     private boolean mouseWithinWindow = false;
46
47     public HIDInputTracker(final ViewPanel viewPanel) {
48         this.viewPanel = viewPanel;
49         bind(viewPanel);
50     }
51
52     /**
53      * {@inheritDoc}
54      */
55     @Override
56     public boolean beforeRender(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
57         boolean viewUpdateNeeded = handleKeyboardEvents();
58         viewUpdateNeeded |= handleMouseClicksAndHover(viewPanel);
59         viewUpdateNeeded |= handleMouseDragging();
60         viewUpdateNeeded |= handleMouseVerticalScrolling();
61         return viewUpdateNeeded;
62     }
63
64     private void bind(final JPanel panel) {
65         panel.addMouseMotionListener(this);
66
67         panel.addKeyListener(this);
68
69         panel.addMouseListener(this);
70
71         panel.addMouseWheelListener(this);
72     }
73
74     /**
75      * @return <code>true</code> if view needs to be repainted.
76      */
77     private boolean handleKeyboardEvents() {
78         final UserInputHandler currentFocusOwner = viewPanel.getKeyboardFocusStack().getCurrentFocusOwner();
79         ArrayList<KeyEvent> unprocessedKeyboardEvents = getUnprocessedKeyboardEvents();
80
81         return currentFocusOwner != null
82                 && forwardKeyboardEventsToFocusOwner(currentFocusOwner, unprocessedKeyboardEvents);
83     }
84
85     private ArrayList<KeyEvent> getUnprocessedKeyboardEvents() {
86         synchronized (detectedKeyEvents) {
87             ArrayList<KeyEvent> result = new ArrayList<>(detectedKeyEvents);
88             detectedKeyEvents.clear();
89             return result;
90         }
91     }
92
93     /**
94      * @return <code>true</code> if view update is needed.
95      */
96     private boolean forwardKeyboardEventsToFocusOwner(
97             UserInputHandler currentFocusOwner, ArrayList<KeyEvent> keyEvents) {
98         boolean viewUpdateNeeded = false;
99
100         for (KeyEvent keyEvent : keyEvents)
101             viewUpdateNeeded |= processKeyEvent(currentFocusOwner, keyEvent);
102
103         return viewUpdateNeeded;
104     }
105
106     private boolean processKeyEvent(UserInputHandler currentFocusOwner, KeyEvent keyEvent) {
107         switch (keyEvent.getID()) {
108             case KeyEvent.KEY_PRESSED:
109                 return currentFocusOwner.keyPressed(keyEvent, viewPanel);
110
111             case KeyEvent.KEY_RELEASED:
112                 return currentFocusOwner.keyReleased(keyEvent, viewPanel);
113         }
114         return false;
115     }
116
117     /**
118      * @return <code>true</code> if view needs to be repainted.
119      */
120     private synchronized boolean handleMouseClicksAndHover(final ViewPanel viewPanel) {
121         MouseClick unprocessedMouseClick = findUnprocessedMouseClick();
122
123         if (unprocessedMouseClick != null) {
124             viewPanel.getRenderingContext().mouseClick = unprocessedMouseClick;
125             return false;
126         } else
127             return handleMouseHovering(viewPanel);
128     }
129
130     private MouseClick findUnprocessedMouseClick() {
131         synchronized (detectedMouseClicks) {
132             if (detectedMouseClicks.isEmpty())
133                 return null;
134
135             return detectedMouseClicks.remove(0);
136         }
137     }
138
139     private boolean handleMouseHovering(ViewPanel viewPanel) {
140         if (currentMouseLocation != null) {
141             RenderingContext renderingContext = viewPanel.getRenderingContext();
142             if (renderingContext != null)
143                renderingContext.mouseClick = new MouseClick(currentMouseLocation, 0);
144             // mouse click with button 0 amounts to mouse hovering event
145         }
146
147         if (mouseMoved) {
148             mouseMoved = false;
149             return true;
150         } else
151             return false;
152     }
153
154     boolean isKeyPressed(final int keyCode) {
155         return pressedKeysToPressedTimeMap.containsKey(keyCode);
156     }
157
158     @Override
159     public void keyPressed(final KeyEvent evt) {
160         synchronized (detectedKeyEvents) {
161             pressedKeysToPressedTimeMap.put(evt.getKeyCode(), System.currentTimeMillis());
162             detectedKeyEvents.add(evt);
163         }
164     }
165
166     @Override
167     public void keyReleased(final KeyEvent evt) {
168         synchronized (detectedKeyEvents) {
169             pressedKeysToPressedTimeMap.remove(evt.getKeyCode());
170             detectedKeyEvents.add(evt);
171         }
172     }
173
174     @Override
175     public void keyTyped(final KeyEvent e) {
176     }
177
178     @Override
179     public void mouseClicked(final MouseEvent e) {
180         synchronized (detectedMouseClicks) {
181             detectedMouseClicks.add(new MouseClick(e.getX(), e.getY(), e.getButton()));
182         }
183     }
184
185     @Override
186     public void mouseDragged(final java.awt.event.MouseEvent evt) {
187         final Point2D mouseLocation = new Point2D(evt.getX(), evt.getY());
188
189         if (oldMouseCoordinatesWhenDragging == null) {
190             oldMouseCoordinatesWhenDragging = mouseLocation;
191             return;
192         }
193
194         mouseDraggedDirection.add(mouseLocation.clone().subtract(oldMouseCoordinatesWhenDragging));
195
196         oldMouseCoordinatesWhenDragging = mouseLocation;
197     }
198
199     @Override
200     public void mouseEntered(final MouseEvent e) {
201         mouseWithinWindow = true;
202     }
203
204     @Override
205     public synchronized void mouseExited(final MouseEvent e) {
206         mouseWithinWindow = false;
207         currentMouseLocation = null;
208     }
209
210     @Override
211     public synchronized void mouseMoved(final MouseEvent e) {
212         currentMouseLocation = new Point2D(e.getX(), e.getY());
213         mouseMoved = true;
214     }
215
216     @Override
217     public void mousePressed(final MouseEvent e) {
218     }
219
220     @Override
221     public void mouseReleased(final java.awt.event.MouseEvent evt) {
222         oldMouseCoordinatesWhenDragging = null;
223     }
224
225     @Override
226     public void mouseWheelMoved(final java.awt.event.MouseWheelEvent evt) {
227         wheelMovedDirection += evt.getWheelRotation();
228     }
229
230     /**
231      * @return <code>true</code> if view needs to be repainted.
232      */
233     private boolean handleMouseVerticalScrolling() {
234         final Avatar avatar = viewPanel.getAvatar();
235         final double actualAcceleration = 50 * avatar.avatarAcceleration * (1 + (avatar.getMovementSpeed() / 10));
236         avatar.getMovementVector().y += (wheelMovedDirection * actualAcceleration);
237         avatar.enforceSpeedLimit();
238         boolean repaintNeeded = wheelMovedDirection != 0;
239         wheelMovedDirection = 0;
240         return repaintNeeded;
241     }
242
243     /**
244      * @return <code>true</code> if view needs to be repainted.
245      */
246     private boolean handleMouseDragging() {
247         // TODO: need to detect whether user moved mouse or touch screen
248
249         final Avatar avatar = viewPanel.getAvatar();
250         // for mouse
251         avatar.setAngleXZ(avatar.getAngleXZ() - ((float) mouseDraggedDirection.x / 50));
252         avatar.setAngleYZ(avatar.getAngleYZ() - ((float) mouseDraggedDirection.y / 50));
253
254         // for touch screen
255         // avatar.setAngleXZ(avatar.getAngleXZ() + ((float)
256         // mouseDraggedDirection.x / 50));
257         // avatar.setAngleYZ(avatar.getAngleYZ() + ((float)
258         // mouseDraggedDirection.y / 50));
259
260         boolean viewUpdateNeeded = !mouseDraggedDirection.isZero();
261         mouseDraggedDirection.zero();
262         return viewUpdateNeeded;
263     }
264
265 }