/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.util;
import java.util.logging.Logger;
import org.lwjgl.glfw.GLFW;
import com.jme3.app.VREnvironment;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.lwjgl.GlfwMouseInputVR;
import com.jme3.input.vr.VRInputType;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import com.jme3.system.AppSettings;
import com.jme3.system.lwjgl.LwjglWindow;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.ui.Picture;
/**
* A class dedicated to the handling of the mouse within VR environment.
* @author Julien Seinturier - (c) 2016 - JOrigin project - <a href="http://www.jorigin.org">http:/www.jorigin.org</a>
*
*/
public class VRMouseManager {
private static final Logger logger = Logger.getLogger(VRMouseManager.class.getName());
private VREnvironment environment = null;
private final int AVERAGE_AMNT = 4;
private int avgCounter;
private Picture mouseImage;
private int recentCenterCount = 0;
private final Vector2f cursorPos = new Vector2f();
private float ySize, sensitivity = 8f, acceleration = 2f;
private final float[] lastXmv = new float[AVERAGE_AMNT], lastYmv = new float[AVERAGE_AMNT];
private boolean thumbstickMode;
private float moveScale = 1f;
private float avg(float[] arr) {
float amt = 0f;
for(float f : arr) amt += f;
return amt / arr.length;
}
/**
* Create a new VR mouse manager within the given {@link VREnvironment VR environment}.
* @param environment the VR environment of the mouse manager.
*/
public VRMouseManager(VREnvironment environment){
this.environment = environment;
}
/**
* Initialize the VR mouse manager.
*/
protected void initialize() {
logger.config("Initializing VR mouse manager.");
// load default mouseimage
mouseImage = new Picture("mouse");
setImage("Common/Util/mouse.png");
// hide default cursor by making it invisible
MouseInput mi = environment.getApplication().getContext().getMouseInput();
if( mi instanceof GlfwMouseInputVR ){
((GlfwMouseInputVR)mi).hideActiveCursor();
}
centerMouse();
logger.config("Initialized VR mouse manager [SUCCESS]");
}
public void setThumbstickMode(boolean set) {
thumbstickMode = set;
}
public boolean isThumbstickMode() {
return thumbstickMode;
}
/**
* Set the speed of the mouse.
* @param sensitivity the sensitivity of the mouse.
* @param acceleration the acceleration of the mouse.
* @see #getSpeedAcceleration()
* @see #getSpeedSensitivity()
*/
public void setSpeed(float sensitivity, float acceleration) {
this.sensitivity = sensitivity;
this.acceleration = acceleration;
}
/**
* Get the sensitivity of the mouse.
* @return the sensitivity of the mouse.
* @see #setSpeed(float, float)
*/
public float getSpeedSensitivity() {
return sensitivity;
}
/**
* Get the acceleration of the mouse.
* @return the acceleration of the mouse.
* @see #setSpeed(float, float)
*/
public float getSpeedAcceleration() {
return acceleration;
}
/**
* Set the mouse move scale.
* @param set the mouse move scale.
*/
public void setMouseMoveScale(float set) {
moveScale = set;
}
/**
* Set the image to use as mouse cursor. The given string describe an asset that the underlying application asset manager has to load.
* @param texture the image to use as mouse cursor.
*/
public void setImage(String texture) {
if (environment != null){
if (environment.getApplication() != null){
if( environment.isInVR() == false ){
Texture tex = environment.getApplication().getAssetManager().loadTexture(texture);
mouseImage.setTexture(environment.getApplication().getAssetManager(), (Texture2D)tex, true);
ySize = tex.getImage().getHeight();
mouseImage.setHeight(ySize);
mouseImage.setWidth(tex.getImage().getWidth());
mouseImage.getMaterial().getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
mouseImage.getMaterial().getAdditionalRenderState().setDepthWrite(false);
} else {
Texture tex = environment.getApplication().getAssetManager().loadTexture(texture);
mouseImage.setTexture(environment.getApplication().getAssetManager(), (Texture2D)tex, true);
ySize = tex.getImage().getHeight();
mouseImage.setHeight(ySize);
mouseImage.setWidth(tex.getImage().getWidth());
mouseImage.getMaterial().getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
mouseImage.getMaterial().getAdditionalRenderState().setDepthWrite(false);
}
} else {
throw new IllegalStateException("This VR environment is not attached to any application.");
}
} else {
throw new IllegalStateException("This VR view manager is not attached to any VR environment.");
}
}
/**
* Update analog controller as it was a mouse controller.
* @param inputIndex the index of the controller attached to the VR system.
* @param mouseListener the JMonkey mouse listener to trigger.
* @param mouseXName the mouseX identifier.
* @param mouseYName the mouseY identifier
* @param tpf the time per frame.
*/
public void updateAnalogAsMouse(int inputIndex, AnalogListener mouseListener, String mouseXName, String mouseYName, float tpf) {
if (environment != null){
if (environment.getApplication() != null){
// got a tracked controller to use as the "mouse"
if( environment.isInVR() == false ||
environment.getVRinput() == null ||
environment.getVRinput().isInputDeviceTracking(inputIndex) == false ){
return;
}
Vector2f tpDelta;
if( thumbstickMode ) {
tpDelta = environment.getVRinput().getAxis(inputIndex, VRInputType.ViveTrackpadAxis);
} else {
tpDelta = environment.getVRinput().getAxisDeltaSinceLastCall(inputIndex, VRInputType.ViveTrackpadAxis);
}
float Xamount = (float)Math.pow(Math.abs(tpDelta.x) * sensitivity, acceleration);
float Yamount = (float)Math.pow(Math.abs(tpDelta.y) * sensitivity, acceleration);
if( tpDelta.x < 0f ){
Xamount = -Xamount;
}
if( tpDelta.y < 0f ){
Yamount = -Yamount;
}
Xamount *= moveScale; Yamount *= moveScale;
if( mouseListener != null ) {
if( tpDelta.x != 0f && mouseXName != null ) mouseListener.onAnalog(mouseXName, Xamount * 0.2f, tpf);
if( tpDelta.y != 0f && mouseYName != null ) mouseListener.onAnalog(mouseYName, Yamount * 0.2f, tpf);
}
if( environment.getApplication().getInputManager().isCursorVisible() ) {
int index = (avgCounter+1) % AVERAGE_AMNT;
lastXmv[index] = Xamount * 133f;
lastYmv[index] = Yamount * 133f;
cursorPos.x -= avg(lastXmv);
cursorPos.y -= avg(lastYmv);
Vector2f maxsize = environment.getVRGUIManager().getCanvasSize();
if( cursorPos.x > maxsize.x ){
cursorPos.x = maxsize.x;
}
if( cursorPos.x < 0f ){
cursorPos.x = 0f;
}
if( cursorPos.y > maxsize.y ){
cursorPos.y = maxsize.y;
}
if( cursorPos.y < 0f ){
cursorPos.y = 0f;
}
}
} else {
throw new IllegalStateException("This VR environment is not attached to any application.");
}
} else {
throw new IllegalStateException("This VR view manager is not attached to any VR environment.");
}
}
/**
* Get the actual cursor position.
* @return the actual cursor position.
*/
public Vector2f getCursorPosition() {
if (environment != null){
if (environment.getApplication() != null){
if( environment.isInVR() ) {
return cursorPos;
}
return environment.getApplication().getInputManager().getCursorPosition();
} else {
throw new IllegalStateException("This VR environment is not attached to any application.");
}
} else {
throw new IllegalStateException("This VR view manager is not attached to any VR environment.");
}
}
/**
* Center the mouse on the display.
*/
public void centerMouse() {
if (environment != null){
if (environment.getApplication() != null){
// set mouse in center of the screen if newly added
Vector2f size = environment.getVRGUIManager().getCanvasSize();
MouseInput mi = environment.getApplication().getContext().getMouseInput();
AppSettings as = environment.getApplication().getContext().getSettings();
if( mi instanceof GlfwMouseInputVR ) ((GlfwMouseInputVR)mi).setCursorPosition((int)(as.getWidth() / 2f), (int)(as.getHeight() / 2f));
if( environment.isInVR() ) {
cursorPos.x = size.x / 2f;
cursorPos.y = size.y / 2f;
recentCenterCount = 2;
}
} else {
throw new IllegalStateException("This VR environment is not attached to any application.");
}
} else {
throw new IllegalStateException("This VR view manager is not attached to any VR environment.");
}
}
/**
* Update the mouse manager. This method should not be called manually.
* The standard behavior for this method is to be called from the {@link VRViewManager#update(float) update method} of the attached {@link VRViewManager VR view manager}.
* @param tpf the time per frame.
*/
protected void update(float tpf) {
// if we are showing the cursor, add our picture as it
if( environment.getApplication().getInputManager().isCursorVisible() ) {
if( mouseImage.getParent() == null ) {
environment.getApplication().getGuiViewPort().attachScene(mouseImage);
centerMouse();
// the "real" mouse pointer should stay hidden
if (environment.getApplication().getContext() instanceof LwjglWindow){
GLFW.glfwSetInputMode(((LwjglWindow)environment.getApplication().getContext()).getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
}
}
// handle mouse movements, which may be in addition to (or exclusive from) tracked movement
MouseInput mi = environment.getApplication().getContext().getMouseInput();
if( mi instanceof GlfwMouseInputVR ) {
if( recentCenterCount <= 0 ) {
//Vector2f winratio = VRGuiManager.getCanvasToWindowRatio();
cursorPos.x += ((GlfwMouseInputVR)mi).getLastDeltaX();// * winratio.x;
cursorPos.y += ((GlfwMouseInputVR)mi).getLastDeltaY();// * winratio.y;
if( cursorPos.x < 0f ) cursorPos.x = 0f;
if( cursorPos.y < 0f ) cursorPos.y = 0f;
if( cursorPos.x > environment.getVRGUIManager().getCanvasSize().x ) cursorPos.x = environment.getVRGUIManager().getCanvasSize().x;
if( cursorPos.y > environment.getVRGUIManager().getCanvasSize().y ) cursorPos.y = environment.getVRGUIManager().getCanvasSize().y;
} else recentCenterCount--;
((GlfwMouseInputVR)mi).clearDeltas();
}
// ok, update the cursor graphic position
Vector2f currentPos = getCursorPosition();
mouseImage.setLocalTranslation(currentPos.x, currentPos.y - ySize, environment.getVRGUIManager().getGuiDistance() + 1f);
mouseImage.updateGeometricState();
} else if( mouseImage.getParent() != null ) {
Node n = mouseImage.getParent();
mouseImage.removeFromParent();
if (n != null){
n.updateGeometricState();
}
}
}
}