• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2009-2012 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17  *   may be used to endorse or promote products derived from this software
18  *   without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 package com.jme3.input;
34 
35 import com.jme3.collision.MotionAllowedListener;
36 import com.jme3.input.controls.*;
37 import com.jme3.math.FastMath;
38 import com.jme3.math.Matrix3f;
39 import com.jme3.math.Quaternion;
40 import com.jme3.math.Vector3f;
41 import com.jme3.renderer.Camera;
42 
43 /**
44  * A first person view camera controller.
45  * After creation, you must register the camera controller with the
46  * dispatcher using #registerWithDispatcher().
47  *
48  * Controls:
49  *  - Move the mouse to rotate the camera
50  *  - Mouse wheel for zooming in or out
51  *  - WASD keys for moving forward/backward and strafing
52  *  - QZ keys raise or lower the camera
53  */
54 public class FlyByCamera implements AnalogListener, ActionListener {
55 
56     private static String[] mappings = new String[]{
57             "FLYCAM_Left",
58             "FLYCAM_Right",
59             "FLYCAM_Up",
60             "FLYCAM_Down",
61 
62             "FLYCAM_StrafeLeft",
63             "FLYCAM_StrafeRight",
64             "FLYCAM_Forward",
65             "FLYCAM_Backward",
66 
67             "FLYCAM_ZoomIn",
68             "FLYCAM_ZoomOut",
69             "FLYCAM_RotateDrag",
70 
71             "FLYCAM_Rise",
72             "FLYCAM_Lower"
73         };
74 
75     protected Camera cam;
76     protected Vector3f initialUpVec;
77     protected float rotationSpeed = 1f;
78     protected float moveSpeed = 3f;
79     protected MotionAllowedListener motionAllowed = null;
80     protected boolean enabled = true;
81     protected boolean dragToRotate = false;
82     protected boolean canRotate = false;
83     protected InputManager inputManager;
84 
85     /**
86      * Creates a new FlyByCamera to control the given Camera object.
87      * @param cam
88      */
FlyByCamera(Camera cam)89     public FlyByCamera(Camera cam){
90         this.cam = cam;
91         initialUpVec = cam.getUp().clone();
92     }
93 
94     /**
95      * Sets the up vector that should be used for the camera.
96      * @param upVec
97      */
setUpVector(Vector3f upVec)98     public void setUpVector(Vector3f upVec) {
99        initialUpVec.set(upVec);
100     }
101 
setMotionAllowedListener(MotionAllowedListener listener)102     public void setMotionAllowedListener(MotionAllowedListener listener){
103         this.motionAllowed = listener;
104     }
105 
106     /**
107      * Sets the move speed. The speed is given in world units per second.
108      * @param moveSpeed
109      */
setMoveSpeed(float moveSpeed)110     public void setMoveSpeed(float moveSpeed){
111         this.moveSpeed = moveSpeed;
112     }
113 
114     /**
115      * Sets the rotation speed.
116      * @param rotationSpeed
117      */
setRotationSpeed(float rotationSpeed)118     public void setRotationSpeed(float rotationSpeed){
119         this.rotationSpeed = rotationSpeed;
120     }
121 
122     /**
123      * @param enable If false, the camera will ignore input.
124      */
setEnabled(boolean enable)125     public void setEnabled(boolean enable){
126         if (enabled && !enable){
127             if (inputManager!= null && (!dragToRotate || (dragToRotate && canRotate))){
128                 inputManager.setCursorVisible(true);
129             }
130         }
131         enabled = enable;
132     }
133 
134     /**
135      * @return If enabled
136      * @see FlyByCamera#setEnabled(boolean)
137      */
isEnabled()138     public boolean isEnabled(){
139         return enabled;
140     }
141 
142     /**
143      * @return If drag to rotate feature is enabled.
144      *
145      * @see FlyByCamera#setDragToRotate(boolean)
146      */
isDragToRotate()147     public boolean isDragToRotate() {
148         return dragToRotate;
149     }
150 
151     /**
152      * Set if drag to rotate mode is enabled.
153      *
154      * When true, the user must hold the mouse button
155      * and drag over the screen to rotate the camera, and the cursor is
156      * visible until dragged. Otherwise, the cursor is invisible at all times
157      * and holding the mouse button is not needed to rotate the camera.
158      * This feature is disabled by default.
159      *
160      * @param dragToRotate True if drag to rotate mode is enabled.
161      */
setDragToRotate(boolean dragToRotate)162     public void setDragToRotate(boolean dragToRotate) {
163         this.dragToRotate = dragToRotate;
164         if (inputManager != null) {
165             inputManager.setCursorVisible(dragToRotate);
166         }
167     }
168 
169     /**
170      * Registers the FlyByCamera to receive input events from the provided
171      * Dispatcher.
172      * @param inputManager
173      */
registerWithInput(InputManager inputManager)174     public void registerWithInput(InputManager inputManager){
175         this.inputManager = inputManager;
176 
177         // both mouse and button - rotation of cam
178         inputManager.addMapping("FLYCAM_Left", new MouseAxisTrigger(MouseInput.AXIS_X, true),
179                                                new KeyTrigger(KeyInput.KEY_LEFT));
180 
181         inputManager.addMapping("FLYCAM_Right", new MouseAxisTrigger(MouseInput.AXIS_X, false),
182                                                 new KeyTrigger(KeyInput.KEY_RIGHT));
183 
184         inputManager.addMapping("FLYCAM_Up", new MouseAxisTrigger(MouseInput.AXIS_Y, false),
185                                              new KeyTrigger(KeyInput.KEY_UP));
186 
187         inputManager.addMapping("FLYCAM_Down", new MouseAxisTrigger(MouseInput.AXIS_Y, true),
188                                                new KeyTrigger(KeyInput.KEY_DOWN));
189 
190         // mouse only - zoom in/out with wheel, and rotate drag
191         inputManager.addMapping("FLYCAM_ZoomIn", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
192         inputManager.addMapping("FLYCAM_ZoomOut", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
193         inputManager.addMapping("FLYCAM_RotateDrag", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
194 
195         // keyboard only WASD for movement and WZ for rise/lower height
196         inputManager.addMapping("FLYCAM_StrafeLeft", new KeyTrigger(KeyInput.KEY_A));
197         inputManager.addMapping("FLYCAM_StrafeRight", new KeyTrigger(KeyInput.KEY_D));
198         inputManager.addMapping("FLYCAM_Forward", new KeyTrigger(KeyInput.KEY_W));
199         inputManager.addMapping("FLYCAM_Backward", new KeyTrigger(KeyInput.KEY_S));
200         inputManager.addMapping("FLYCAM_Rise", new KeyTrigger(KeyInput.KEY_Q));
201         inputManager.addMapping("FLYCAM_Lower", new KeyTrigger(KeyInput.KEY_Z));
202 
203         inputManager.addListener(this, mappings);
204         inputManager.setCursorVisible(dragToRotate || !isEnabled());
205 
206         Joystick[] joysticks = inputManager.getJoysticks();
207         if (joysticks != null && joysticks.length > 0){
208             Joystick joystick = joysticks[0];
209             joystick.assignAxis("FLYCAM_StrafeRight", "FLYCAM_StrafeLeft", JoyInput.AXIS_POV_X);
210             joystick.assignAxis("FLYCAM_Forward", "FLYCAM_Backward", JoyInput.AXIS_POV_Y);
211             joystick.assignAxis("FLYCAM_Right", "FLYCAM_Left", joystick.getXAxisIndex());
212             joystick.assignAxis("FLYCAM_Down", "FLYCAM_Up", joystick.getYAxisIndex());
213         }
214     }
215 
216     /**
217      * Registers the FlyByCamera to receive input events from the provided
218      * Dispatcher.
219      * @param inputManager
220      */
unregisterInput()221     public void unregisterInput(){
222 
223         if (inputManager == null) {
224             return;
225         }
226 
227         for (String s : mappings) {
228             if (inputManager.hasMapping(s)) {
229                 inputManager.deleteMapping( s );
230             }
231         }
232 
233         inputManager.removeListener(this);
234         inputManager.setCursorVisible(!dragToRotate);
235 
236         Joystick[] joysticks = inputManager.getJoysticks();
237         if (joysticks != null && joysticks.length > 0){
238             Joystick joystick = joysticks[0];
239 
240             // No way to unassing axis
241         }
242     }
243 
rotateCamera(float value, Vector3f axis)244     protected void rotateCamera(float value, Vector3f axis){
245         if (dragToRotate){
246             if (canRotate){
247 //                value = -value;
248             }else{
249                 return;
250             }
251         }
252 
253         Matrix3f mat = new Matrix3f();
254         mat.fromAngleNormalAxis(rotationSpeed * value, axis);
255 
256         Vector3f up = cam.getUp();
257         Vector3f left = cam.getLeft();
258         Vector3f dir = cam.getDirection();
259 
260         mat.mult(up, up);
261         mat.mult(left, left);
262         mat.mult(dir, dir);
263 
264         Quaternion q = new Quaternion();
265         q.fromAxes(left, up, dir);
266         q.normalize();
267 
268         cam.setAxes(q);
269     }
270 
zoomCamera(float value)271     protected void zoomCamera(float value){
272         // derive fovY value
273         float h = cam.getFrustumTop();
274         float w = cam.getFrustumRight();
275         float aspect = w / h;
276 
277         float near = cam.getFrustumNear();
278 
279         float fovY = FastMath.atan(h / near)
280                   / (FastMath.DEG_TO_RAD * .5f);
281         fovY += value * 0.1f;
282 
283         h = FastMath.tan( fovY * FastMath.DEG_TO_RAD * .5f) * near;
284         w = h * aspect;
285 
286         cam.setFrustumTop(h);
287         cam.setFrustumBottom(-h);
288         cam.setFrustumLeft(-w);
289         cam.setFrustumRight(w);
290     }
291 
riseCamera(float value)292     protected void riseCamera(float value){
293         Vector3f vel = new Vector3f(0, value * moveSpeed, 0);
294         Vector3f pos = cam.getLocation().clone();
295 
296         if (motionAllowed != null)
297             motionAllowed.checkMotionAllowed(pos, vel);
298         else
299             pos.addLocal(vel);
300 
301         cam.setLocation(pos);
302     }
303 
moveCamera(float value, boolean sideways)304     protected void moveCamera(float value, boolean sideways){
305         Vector3f vel = new Vector3f();
306         Vector3f pos = cam.getLocation().clone();
307 
308         if (sideways){
309             cam.getLeft(vel);
310         }else{
311             cam.getDirection(vel);
312         }
313         vel.multLocal(value * moveSpeed);
314 
315         if (motionAllowed != null)
316             motionAllowed.checkMotionAllowed(pos, vel);
317         else
318             pos.addLocal(vel);
319 
320         cam.setLocation(pos);
321     }
322 
onAnalog(String name, float value, float tpf)323     public void onAnalog(String name, float value, float tpf) {
324         if (!enabled)
325             return;
326 
327         if (name.equals("FLYCAM_Left")){
328             rotateCamera(value, initialUpVec);
329         }else if (name.equals("FLYCAM_Right")){
330             rotateCamera(-value, initialUpVec);
331         }else if (name.equals("FLYCAM_Up")){
332             rotateCamera(-value, cam.getLeft());
333         }else if (name.equals("FLYCAM_Down")){
334             rotateCamera(value, cam.getLeft());
335         }else if (name.equals("FLYCAM_Forward")){
336             moveCamera(value, false);
337         }else if (name.equals("FLYCAM_Backward")){
338             moveCamera(-value, false);
339         }else if (name.equals("FLYCAM_StrafeLeft")){
340             moveCamera(value, true);
341         }else if (name.equals("FLYCAM_StrafeRight")){
342             moveCamera(-value, true);
343         }else if (name.equals("FLYCAM_Rise")){
344             riseCamera(value);
345         }else if (name.equals("FLYCAM_Lower")){
346             riseCamera(-value);
347         }else if (name.equals("FLYCAM_ZoomIn")){
348             zoomCamera(value);
349         }else if (name.equals("FLYCAM_ZoomOut")){
350             zoomCamera(-value);
351         }
352     }
353 
onAction(String name, boolean value, float tpf)354     public void onAction(String name, boolean value, float tpf) {
355         if (!enabled)
356             return;
357 
358         if (name.equals("FLYCAM_RotateDrag") && dragToRotate){
359             canRotate = value;
360             inputManager.setCursorVisible(!value);
361         }
362     }
363 
364 }
365