/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.util;
import com.jme3.app.VREnvironment;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Spatial;
import com.jme3.scene.CenterQuad;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.system.AppSettings;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import java.awt.GraphicsEnvironment;
import java.util.Iterator;
/**
* A class dedicated to the management and the display of a Graphical User Interface (GUI) within a VR environment.
* @author reden - phr00t - https://github.com/phr00t
* @author Julien Seinturier - (c) 2016 - JOrigin project - <a href="http://www.jorigin.org">http:/www.jorigin.org</a>
*
*/
public class VRGuiManager {
private Camera camLeft, camRight;
private float guiDistance = 1.5f;
private float guiScale = 1f;
private float guiPositioningElastic;
private VRGUIPositioningMode posMode = VRGUIPositioningMode.AUTO_CAM_ALL;
private final Matrix3f orient = new Matrix3f();
private Vector2f screenSize;
protected boolean wantsReposition;
private Vector2f ratio;
private final Vector3f EoldPos = new Vector3f();
private final Quaternion EoldDir = new Quaternion();
private final Vector3f look = new Vector3f();
private final Vector3f left = new Vector3f();
private final Vector3f temppos = new Vector3f();
private final Vector3f up = new Vector3f();
private boolean useCurvedSurface = false;
private boolean overdraw = false;
private Geometry guiQuad;
private Node guiQuadNode;
private ViewPort offView;
private Texture2D guiTexture;
private final Quaternion tempq = new Quaternion();
private VREnvironment environment = null;
/**
* Create a new GUI manager attached to the given app state.
* @param environment the VR environment to which this manager is attached to.
*/
public VRGuiManager(VREnvironment environment){
this.environment = environment;
}
/**
*
* Makes auto GUI positioning happen not immediately, but like an
* elastic connected to the headset. Setting to 0 disables (default)
* Higher settings make it track the headset quicker.
*
* @param elastic amount of elasticity
*/
public void setPositioningElasticity(float elastic) {
guiPositioningElastic = elastic;
}
public float getPositioningElasticity() {
return guiPositioningElastic;
}
/**
* Get the GUI {@link VRGUIPositioningMode positioning mode}.
* @return the GUI {@link VRGUIPositioningMode positioning mode}.
* @see #setPositioningMode(VRGUIPositioningMode)
*/
public VRGUIPositioningMode getPositioningMode() {
return posMode;
}
/**
* Set the GUI {@link VRGUIPositioningMode positioning mode}.
* @param mode the GUI {@link VRGUIPositioningMode positioning mode}.
* @see #getPositioningMode()
*/
public void setPositioningMode(VRGUIPositioningMode mode) {
posMode = mode;
}
/**
* Get the GUI canvas size. This method return the size in pixels of the GUI available area within the VR view.
* @return the GUI canvas size. This method return the size in pixels of the GUI available area within the VR view.
*/
public Vector2f getCanvasSize() {
if (environment != null){
if (environment.getApplication() != null){
if( screenSize == null ) {
if( environment.isInVR() && environment.getVRHardware() != null ) {
screenSize = new Vector2f();
environment.getVRHardware().getRenderSize(screenSize);
screenSize.multLocal(environment.getVRViewManager().getResolutionMuliplier());
} else {
AppSettings as = environment.getApplication().getContext().getSettings();
screenSize = new Vector2f(as.getWidth(), as.getHeight());
}
}
return screenSize;
} else {
throw new IllegalStateException("VR GUI manager underlying environment is not attached to any application.");
}
} else {
throw new IllegalStateException("VR GUI manager is not attached to any environment.");
}
}
/**
* Get the ratio between the {@link #getCanvasSize() GUI canvas size} and the application main windows (if available) or the screen size.
* @return the ratio between the {@link #getCanvasSize() GUI canvas size} and the application main windows (if available).
* @see #getCanvasSize()
*/
public Vector2f getCanvasToWindowRatio() {
if (environment != null){
if (environment.getApplication() != null){
if( ratio == null ) {
ratio = new Vector2f();
Vector2f canvas = getCanvasSize();
int width = Integer.min(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getWidth(),
environment.getApplication().getContext().getSettings().getWidth());
int height = Integer.min(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getHeight(),
environment.getApplication().getContext().getSettings().getHeight());
ratio.x = Float.max(1f, canvas.x / width);
ratio.y = Float.max(1f, canvas.y / height);
}
return ratio;
} else {
throw new IllegalStateException("VR GUI manager underlying environment is not attached to any application.");
}
} else {
throw new IllegalStateException("VR GUI manager is not attached to any environment.");
}
}
/**
* Inform this manager that it has to position the GUI.
*/
public void positionGui() {
wantsReposition = true;
}
/**
* Position the GUI to the given location.
* @param pos the position of the GUI.
* @param dir the rotation of the GUI.
* @param tpf the time per frame.
*/
private void positionTo(Vector3f pos, Quaternion dir, float tpf) {
if (environment != null){
Vector3f guiPos = guiQuadNode.getLocalTranslation();
guiPos.set(0f, 0f, guiDistance);
dir.mult(guiPos, guiPos);
guiPos.x += pos.x;
guiPos.y += pos.y + environment.getVRHeightAdjustment();
guiPos.z += pos.z;
if( guiPositioningElastic > 0f && posMode != VRGUIPositioningMode.MANUAL ) {
// mix pos & dir with current pos & dir
guiPos.interpolateLocal(EoldPos, guiPos, Float.min(1f, tpf * guiPositioningElastic));
EoldPos.set(guiPos);
}
} else {
throw new IllegalStateException("VR GUI manager is not attached to any environment.");
}
}
/**
* Update the GUI geometric state. This method should be called after GUI modification.
*/
protected void updateGuiQuadGeometricState() {
guiQuadNode.updateGeometricState();
}
/**
* Position the GUI without delay.
* @param tpf the time per frame.
*/
protected void positionGuiNow(float tpf) {
if (environment != null){
wantsReposition = false;
if( environment.isInVR() == false ){
return;
}
guiQuadNode.setLocalScale(guiDistance * guiScale * 4f, 4f * guiDistance * guiScale, 1f);
switch( posMode ) {
case MANUAL:
case AUTO_CAM_ALL_SKIP_PITCH:
case AUTO_CAM_ALL:
if( camLeft != null && camRight != null ) {
// get middle point
temppos.set(camLeft.getLocation()).interpolateLocal(camRight.getLocation(), 0.5f);
positionTo(temppos, camLeft.getRotation(), tpf);
}
rotateScreenTo(camLeft.getRotation(), tpf);
break;
case AUTO_OBSERVER_POS_CAM_ROTATION:
Object obs = environment.getObserver();
if( obs != null ) {
if( obs instanceof Camera ) {
positionTo(((Camera)obs).getLocation(), camLeft.getRotation(), tpf);
} else {
positionTo(((Spatial)obs).getWorldTranslation(), camLeft.getRotation(), tpf);
}
}
rotateScreenTo(camLeft.getRotation(), tpf);
break;
case AUTO_OBSERVER_ALL:
case AUTO_OBSERVER_ALL_CAMHEIGHT:
obs = environment.getObserver();
if( obs != null ) {
Quaternion q;
if( obs instanceof Camera ) {
q = ((Camera)obs).getRotation();
temppos.set(((Camera)obs).getLocation());
} else {
q = ((Spatial)obs).getWorldRotation();
temppos.set(((Spatial)obs).getWorldTranslation());
}
if( posMode == VRGUIPositioningMode.AUTO_OBSERVER_ALL_CAMHEIGHT ) {
temppos.y = camLeft.getLocation().y;
}
positionTo(temppos, q, tpf);
rotateScreenTo(q, tpf);
}
break;
}
} else {
throw new IllegalStateException("VR GUI manager is not attached to any environment.");
}
}
/**
* Rotate the GUI to the given direction.
* @param dir the direction to rotate to.
* @param tpf the time per frame.
*/
private void rotateScreenTo(Quaternion dir, float tpf) {
dir.getRotationColumn(2, look).negateLocal();
dir.getRotationColumn(0, left).negateLocal();
orient.fromAxes(left, dir.getRotationColumn(1, up), look);
Quaternion rot = tempq.fromRotationMatrix(orient);
if( posMode == VRGUIPositioningMode.AUTO_CAM_ALL_SKIP_PITCH ){
VRUtil.stripToYaw(rot);
}
if( guiPositioningElastic > 0f && posMode != VRGUIPositioningMode.MANUAL ) {
// mix pos & dir with current pos & dir
EoldDir.nlerp(rot, tpf * guiPositioningElastic);
guiQuadNode.setLocalRotation(EoldDir);
} else {
guiQuadNode.setLocalRotation(rot);
}
}
/**
* Get the GUI distance from the observer.
* @return the GUI distance from the observer.
* @see #setGuiDistance(float)
*/
public float getGuiDistance() {
return guiDistance;
}
/**
* Set the GUI distance from the observer.
* @param newGuiDistance the GUI distance from the observer.
* @see #getGuiDistance()
*/
public void setGuiDistance(float newGuiDistance) {
guiDistance = newGuiDistance;
}
/**
* Get the GUI scale.
* @return the GUI scale.
* @see #setGuiScale(float)
*/
public float getGUIScale(){
return guiScale;
}
/**
* Set the GUI scale.
* @param scale the GUI scale.
* @see #getGUIScale()
*/
public void setGuiScale(float scale) {
guiScale = scale;
}
/**
* Adjust the GUI distance from the observer.
* This method increment / decrement the {@link #getGuiDistance() GUI distance} by the given value.
* @param adjustAmount the increment (if positive) / decrement (if negative) value of the GUI distance.
*/
public void adjustGuiDistance(float adjustAmount) {
guiDistance += adjustAmount;
}
/**
* Set up the GUI.
* @param leftcam the left eye camera.
* @param rightcam the right eye camera.
* @param left the left eye viewport.
* @param right the right eye viewport.
*/
protected void setupGui(Camera leftcam, Camera rightcam, ViewPort left, ViewPort right) {
if (environment != null){
if( environment.hasTraditionalGUIOverlay() ) {
camLeft = leftcam;
camRight = rightcam;
Spatial guiScene = getGuiQuad(camLeft);
left.attachScene(guiScene);
if( right != null ) right.attachScene(guiScene);
setPositioningMode(posMode);
}
} else {
throw new IllegalStateException("VR GUI manager is not attached to any environment.");
}
}
/**
* Get if the GUI has to use curved surface.
* @return <code>true</code> if the GUI has to use curved surface and <code>false</code> otherwise.
* @see #setCurvedSurface(boolean)
*/
public boolean isCurverSurface(){
return useCurvedSurface;
}
/**
* Set if the GUI has to use curved surface.
* @param set <code>true</code> if the GUI has to use curved surface and <code>false</code> otherwise.
* @see #isCurverSurface()
*/
public void setCurvedSurface(boolean set) {
useCurvedSurface = set;
}
/**
* Get if the GUI has to be displayed even if it is behind objects.
* @return <code>true</code> if the GUI has to use curved surface and <code>false</code> otherwise.
* @see #setGuiOverdraw(boolean)
*/
public boolean isGuiOverdraw(){
return overdraw;
}
/**
* Set if the GUI has to be displayed even if it is behind objects.
* @param set <code>true</code> if the GUI has to use curved surface and <code>false</code> otherwise.
* @see #isGuiOverdraw()
*/
public void setGuiOverdraw(boolean set) {
overdraw = set;
}
/**
* Create a GUI quad for the given camera.
* @param sourceCam the camera
* @return a GUI quad for the given camera.
*/
private Spatial getGuiQuad(Camera sourceCam){
if (environment != null){
if (environment.getApplication() != null){
if( guiQuadNode == null ) {
Vector2f guiCanvasSize = getCanvasSize();
Camera offCamera = sourceCam.clone();
offCamera.setParallelProjection(true);
offCamera.setLocation(Vector3f.ZERO);
offCamera.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y);
offView = environment.getApplication().getRenderManager().createPreView("GUI View", offCamera);
offView.setClearFlags(true, true, true);
offView.setBackgroundColor(ColorRGBA.BlackNoAlpha);
// create offscreen framebuffer
FrameBuffer offBuffer = new FrameBuffer((int)guiCanvasSize.x, (int)guiCanvasSize.y, 1);
//setup framebuffer's texture
guiTexture = new Texture2D((int)guiCanvasSize.x, (int)guiCanvasSize.y, Format.RGBA8);
guiTexture.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
guiTexture.setMagFilter(Texture.MagFilter.Bilinear);
//setup framebuffer to use texture
offBuffer.setDepthBuffer(Format.Depth);
offBuffer.setColorTexture(guiTexture);
//set viewport to render to offscreen framebuffer
offView.setOutputFrameBuffer(offBuffer);
// setup framebuffer's scene
Iterator<Spatial> spatialIter = environment.getApplication().getGuiViewPort().getScenes().iterator();
while(spatialIter.hasNext()){
offView.attachScene(spatialIter.next());
}
if( useCurvedSurface ) {
guiQuad = (Geometry)environment.getApplication().getAssetManager().loadModel("Common/Util/gui_mesh.j3o");
} else {
guiQuad = new Geometry("guiQuad", new CenterQuad(1f, 1f));
}
Material mat = new Material(environment.getApplication().getAssetManager(), "Common/MatDefs/VR/GuiOverlay.j3md");
mat.getAdditionalRenderState().setDepthTest(!overdraw);
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
mat.getAdditionalRenderState().setDepthWrite(false);
mat.setTexture("ColorMap", guiTexture);
guiQuad.setQueueBucket(Bucket.Translucent);
guiQuad.setMaterial(mat);
guiQuadNode = new Node("gui-quad-node");
guiQuadNode.setQueueBucket(Bucket.Translucent);
guiQuadNode.attachChild(guiQuad);
}
return guiQuadNode;
} else {
throw new IllegalStateException("VR GUI manager underlying environment is not attached to any application.");
}
} else {
throw new IllegalStateException("VR GUI manager is not attached to any environment.");
}
}
}