package com.jogamp.opengl.test.junit.graph.demos.ui;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2ES2;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLRunnable;
import com.jogamp.opengl.fixedfunc.GLMatrixFunc;
import com.jogamp.graph.curve.opengl.RegionRenderer;
import com.jogamp.newt.event.GestureHandler;
import com.jogamp.newt.event.InputEvent;
import com.jogamp.newt.event.MouseEvent;
import com.jogamp.newt.event.MouseListener;
import com.jogamp.newt.event.PinchToZoomGesture;
import com.jogamp.newt.event.GestureHandler.GestureEvent;
import com.jogamp.newt.opengl.GLWindow;
import com.jogamp.opengl.math.FloatUtil;
import com.jogamp.opengl.math.Quaternion;
import com.jogamp.opengl.math.Ray;
import com.jogamp.opengl.math.VectorUtil;
import com.jogamp.opengl.math.geom.AABBox;
import com.jogamp.opengl.util.PMVMatrix;
public class SceneUIController implements GLEventListener{
private final ArrayList<UIShape> shapes = new ArrayList<UIShape>();
private final float sceneDist, zNear, zFar;
private RegionRenderer renderer;
private final int[] sampleCount = new int[1];
/** Describing the bounding box in model-coordinates of the near-plane parallel at distance one. */
private final AABBox nearPlane1Box = new AABBox();
private final int[] viewport = new int[] { 0, 0, 0, 0 };
private final float[] sceneScale = new float[3];
private final float[] scenePlaneOrigin = new float[3];
private volatile UIShape activeShape = null;
private SBCMouseListener sbcMouseListener = null;
private SBCGestureListener sbcGestureListener = null;
private PinchToZoomGesture pinchToZoomGesture = null;
private GLAutoDrawable cDrawable = null;
public SceneUIController(final float sceneDist, final float zNear, final float zFar) {
this(null, sceneDist, zNear, zFar);
}
public SceneUIController(final RegionRenderer renderer, final float sceneDist, final float zNear, final float zFar) {
this.renderer = renderer;
this.sceneDist = sceneDist;
this.zFar = zFar;
this.zNear = zNear;
this.sampleCount[0] = 4;
}
public void setRenderer(final RegionRenderer renderer) {
this.renderer = renderer;
}
public void attachInputListenerTo(final GLWindow window) {
if(null == sbcMouseListener) {
sbcMouseListener = new SBCMouseListener();
window.addMouseListener(sbcMouseListener);
sbcGestureListener = new SBCGestureListener();
window.addGestureListener(sbcGestureListener);
pinchToZoomGesture = new PinchToZoomGesture(window.getNativeSurface(), false);
window.addGestureHandler(pinchToZoomGesture);
}
}
public void detachInputListenerFrom(final GLWindow window) {
if(null != sbcMouseListener) {
window.removeMouseListener(sbcMouseListener);
sbcMouseListener = null;
window.removeGestureListener(sbcGestureListener);
sbcGestureListener = null;
window.removeGestureHandler(pinchToZoomGesture);
pinchToZoomGesture = null;
}
}
public ArrayList<UIShape> getShapes() {
return shapes;
}
public void addShape(final UIShape b) {
shapes.add(b);
}
public void removeShape(final UIShape b) {
shapes.remove(b);
}
public int getSampleCount() { return sampleCount[0]; }
public int setSampleCount(final int v) {
sampleCount[0] = Math.min(8, Math.max(v, 1)); // clip
markAllShapesDirty();
return sampleCount[0];
}
public void setAllShapesQuality(final int q) {
for(int i=0; i<shapes.size(); i++) {
shapes.get(i).setQuality(q);
}
}
public void setAllShapesSharpness(final float sharpness) {
for(int i=0; i<shapes.size(); i++) {
shapes.get(i).setSharpness(sharpness);
}
}
public void markAllShapesDirty() {
for(int i=0; i<shapes.size(); i++) {
shapes.get(i).markShapeDirty();
}
}
public final float[] getSceneScale() { return sceneScale; }
public final float[] getScenePlaneOrigin() { return scenePlaneOrigin; }
@Override
public void init(final GLAutoDrawable drawable) {
System.err.println("SceneUIController: init");
cDrawable = drawable;
}
private void transformShape(final PMVMatrix pmv, final UIShape uiShape) {
final float[] uiTranslate = uiShape.getTranslate();
pmv.glTranslatef(uiTranslate[0], uiTranslate[1], uiTranslate[2]);
// final float dz = 100f;
final Quaternion quat = uiShape.getRotation();
final boolean rotate = !quat.isIdentity();
final float[] uiScale = uiShape.getScale();
final boolean scale = !VectorUtil.isVec3Equal(uiScale, 0, VectorUtil.VEC3_ONE, 0, FloatUtil.EPSILON);
if( rotate || scale ) {
final float[] rotOrigin = uiShape.getRotationOrigin();
final boolean pivot = !VectorUtil.isVec3Zero(rotOrigin, 0, FloatUtil.EPSILON);
// pmv.glTranslatef(0f, 0f, dz);
if( pivot ) {
pmv.glTranslatef(rotOrigin[0], rotOrigin[1], rotOrigin[2]);
}
if( scale ) {
pmv.glScalef(uiScale[0], uiScale[1], uiScale[2]);
}
if( rotate ) {
pmv.glRotate(quat);
}
if( pivot ) {
pmv.glTranslatef(-rotOrigin[0], -rotOrigin[1], -rotOrigin[2]);
}
// pmv.glTranslatef(0f, 0f, -dz);
}
}
private static Comparator<UIShape> shapeZAscComparator = new Comparator<UIShape>() {
@Override
public int compare(final UIShape s1, final UIShape s2) {
final float s1Z = s1.getBounds().getMinZ()+s1.getTranslate()[2];
final float s2Z = s2.getBounds().getMinZ()+s2.getTranslate()[2];
if( FloatUtil.isEqual(s1Z, s2Z, FloatUtil.EPSILON) ) {
return 0;
} else if( s1Z < s2Z ){
return -1;
} else {
return 1;
}
} };
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public void display(final GLAutoDrawable drawable) {
final GL2ES2 gl = drawable.getGL().getGL2ES2();
gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
final PMVMatrix pmv = renderer.getMatrix();
pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
final Object[] shapesS = shapes.toArray();
Arrays.sort(shapesS, (Comparator)shapeZAscComparator);
renderer.enable(gl, true);
//final int shapeCount = shapes.size();
final int shapeCount = shapesS.length;
for(int i=0; i<shapeCount; i++) {
// final UIShape uiShape = shapes.get(i);
final UIShape uiShape = (UIShape)shapesS[i];
// System.err.println("Id "+i+": "+uiShape);
if( uiShape.isEnabled() ) {
uiShape.validate(gl, renderer);
pmv.glPushMatrix();
transformShape(pmv, uiShape);
uiShape.drawShape(gl, renderer, sampleCount);
pmv.glPopMatrix();
}
}
renderer.enable(gl, false);
}
public void pickShape(final int glWinX, final int glWinY, final float[] objPos, final UIShape[] shape, final Runnable runnable) {
if( null == cDrawable ) {
return;
}
cDrawable.invoke(false, new GLRunnable() {
@Override
public boolean run(final GLAutoDrawable drawable) {
shape[0] = pickShapeImpl(glWinX, glWinY, objPos);
if( null != shape[0] ) {
runnable.run();
}
return true;
} } );
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private UIShape pickShapeImpl(final int glWinX, final int glWinY, final float[] objPos) {
final float winZ0 = 0f;
final float winZ1 = 0.3f;
/**
final FloatBuffer winZRB = Buffers.newDirectFloatBuffer(1);
gl.glReadPixels( x, y, 1, 1, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_FLOAT, winZRB);
winZ1 = winZRB.get(0); // dir
*/
final PMVMatrix pmv = renderer.getMatrix();
pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
final Ray ray = new Ray();
final Object[] shapesS = shapes.toArray();
Arrays.sort(shapesS, (Comparator)shapeZAscComparator);
for(int i=shapesS.length-1; i>=0; i--) {
final UIShape uiShape = (UIShape)shapesS[i];
if( uiShape.isEnabled() ) {
pmv.glPushMatrix();
transformShape(pmv, uiShape);
final boolean ok = pmv.gluUnProjectRay(glWinX, glWinY, winZ0, winZ1, viewport, 0, ray);
pmv.glPopMatrix();
if( ok ) {
final AABBox sbox = uiShape.getBounds();
if( sbox.intersectsRay(ray) ) {
// System.err.printf("Pick.0: shape %d, [%d, %d, %f/%f] -> %s%n", i, glWinX, glWinY, winZ0, winZ1, ray);
if( null == sbox.getRayIntersection(objPos, ray, FloatUtil.EPSILON, true, dpyTmp1V3, dpyTmp2V3, dpyTmp3V3) ) {
throw new InternalError("Ray "+ray+", box "+sbox);
}
// System.err.printf("Pick.1: shape %d @ [%f, %f, %f], within %s%n", i, objPos[0], objPos[1], objPos[2], uiShape.getBounds());
return uiShape;
}
}
}
}
return null;
}
private final float[] dpyTmp1V3 = new float[3];
private final float[] dpyTmp2V3 = new float[3];
private final float[] dpyTmp3V3 = new float[3];
public void windowToShapeCoords(final UIShape activeShape, final int glWinX, final int glWinY, final float[] objPos, final Runnable runnable) {
if( null == cDrawable || null == activeShape ) {
return;
}
cDrawable.invoke(false, new GLRunnable() {
@Override
public boolean run(final GLAutoDrawable drawable) {
if( windowToShapeCoordsImpl(activeShape, glWinX, glWinY, objPos) ) {
runnable.run();
}
return true;
} } );
}
private boolean windowToShapeCoordsImpl(final UIShape activeShape, final int glWinX, final int glWinY, final float[] objPos) {
final PMVMatrix pmv = renderer.getMatrix();
pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
pmv.glPushMatrix();
transformShape(pmv, activeShape);
boolean res = false;
final float[] ctr = activeShape.getBounds().getCenter();
if( pmv.gluProject(ctr[0], ctr[1], ctr[2], viewport, 0, dpyTmp1V3, 0) ) {
// System.err.printf("winToShapeCoords.0: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", shapeId, ctr[0], ctr[1], ctr[2], dpyTmp1V3[0], dpyTmp1V3[1], dpyTmp1V3[2]);
if( pmv.gluUnProject(glWinX, glWinY, dpyTmp1V3[2], viewport, 0, objPos, 0) ) {
// System.err.printf("winToShapeCoords.1: shape %d: win [%d, %d, %f] -> obj [%f, %f, %f]%n", shapeId, glWinX, glWinY, dpyTmp1V3[2], objPos[0], objPos[1], objPos[2]);
res = true;
}
}
pmv.glPopMatrix();
return res;
}
@Override
public void dispose(final GLAutoDrawable drawable) {
System.err.println("SceneUIController: dispose");
final GL2ES2 gl = drawable.getGL().getGL2ES2();
for(int i=0; i<shapes.size(); i++) {
shapes.get(i).destroy(gl, renderer);
}
shapes.clear();
cDrawable = null;
}
public static void mapWin2ObjectCoords(final PMVMatrix pmv, final int[] view,
final float zNear, final float zFar,
final float orthoX, final float orthoY, final float orthoDist,
final float[] winZ, final float[] objPos) {
winZ[0] = FloatUtil.getOrthoWinZ(orthoDist, zNear, zFar);
pmv.gluUnProject(orthoX, orthoY, winZ[0], view, 0, objPos, 0);
}
@Override
public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) {
viewport[0] = x;
viewport[1] = y;
viewport[2] = width;
viewport[3] = height;
final PMVMatrix pmv = renderer.getMatrix();
renderer.reshapePerspective(45.0f, width, height, zNear, zFar);
pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
pmv.glLoadIdentity();
System.err.printf("Reshape: zNear %f, zFar %f%n", zNear, zFar);
System.err.printf("Reshape: Frustum: %s%n", pmv.glGetFrustum());
{
final float orthoDist = 1f;
final float[] obj00Coord = new float[3];
final float[] obj11Coord = new float[3];
final float[] winZ = new float[1];
mapWin2ObjectCoords(pmv, viewport, zNear, zFar, 0f, 0f, orthoDist, winZ, obj00Coord);
System.err.printf("Reshape: mapped.00: [%f, %f, %f], winZ %f -> [%f, %f, %f]%n", 0f, 0f, orthoDist, winZ[0], obj00Coord[0], obj00Coord[1], obj00Coord[2]);
mapWin2ObjectCoords(pmv, viewport, zNear, zFar, width, height, orthoDist, winZ, obj11Coord);
System.err.printf("Reshape: mapped.11: [%f, %f, %f], winZ %f -> [%f, %f, %f]%n", (float)width, (float)height, orthoDist, winZ[0], obj11Coord[0], obj11Coord[1], obj11Coord[2]);
nearPlane1Box.setSize( obj00Coord[0], // lx
obj00Coord[1], // ly
obj00Coord[2], // lz
obj11Coord[0], // hx
obj11Coord[1], // hy
obj11Coord[2] );// hz
System.err.printf("Reshape: dist1Box: %s%n", nearPlane1Box);
}
scenePlaneOrigin[0] = nearPlane1Box.getMinX() * sceneDist;
scenePlaneOrigin[1] = nearPlane1Box.getMinY() * sceneDist;
scenePlaneOrigin[2] = nearPlane1Box.getMinZ() * sceneDist;
sceneScale[0] = ( nearPlane1Box.getWidth() * sceneDist ) / width;
sceneScale[1] = ( nearPlane1Box.getHeight() * sceneDist ) / height;
sceneScale[2] = 1f;
System.err.printf("Scene Origin [%f, %f, %f]%n", scenePlaneOrigin[0], scenePlaneOrigin[1], scenePlaneOrigin[2]);
System.err.printf("Scene Scale %f * [%f x %f] / [%d x %d] = [%f, %f, %f]%n",
sceneDist, nearPlane1Box.getWidth(), nearPlane1Box.getHeight(),
width, height,
sceneScale[0], sceneScale[1], sceneScale[2]);
pmv.glTranslatef(scenePlaneOrigin[0], scenePlaneOrigin[1], scenePlaneOrigin[2]);
pmv.glScalef(sceneScale[0], sceneScale[1], sceneScale[2]);
}
public final UIShape getShape(final int id) {
if( 0 > id ) {
return null;
}
return shapes.get(id);
}
public final UIShape getActiveShape() {
return activeShape;
}
public void release() {
setActiveShape(null);
}
private void setActiveShape(final UIShape shape) {
activeShape = shape;
}
private final class SBCGestureListener implements GestureHandler.GestureListener {
@Override
public void gestureDetected(final GestureEvent gh) {
if( null != activeShape ) {
// gesture .. delegate to active shape!
final InputEvent orig = gh.getTrigger();
if( orig instanceof MouseEvent ) {
final MouseEvent e = (MouseEvent) orig;
// flip to GL window coordinates
final int glWinX = e.getX();
final int glWinY = viewport[3] - e.getY() - 1;
final float[] objPos = new float[3];
final UIShape shape = activeShape;
windowToShapeCoords(shape, glWinX, glWinY, objPos, new Runnable() {
public void run() {
shape.dispatchGestureEvent(gh, glWinX, glWinY, objPos);
} } );
}
}
}
}
final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY) {
if( null == activeShape ) {
dispatchMouseEventPickShape(e, glWinX, glWinY, true);
} else {
dispatchMouseEventForShape(activeShape, e, glWinX, glWinY);
}
}
final void dispatchMouseEventPickShape(final MouseEvent e, final int glWinX, final int glWinY, final boolean setActive) {
final float[] objPos = new float[3];
final UIShape[] shape = { null };
pickShape(glWinX, glWinY, objPos, shape, new Runnable() {
public void run() {
if( setActive ) {
setActiveShape(shape[0]);
}
shape[0].dispatchMouseEvent(e, glWinX, glWinY, objPos);
} } );
}
final void dispatchMouseEventForShape(final UIShape shape, final MouseEvent e, final int glWinX, final int glWinY) {
final float[] objPos = new float[3];
windowToShapeCoords(shape, glWinX, glWinY, objPos, new Runnable() {
public void run() {
shape.dispatchMouseEvent(e, glWinX, glWinY, objPos);
} } );
}
private class SBCMouseListener implements MouseListener {
int lx=-1, ly=-1, lId=-1;
void clear() {
lx = -1; ly = -1; lId = -1;
}
@Override
public void mousePressed(final MouseEvent e) {
if( -1 == lId || e.getPointerId(0) == lId ) {
lx = e.getX();
ly = e.getY();
lId = e.getPointerId(0);
}
// flip to GL window coordinates
final int glWinX = e.getX();
final int glWinY = viewport[3] - e.getY() - 1;
dispatchMouseEvent(e, glWinX, glWinY);
}
@Override
public void mouseReleased(final MouseEvent e) {
// flip to GL window coordinates
final int glWinX = e.getX();
final int glWinY = viewport[3] - e.getY() - 1;
dispatchMouseEvent(e, glWinX, glWinY);
if( 1 == e.getPointerCount() ) {
// Release active shape: last pointer has been lifted!
release();
clear();
}
}
@Override
public void mouseClicked(final MouseEvent e) {
// flip to GL window coordinates
final int glWinX = e.getX();
final int glWinY = viewport[3] - e.getY() - 1;
// activeId should have been released by mouseRelease() already!
dispatchMouseEventPickShape(e, glWinX, glWinY, false);
// Release active shape: last pointer has been lifted!
release();
clear();
}
@Override
public void mouseDragged(final MouseEvent e) {
// drag activeShape, if no gesture-activity, only on 1st pointer
if( null != activeShape && !pinchToZoomGesture.isWithinGesture() && e.getPointerId(0) == lId ) {
lx = e.getX();
ly = e.getY();
// dragged .. delegate to active shape!
// flip to GL window coordinates
final int glWinX = lx;
final int glWinY = viewport[3] - ly - 1;
dispatchMouseEventForShape(activeShape, e, glWinX, glWinY);
}
}
@Override
public void mouseWheelMoved(final MouseEvent e) {
// flip to GL window coordinates
final int glWinX = lx;
final int glWinY = viewport[3] - ly - 1;
dispatchMouseEventPickShape(e, glWinX, glWinY, true);
}
@Override
public void mouseMoved(final MouseEvent e) {
if( -1 == lId || e.getPointerId(0) == lId ) {
lx = e.getX();
ly = e.getY();
lId = e.getPointerId(0);
}
}
@Override
public void mouseEntered(final MouseEvent e) { }
@Override
public void mouseExited(final MouseEvent e) {
release();
clear();
}
}
}