aboutsummaryrefslogtreecommitdiffstats
path: root/src/graphui/classes/com/jogamp/graph/ui/Scene.java
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2023-03-31 11:06:44 +0200
committerSven Gothel <[email protected]>2023-03-31 11:06:44 +0200
commit4f32f3aba62a73cafecec8af461cff4d0d475882 (patch)
tree58f6e7f0800a1ca8b793966520d6008cc6f2080b /src/graphui/classes/com/jogamp/graph/ui/Scene.java
parent759a381963476e2e64b7afba0d6ef2bafeb35417 (diff)
GraphUI: Relocate com.jogamp.graph.ui.gl.* -> com.jogamp.graph.ui.*, resolve GL/VK abstraction at a later time differently
Actual GPU rendering toolkit dependency can be abstracted differently, i.e. GPU <- { GL, VK } etc.
Diffstat (limited to 'src/graphui/classes/com/jogamp/graph/ui/Scene.java')
-rw-r--r--src/graphui/classes/com/jogamp/graph/ui/Scene.java1073
1 files changed, 1073 insertions, 0 deletions
diff --git a/src/graphui/classes/com/jogamp/graph/ui/Scene.java b/src/graphui/classes/com/jogamp/graph/ui/Scene.java
new file mode 100644
index 000000000..cf0f96b28
--- /dev/null
+++ b/src/graphui/classes/com/jogamp/graph/ui/Scene.java
@@ -0,0 +1,1073 @@
+/**
+ * Copyright 2010-2023 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.graph.ui;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Locale;
+
+import com.jogamp.opengl.FPSCounter;
+import com.jogamp.opengl.GL;
+import com.jogamp.opengl.GL2ES2;
+import com.jogamp.opengl.GLAutoDrawable;
+import com.jogamp.opengl.GLCapabilitiesImmutable;
+import com.jogamp.opengl.GLEventListener;
+import com.jogamp.opengl.GLException;
+import com.jogamp.opengl.GLRunnable;
+import com.jogamp.opengl.fixedfunc.GLMatrixFunc;
+import com.jogamp.common.nio.Buffers;
+import com.jogamp.graph.curve.Region;
+import com.jogamp.graph.curve.opengl.RegionRenderer;
+import com.jogamp.graph.curve.opengl.RenderState;
+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.Ray;
+import com.jogamp.opengl.math.geom.AABBox;
+import com.jogamp.opengl.util.GLPixelStorageModes;
+import com.jogamp.opengl.util.GLReadBufferUtil;
+import com.jogamp.opengl.util.PMVMatrix;
+
+/**
+ * GraphUI Scene
+ * <p>
+ * GraphUI is GPU based and resolution independent.
+ * </p>
+ * <p>
+ * GraphUI is intended to become an immediate- and retained-mode API.
+ * </p>
+ * <p>
+ * To utilize a Scene instance directly as a {@link GLEventListener},
+ * user needs to {@link #setClearParams(float[], int)}.
+ *
+ * Otherwise user may just call provided {@link GLEventListener} from within their own workflow
+ * - {@link GLEventListener#init(GLAutoDrawable)}
+ * - {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int)}
+ * - {@link GLEventListener#display(GLAutoDrawable)}
+ * - {@link GLEventListener#dispose(GLAutoDrawable)}
+ * </p>
+ * <p>
+ * {@link #setPMVMatrixSetup(PMVMatrixSetup)} maybe used to provide a custom {@link PMVMatrix} setup.
+ * </p>
+ * @see Shape
+ */
+public final class Scene implements GLEventListener {
+ /** Default scene distance on z-axis to projection is -1/5f. */
+ public static final float DEFAULT_SCENE_DIST = -1/5f;
+ /** Default projection angle in degrees value is 45.0. */
+ public static final float DEFAULT_ANGLE = 45.0f;
+ /** Default projection z-near value is 0.1. */
+ public static final float DEFAULT_ZNEAR = 0.1f;
+ /** Default projection z-far value is 7000. */
+ public static final float DEFAULT_ZFAR = 7000.0f;
+
+ @SuppressWarnings("unused")
+ private static final boolean DEBUG = false;
+
+ private final ArrayList<Shape> shapes = new ArrayList<Shape>();
+ private float dbgbox_thickness = 0f;
+ private boolean doFrustumCulling = false;
+
+ private float[] clearColor = null;
+ private int clearMask;
+
+ private final RegionRenderer renderer;
+
+ private final int[] sampleCount = new int[1];
+
+ /** Describing the bounding box in shape's object model-coordinates of the near-plane parallel at its scene-distance, post {@link #translate(PMVMatrix)} */
+ private final AABBox planeBox = new AABBox(0f, 0f, 0f, 0f, 0f, 0f);
+
+ private volatile Shape activeShape = null;
+
+ private SBCMouseListener sbcMouseListener = null;
+ private SBCGestureListener sbcGestureListener = null;
+ private PinchToZoomGesture pinchToZoomGesture = null;
+
+ final GLReadBufferUtil screenshot;
+
+ private GLAutoDrawable cDrawable = null;
+
+ private static RegionRenderer createRenderer() {
+ return RegionRenderer.create(RegionRenderer.defaultBlendEnable, RegionRenderer.defaultBlendDisable);
+ }
+
+ /**
+ * Create a new scene with an internally created RegionRenderer
+ * and using default values {@link #DEFAULT_SCENE_DIST}, {@link #DEFAULT_ANGLE}, {@link #DEFAULT_ZNEAR} and {@link #DEFAULT_ZFAR}.
+ */
+ public Scene() {
+ this(createRenderer());
+ }
+
+ /**
+ * Create a new scene taking ownership of the given RegionRenderer
+ * and using default values {@link #DEFAULT_SCENE_DIST}, {@link #DEFAULT_ANGLE}, {@link #DEFAULT_ZNEAR} and {@link #DEFAULT_ZFAR}.
+ */
+ public Scene(final RegionRenderer renderer) {
+ if( null == renderer ) {
+ throw new IllegalArgumentException("Null RegionRenderer");
+ }
+ this.renderer = renderer;
+ this.sampleCount[0] = 4;
+ this.screenshot = new GLReadBufferUtil(false, false);
+ }
+
+ /** Returns the associated RegionRenderer */
+ public RegionRenderer getRenderer() { return renderer; }
+
+ /** Returns the associated RegionRenderer's RenderState. */
+ public RenderState getRenderState() { return renderer.getRenderState(); }
+
+ /**
+ * Sets the clear parameter for {@link GL#glClearColor(float, float, float, float) glClearColor(..)} and {@link GL#glClear(int) glClear(..)}
+ * to be issued at {@link #display(GLAutoDrawable)}.
+ *
+ * Without setting these parameter, user has to issue
+ * {@link GL#glClearColor(float, float, float, float) glClearColor(..)} and {@link GL#glClear(int) glClear(..)}
+ * before calling {@link #display(GLAutoDrawable)}.
+ *
+ * @param clearColor {@link GL#glClearColor(float, float, float, float) glClearColor(..)} arguments
+ * @param clearMask {@link GL#glClear(int) glClear(..)} mask, default is {@link GL#GL_COLOR_BUFFER_BIT} | {@link GL#GL_DEPTH_BUFFER_BIT}
+ */
+ public final void setClearParams(final float[] clearColor, final int clearMask) { this.clearColor = clearColor; this.clearMask = clearMask; }
+
+ /** Returns the {@link GL#glClearColor(float, float, float, float) glClearColor(..)} arguments, see {@link #setClearParams(float[], int)}. */
+ public final float[] getClearColor() { return clearColor; }
+
+ /** Returns the {@link GL#glClear(int) glClear(..)} mask, see {@link #setClearParams(float[], int)}. */
+ public final int getClearMask() { return clearMask; }
+
+ /** Enable or disable {@link PMVMatrix#glGetFrustum()} culling per {@link Shape}. Default is disabled. */
+ public final void setFrustumCullingEnabled(final boolean v) { doFrustumCulling = v; }
+
+ /** Return whether {@link #setFrustumCullingEnabled(boolean) frustum culling} is enabled. */
+ public final boolean isFrustumCullingEnabled() { return doFrustumCulling; }
+
+ 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<Shape> getShapes() {
+ return shapes;
+ }
+ public void addShape(final Shape s) {
+ s.setDebugBox(dbgbox_thickness);
+ shapes.add(s);
+ }
+ /** Removes given shape, keeps it alive. */
+ public void removeShape(final Shape s) {
+ s.setDebugBox(0f);
+ shapes.remove(s);
+ }
+ /** Removes all given shapes and destroys them. */
+ public void removeShape(final GL2ES2 gl, final Shape s) {
+ s.setDebugBox(0f);
+ shapes.remove(s);
+ s.destroy(gl, renderer);
+ }
+ public void addShapes(final Collection<? extends Shape> shapes) {
+ for(final Shape s : shapes) {
+ addShape(s);
+ }
+ }
+ /** Removes all given shapes, keeps them alive. */
+ public void removeShapes(final Collection<? extends Shape> shapes) {
+ for(final Shape s : shapes) {
+ removeShape(s);
+ }
+ }
+ /** Removes all given shapes and destroys them. */
+ public void removeShapes(final GL2ES2 gl, final Collection<? extends Shape> shapes) {
+ for(final Shape s : shapes) {
+ removeShape(gl, s);
+ }
+ }
+ public Shape getShapeByIdx(final int id) {
+ if( 0 > id ) {
+ return null;
+ }
+ return shapes.get(id);
+ }
+ public Shape getShapeByName(final int name) {
+ for(final Shape b : shapes) {
+ if(b.getName() == name ) {
+ return b;
+ }
+ }
+ return null;
+ }
+
+ public int getSampleCount() { return sampleCount[0]; }
+ public int setSampleCount(final int v) {
+ sampleCount[0] = Math.min(8, Math.max(v, 0)); // clip
+ markAllShapesDirty();
+ return sampleCount[0];
+ }
+
+ public void setAllShapesQuality(final int q) {
+ for(int i=0; i<shapes.size(); i++) {
+ final Shape shape = shapes.get(i);
+ if( shape instanceof GraphShape ) {
+ ((GraphShape)shape).setQuality(q);
+ }
+ }
+ }
+ public void setAllShapesSharpness(final float sharpness) {
+ for(int i=0; i<shapes.size(); i++) {
+ final Shape shape = shapes.get(i);
+ if( shape instanceof GraphShape ) {
+ ((GraphShape)shape).setSharpness(sharpness);
+ }
+ }
+ }
+ public void markAllShapesDirty() {
+ for(int i=0; i<shapes.size(); i++) {
+ shapes.get(i).markShapeDirty();
+ }
+ }
+
+ /**
+ * Sets the {@link #getBounds()} fractional thickness of the debug box ranging [0..1] for all shapes, zero for no debug box (default).
+ * @param v fractional thickness of {@link #getBounds()} ranging [0..1], zero for no debug box
+ */
+ public final void setDebugBox(final float v) {
+ dbgbox_thickness = v;
+ for(int i=0; i<shapes.size(); i++) {
+ shapes.get(i).setDebugBox(v);
+ }
+ }
+
+ @Override
+ public void init(final GLAutoDrawable drawable) {
+ cDrawable = drawable;
+ renderer.init(drawable.getGL().getGL2ES2());
+ }
+
+ /**
+ * Reshape scene using {@link #setupMatrix(PMVMatrix, int, int, int, int)} using {@link PMVMatrixSetup}.
+ * <p>
+ * {@inheritDoc}
+ * </p>
+ * @see PMVMatrixSetup
+ * @see #setPMVMatrixSetup(PMVMatrixSetup)
+ * @see #setupMatrix(PMVMatrix, int, int, int, int)
+ * @see #getBounds()
+ * @see #getBoundsCenter()
+ */
+ @Override
+ public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) {
+ renderer.reshapeNotify(x, y, width, height);
+
+ setupMatrix(renderer.getMatrix(), x, y, width, height);
+ pmvMatrixSetup.setPlaneBox(planeBox, renderer.getMatrix(), x, y, width, height);
+ }
+
+ private static Comparator<Shape> shapeZAscComparator = new Comparator<Shape>() {
+ @Override
+ public int compare(final Shape s1, final Shape s2) {
+ final float s1Z = s1.getBounds().getMinZ()+s1.getPosition()[2];
+ final float s2Z = s2.getBounds().getMinZ()+s2.getPosition()[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 Object[] shapesS = shapes.toArray();
+ Arrays.sort(shapesS, (Comparator)shapeZAscComparator);
+
+ display(drawable, shapesS, false);
+ }
+
+ private static final int[] sampleCountGLSelect = { -1 };
+
+ private void display(final GLAutoDrawable drawable, final Object[] shapes, final boolean glSelect) {
+ final GL2ES2 gl = drawable.getGL().getGL2ES2();
+
+ final int[] sampleCount0;
+ if( glSelect ) {
+ gl.glClearColor(0f, 0f, 0f, 1f);
+ sampleCount0 = sampleCountGLSelect;
+ gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
+ } else {
+ if( null != clearColor ) {
+ gl.glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
+ gl.glClear(clearMask);
+ }
+ sampleCount0 = sampleCount;
+ }
+
+ final PMVMatrix pmv = renderer.getMatrix();
+ pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
+
+ if( glSelect ) {
+ renderer.enable(gl, true, RegionRenderer.defaultBlendDisable, RegionRenderer.defaultBlendDisable);
+ } else {
+ renderer.enable(gl, true);
+ }
+
+ //final int shapeCount = shapes.size();
+ final int shapeCount = shapes.length;
+ for(int i=0; i<shapeCount; i++) {
+ // final UIShape uiShape = shapes.get(i);
+ final Shape shape = (Shape)shapes[i];
+ // System.err.println("Id "+i+": "+uiShape);
+ if( shape.isEnabled() ) {
+ pmv.glPushMatrix();
+ shape.setTransform(pmv);
+
+ if( !doFrustumCulling || !pmv.glGetFrustum().isAABBoxOutside( shape.getBounds() ) ) {
+ if( glSelect ) {
+ final float color = ( i + 1f ) / ( shapeCount + 2f );
+ // FIXME
+ // System.err.printf("drawGL: color %f, index %d of [0..%d[%n", color, i, shapeCount);
+ renderer.getRenderState().setColorStatic(color, color, color, 1f);
+ shape.drawToSelect(gl, renderer, sampleCount0);
+ } else {
+ shape.draw(gl, renderer, sampleCount0);
+ }
+ }
+ pmv.glPopMatrix();
+ }
+ }
+ if( glSelect ) {
+ renderer.enable(gl, false, RegionRenderer.defaultBlendDisable, RegionRenderer.defaultBlendDisable);
+ } else {
+ renderer.enable(gl, false);
+ }
+ synchronized ( syncDisplayedOnce ) {
+ displayedOnce = true;
+ syncDisplayedOnce.notifyAll();
+ }
+ }
+
+ private volatile boolean displayedOnce = false;
+ private final Object syncDisplayedOnce = new Object();
+
+ /** Blocks until first {@link #display(GLAutoDrawable)} has completed after construction or {@link #dispose(GLAutoDrawable). */
+ public void waitUntilDisplayed() {
+ synchronized( syncDisplayedOnce ) {
+ while( !displayedOnce ) {
+ try {
+ syncDisplayedOnce.wait();
+ } catch (final InterruptedException e) { }
+ }
+ }
+ }
+
+ /**
+ * Disposes all {@link #addShape(Shape) added} {@link Shape}s.
+ * <p>
+ * Implementation also issues {@link RegionRenderer#destroy(GL2ES2)} if set
+ * and {@link #detachInputListenerFrom(GLWindow)} in case the drawable is of type {@link GLWindow}.
+ * </p>
+ * <p>
+ * {@inheritDoc}
+ * </p>
+ */
+ @Override
+ public void dispose(final GLAutoDrawable drawable) {
+ synchronized ( syncDisplayedOnce ) {
+ displayedOnce = false;
+ syncDisplayedOnce.notifyAll();
+ }
+ if( drawable instanceof GLWindow ) {
+ final GLWindow glw = (GLWindow) drawable;
+ detachInputListenerFrom(glw);
+ }
+ final GL2ES2 gl = drawable.getGL().getGL2ES2();
+ for(int i=0; i<shapes.size(); i++) {
+ shapes.get(i).destroy(gl, renderer);
+ }
+ shapes.clear();
+ cDrawable = null;
+ renderer.destroy(gl);
+ screenshot.dispose(gl);
+ }
+
+ /**
+ * Attempt to pick a {@link Shape} using the window coordinates and contained {@ling Shape}'s {@link AABBox} {@link Shape#getBounds() bounds}
+ * using a ray-intersection algorithm.
+ * <p>
+ * If {@link Shape} was found the given action is performed.
+ * </p>
+ * <p>
+ * Method performs on current thread and returns after probing every {@link Shape}.
+ * </p>
+ * @param glWinX window X coordinate, bottom-left origin
+ * @param glWinY window Y coordinate, bottom-left origin
+ * @param pmv a new {@link PMVMatrix} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) be setup},
+ * {@link Shape#setTransform(PMVMatrix) shape-transformed} and can be reused by the caller and runnable.
+ * @param objPos storage for found object position in model-space of found {@link Shape}
+ * @param shape storage for found {@link Shape} or null
+ * @param runnable the action to perform if {@link Shape} was found
+ */
+ public Shape pickShape(final int glWinX, final int glWinY, final PMVMatrix pmv, final float[] objPos, final Shape[] shape, final Runnable runnable) {
+ shape[0] = pickShapeImpl(glWinX, glWinY, pmv, objPos);
+ if( null != shape[0] ) {
+ runnable.run();
+ }
+ return shape[0];
+ }
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private Shape pickShapeImpl(final int glWinX, final int glWinY, final PMVMatrix pmv, 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
+ */
+ setupMatrix(pmv);
+
+ 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 Shape uiShape = (Shape)shapesS[i];
+
+ if( uiShape.isEnabled() ) {
+ pmv.glPushMatrix();
+ uiShape.setTransform(pmv);
+ final boolean ok = pmv.gluUnProjectRay(glWinX, glWinY, winZ0, winZ1, getViewport(), 0, ray);
+ 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;
+ }
+ }
+ pmv.glPopMatrix(); // we leave the stack open if picked above, allowing the modelview shape transform to be reused
+ }
+ }
+ return null;
+ }
+ private final float[] dpyTmp1V3 = new float[3];
+ private final float[] dpyTmp2V3 = new float[3];
+ private final float[] dpyTmp3V3 = new float[3];
+
+ /**
+ * Attempt to pick a {@link Shape} using the OpenGL false color rendering.
+ * <p>
+ * If {@link Shape} was found the given action is performed on the rendering thread.
+ * </p>
+ * <p>
+ * Method is non blocking and performs on rendering-thread, it returns immediately.
+ * </p>
+ * @param glWinX window X coordinate, bottom-left origin
+ * @param glWinY window Y coordinate, bottom-left origin
+ * @param objPos storage for found object position in model-space of found {@link Shape}
+ * @param shape storage for found {@link Shape} or null
+ * @param runnable the action to perform if {@link Shape} was found
+ */
+ public void pickShapeGL(final int glWinX, final int glWinY, final float[] objPos, final Shape[] shape, final Runnable runnable) {
+ if( null == cDrawable ) {
+ return;
+ }
+ cDrawable.invoke(false, new GLRunnable() {
+ @Override
+ public boolean run(final GLAutoDrawable drawable) {
+ final Shape s = pickShapeGLImpl(drawable, glWinX, glWinY);
+ shape[0] = s;
+ if( null != s ) {
+ final PMVMatrix pmv = renderer.getMatrix();
+ pmv.glPushMatrix();
+ s.setTransform(pmv);
+ final boolean ok = null != shape[0].winToShapeCoord(getMatrix(), getViewport(), glWinX, glWinY, objPos);
+ pmv.glPopMatrix();
+ if( ok ) {
+ runnable.run();
+ }
+ }
+ return false; // needs to re-render to wash away our false-color glSelect
+ } } );
+ }
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private Shape pickShapeGLImpl(final GLAutoDrawable drawable, final int glWinX, final int glWinY) {
+ final Object[] shapesS = shapes.toArray();
+ Arrays.sort(shapesS, (Comparator)shapeZAscComparator);
+
+ final GLPixelStorageModes psm = new GLPixelStorageModes();
+ final ByteBuffer pixel = Buffers.newDirectByteBuffer(4);
+
+ final GL2ES2 gl = drawable.getGL().getGL2ES2();
+
+ display(drawable, shapesS, true);
+
+ psm.setPackAlignment(gl, 4);
+ // psm.setUnpackAlignment(gl, 4);
+ try {
+ // gl.glReadPixels(glWinX, getHeight() - glWinY, 1, 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pixel);
+ gl.glReadPixels(glWinX, glWinY, 1, 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pixel);
+ } catch(final GLException gle) {
+ gle.printStackTrace();
+ return null;
+ }
+ psm.restore(gl);
+
+ // final float color = ( i + 1f ) / ( shapeCount + 2f );
+ final int shapeCount = shapes.size();
+ final int qp = pixel.get(0) & 0xFF;
+ final float color = qp / 255.0f;
+ final int index = Math.round( ( color * ( shapeCount + 2f) ) - 1f );
+
+ // FIXME drawGL: color 0.333333, index 0 of [0..1[
+ System.err.printf("pickGL: glWin %d / %d, byte %d, color %f, index %d of [0..%d[%n",
+ glWinX, glWinY, qp, color, index, shapeCount);
+
+ if( 0 <= index && index < shapeCount ) {
+ return (Shape)shapesS[index];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Calling {@link Shape#winToObjCoord(Scene, int, int, float[])}, retrieving its Shape object position.
+ * @param shape
+ * @param glWinX in GL window coordinates, origin bottom-left
+ * @param glWinY in GL window coordinates, origin bottom-left
+ * @param pmv a new {@link PMVMatrix} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) be setup},
+ * {@link Shape#setTransform(PMVMatrix) shape-transformed} and can be reused by the caller and runnable.
+ * @param objPos resulting object position
+ * @param runnable action
+ */
+ public void winToShapeCoord(final Shape shape, final int glWinX, final int glWinY, final PMVMatrix pmv, final float[] objPos, final Runnable runnable) {
+ if( null != shape && null != shape.winToShapeCoord(pmvMatrixSetup, renderer.getViewport(), glWinX, glWinY, pmv, objPos) ) {
+ runnable.run();
+ }
+ }
+
+ /**
+ * Interface providing {@link #set(PMVMatrix, int, int, int, int) a method} to
+ * setup {@link PMVMatrix}'s {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW}.
+ * <p>
+ * At the end of operations, the {@link GLMatrixFunc#GL_MODELVIEW} matrix has to be selected.
+ * </p>
+ * <p>
+ * Implementation is being called by {@link Scene#setupMatrix(PMVMatrix, int, int, int, int)}
+ * and hence {@link Scene#reshape(GLAutoDrawable, int, int, int, int)}.
+ * </p>
+ * <p>
+ * Custom implementations can be set via {@link Scene#setPMVMatrixSetup(PMVMatrixSetup)}.
+ * </p>
+ * <p>
+ * The default implementation is described below:
+ * <ul>
+ * <li>{@link GLMatrixFunc#GL_PROJECTION} Matrix
+ * <ul>
+ * <li>Identity</li>
+ * <li>Perspective {@link Scene#DEFAULT_ANGLE} with {@link Scene#DEFAULT_ZNEAR} and {@link Scene#DEFAULT_ZFAR}</li>
+ * <li>Translated to given {@link Scene#DEFAULT_SCENE_DIST}</li>
+ * <li>Scale (back) to have normalized {@link Scene#getBounds() plane dimensions}, 1 for the greater of width and height.</li>
+ * </ul></li>
+ * <li>{@link GLMatrixFunc#GL_MODELVIEW} Matrix
+ * <ul>
+ * <li>identity</li>
+ * </ul></li>
+ * </ul>
+ * </p>
+ */
+ public static interface PMVMatrixSetup {
+ /**
+ * Setup {@link PMVMatrix}'s {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW}.
+ * <p>
+ * See {@link PMVMatrixSetup} for details.
+ * </p>
+ * <p>
+ * At the end of operations, the {@link GLMatrixFunc#GL_MODELVIEW} matrix is selected.
+ * </p>
+ * @param pmv the {@link PMVMatrix} to setup
+ * @param x lower left corner of the viewport rectangle
+ * @param y lower left corner of the viewport rectangle
+ * @param width width of the viewport rectangle
+ * @param height height of the viewport rectangle
+ */
+ void set(PMVMatrix pmv, final int x, final int y, final int width, final int height);
+
+ /**
+ * Optional method to set the {@link Scene#getBounds()} {@link AABBox}, maybe a {@code nop} if not desired.
+ * <p>
+ * Will be called by {@link Scene#reshape(GLAutoDrawable, int, int, int, int)} after {@link #set(PMVMatrix, int, int, int, int)}.
+ * </p>
+ * @param planeBox the {@link AABBox} to define
+ * @param pmv the {@link PMVMatrix}, already setup via {@link #set(PMVMatrix, int, int, int, int)}.
+ * @param x lower left corner of the viewport rectangle
+ * @param y lower left corner of the viewport rectangle
+ * @param width width of the viewport rectangle
+ * @param height height of the viewport rectangle
+ */
+ void setPlaneBox(final AABBox planeBox, final PMVMatrix pmv, int x, int y, final int width, final int height);
+ }
+
+ /** Return the default or {@link #setPMVMatrixSetup(PMVMatrixSetup)} {@link PMVMatrixSetup}. */
+ public final PMVMatrixSetup getPMVMatrixSetup() { return pmvMatrixSetup; }
+
+ /** Set a custom {@link PMVMatrixSetup}. */
+ public final void setPMVMatrixSetup(final PMVMatrixSetup setup) { pmvMatrixSetup = setup; }
+
+ /** Return the default {@link PMVMatrixSetup}. */
+ public static PMVMatrixSetup getDefaultPMVMatrixSetup() { return defaultPMVMatrixSetup; }
+
+ /**
+ * Setup {@link PMVMatrix} {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW}
+ * by calling {@link #getPMVMatrixSetup()}'s {@link PMVMatrixSetup#set(PMVMatrix, int, int, int, int)}.
+ * @param pmv the {@link PMVMatrix} to setup
+ * @param x lower left corner of the viewport rectangle
+ * @param y lower left corner of the viewport rectangle
+ * @param width width of the viewport rectangle
+ * @param height height of the viewport rectangle
+ */
+ public void setupMatrix(final PMVMatrix pmv, final int x, final int y, final int width, final int height) {
+ pmvMatrixSetup.set(pmv, x, y, width, height);
+ }
+
+ /**
+ * Setup {@link PMVMatrix} {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW}
+ * using implicit {@link #getViewport()} surface dimension by calling {@link #getPMVMatrixSetup()}'s {@link PMVMatrixSetup#set(PMVMatrix, int, int, int, int)}.
+ * @param pmv the {@link PMVMatrix} to setup
+ */
+ public void setupMatrix(final PMVMatrix pmv) {
+ final int[] viewport = renderer.getViewport();
+ setupMatrix(pmv, viewport[0], viewport[1], viewport[2], viewport[3]);
+ }
+
+ /** Copies the current int[4] viewport in given target and returns it for chaining. It is set after initial {@link #reshape(GLAutoDrawable, int, int, int, int)}. */
+ public final int[/*4*/] getViewport(final int[/*4*/] target) { return renderer.getViewport(target); }
+
+ /** Borrows the current int[4] viewport w/o copying. It is set after initial {@link #reshape(GLAutoDrawable, int, int, int, int)}. */
+ public int[/*4*/] getViewport() { return renderer.getViewport(); }
+
+ /** Returns the {@link #getViewport()}'s width, set after initial {@link #reshape(GLAutoDrawable, int, int, int, int)}. */
+ public int getWidth() { return renderer.getWidth(); }
+ /** Returns the {@link #getViewport()}'s height, set after initial {@link #reshape(GLAutoDrawable, int, int, int, int)}. */
+ public int getHeight() { return renderer.getHeight(); }
+
+ /** Borrow the current {@link PMVMatrix}. */
+ public PMVMatrix getMatrix() { return renderer.getMatrix(); }
+
+ /**
+ * Describing the scene's object model-dimensions of the plane at scene-distance covering the visible viewport rectangle.
+ * <p>
+ * The value is evaluated at {@link #reshape(GLAutoDrawable, int, int, int, int)} via {@link }
+ * </p>
+ * <p>
+ * {@link AABBox#getWidth()} and {@link AABBox#getHeight()} define scene's dimension covered by surface size.
+ * </p>
+ * <p>
+ * {@link AABBox} is setup via {@link #getPMVMatrixSetup()}'s {@link PMVMatrixSetup#setPlaneBox(AABBox, PMVMatrix, int, int, int, int)}.
+ * </p>
+ * <p>
+ * The default {@link PMVMatrixSetup} implementation scales to normalized plane dimensions, 1 for the greater of width and height.
+ * </p>
+ */
+ public AABBox getBounds() { return planeBox; }
+
+ /**
+ *
+ * @param pmv
+ * @param viewport
+ * @param zNear
+ * @param zFar
+ * @param winX
+ * @param winY
+ * @param objOrthoZ
+ * @param objPos float[3] storage for object coord result
+ * @param winZ
+ */
+ public static void winToPlaneCoord(final PMVMatrix pmv, final int[] viewport,
+ final float zNear, final float zFar,
+ final float winX, final float winY, final float objOrthoZ,
+ final float[] objPos) {
+ final float winZ = FloatUtil.getOrthoWinZ(objOrthoZ, zNear, zFar);
+ pmv.gluUnProject(winX, winY, winZ, viewport, 0, objPos, 0);
+ }
+
+ /**
+ * Map given window surface-size to object coordinates relative to this scene using
+ * the give projection parameters.
+ * @param viewport viewport rectangle
+ * @param zNear custom {@link #DEFAULT_ZNEAR}
+ * @param zFar custom {@link #DEFAULT_ZFAR}
+ * @param objOrthoDist custom {@link #DEFAULT_SCENE_DIST}
+ * @param objSceneSize float[2] storage for object surface size result
+ */
+ public void surfaceToPlaneSize(final int[] viewport, final float zNear, final float zFar, final float objOrthoDist, final float[/*2*/] objSceneSize) {
+ final PMVMatrix pmv = new PMVMatrix();
+ setupMatrix(pmv, viewport[0], viewport[1], viewport[2], viewport[3]);
+ {
+ final float[] obj00Coord = new float[3];
+ final float[] obj11Coord = new float[3];
+
+ winToPlaneCoord(pmv, viewport, DEFAULT_ZNEAR, DEFAULT_ZFAR, viewport[0], viewport[1], objOrthoDist, obj00Coord);
+ winToPlaneCoord(pmv, viewport, DEFAULT_ZNEAR, DEFAULT_ZFAR, viewport[2], viewport[3], objOrthoDist, obj11Coord);
+ objSceneSize[0] = obj11Coord[0] - obj00Coord[0];
+ objSceneSize[1] = obj11Coord[1] - obj00Coord[1];
+ }
+ }
+
+ /**
+ * Map given window surface-size to object coordinates relative to this scene using
+ * the default {@link PMVMatrixSetup}, i.e. {@link #DEFAULT_ZNEAR}, {@link #DEFAULT_ZFAR} and {@link #DEFAULT_SCENE_DIST}
+ * @param viewport viewport rectangle
+ * @param objSceneSize float[2] storage for object surface size result
+ */
+ public void surfaceToPlaneSize(final int[] viewport, final float[/*2*/] objSceneSize) {
+ surfaceToPlaneSize(viewport, DEFAULT_ZNEAR, DEFAULT_ZFAR, -DEFAULT_SCENE_DIST, objSceneSize);
+ }
+
+ public final Shape getActiveShape() {
+ return activeShape;
+ }
+
+ public void releaseActiveShape() {
+ activeShape = null;
+ }
+ private void setActiveShape(final Shape 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 = getHeight() - e.getY() - 1;
+ final PMVMatrix pmv = new PMVMatrix();
+ final float[] objPos = new float[3];
+ final Shape shape = activeShape;
+ winToShapeCoord(shape, glWinX, glWinY, pmv, objPos, () -> {
+ shape.dispatchGestureEvent(gh, glWinX, glWinY, pmv, renderer.getViewport(), objPos);
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * Dispatch mouse event, either directly sending to activeShape or picking one
+ * @param e original Newt {@link MouseEvent}
+ * @param glWinX in GL window coordinates, origin bottom-left
+ * @param glWinY in GL window coordinates, origin bottom-left
+ */
+ final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY) {
+ if( null == activeShape ) {
+ dispatchMouseEventPickShape(e, glWinX, glWinY);
+ } else {
+ dispatchMouseEventForShape(activeShape, e, glWinX, glWinY);
+ }
+ }
+ /**
+ * Pick the shape using the event coordinates
+ * @param e original Newt {@link MouseEvent}
+ * @param glWinX in GL window coordinates, origin bottom-left
+ * @param glWinY in GL window coordinates, origin bottom-left
+ */
+ final void dispatchMouseEventPickShape(final MouseEvent e, final int glWinX, final int glWinY) {
+ final PMVMatrix pmv = new PMVMatrix();
+ final float[] objPos = new float[3];
+ final Shape[] shape = { null };
+ if( null == pickShape(glWinX, glWinY, pmv, objPos, shape, () -> {
+ setActiveShape(shape[0]);
+ shape[0].dispatchMouseEvent(e, glWinX, glWinY, objPos);
+ } ) )
+ {
+ releaseActiveShape();
+ }
+ }
+ /**
+ * Dispatch event to shape
+ * @param shape target active shape of event
+ * @param e original Newt {@link MouseEvent}
+ * @param glWinX in GL window coordinates, origin bottom-left
+ * @param glWinY in GL window coordinates, origin bottom-left
+ */
+ final void dispatchMouseEventForShape(final Shape shape, final MouseEvent e, final int glWinX, final int glWinY) {
+ final PMVMatrix pmv = new PMVMatrix();
+ final float[] objPos = new float[3];
+ winToShapeCoord(shape, glWinX, glWinY, pmv, objPos, () -> { 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, origin bottom-left
+ final int glWinX = e.getX();
+ final int glWinY = getHeight() - e.getY() - 1;
+ dispatchMouseEvent(e, glWinX, glWinY);
+ }
+
+ @Override
+ public void mouseReleased(final MouseEvent e) {
+ // flip to GL window coordinates, origin bottom-left
+ final int glWinX = e.getX();
+ final int glWinY = getHeight() - e.getY() - 1;
+ dispatchMouseEvent(e, glWinX, glWinY);
+ if( 1 == e.getPointerCount() ) {
+ // Release active shape: last pointer has been lifted!
+ releaseActiveShape();
+ clear();
+ }
+ }
+
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ // flip to GL window coordinates
+ final int glWinX = e.getX();
+ final int glWinY = getHeight() - e.getY() - 1;
+ // activeId should have been released by mouseRelease() already!
+ dispatchMouseEventPickShape(e, glWinX, glWinY);
+ // Release active shape: last pointer has been lifted!
+ releaseActiveShape();
+ 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, origin bottom-left
+ final int glWinX = lx;
+ final int glWinY = getHeight() - 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 = getHeight() - ly - 1;
+ dispatchMouseEvent(e, glWinX, glWinY);
+ }
+
+ @Override
+ public void mouseMoved(final MouseEvent e) {
+ if( -1 == lId || e.getPointerId(0) == lId ) {
+ lx = e.getX();
+ ly = e.getY();
+ lId = e.getPointerId(0);
+ }
+ final int glWinX = lx;
+ final int glWinY = getHeight() - ly - 1;
+ // dispatchMouseEvent(e, glWinX, glWinY);
+ dispatchMouseEventPickShape(e, glWinX, glWinY);
+ }
+ @Override
+ public void mouseEntered(final MouseEvent e) { }
+ @Override
+ public void mouseExited(final MouseEvent e) {
+ releaseActiveShape();
+ clear();
+ }
+ }
+
+ /**
+ * Return a formatted status string containing avg fps and avg frame duration.
+ * @param glad GLAutoDrawable instance for FPSCounter, its chosen GLCapabilities and its GL's swap-interval
+ * @param renderModes render modes for {@link Region#getRenderModeString(int)}
+ * @param quality the Graph-Curve quality setting or -1 to be ignored
+ * @param dpi the monitor's DPI (vertical preferred)
+ * @return formatted status string
+ */
+ public String getStatusText(final GLAutoDrawable glad, final int renderModes, final int quality, final float dpi) {
+ final FPSCounter fpsCounter = glad.getAnimator();
+ final float lfps, tfps, td;
+ if( null != fpsCounter ) {
+ lfps = fpsCounter.getLastFPS();
+ tfps = fpsCounter.getTotalFPS();
+ td = (float)fpsCounter.getLastFPSPeriod() / (float)fpsCounter.getUpdateFPSFrames();
+ } else {
+ lfps = 0f;
+ tfps = 0f;
+ td = 0f;
+ }
+ final String modeS = Region.getRenderModeString(renderModes);
+ final GLCapabilitiesImmutable caps = glad.getChosenGLCapabilities();
+ final String sampleCountStr1, sampleCountStr2, qualityStr, blendStr;
+ if( Region.isVBAA(renderModes) || Region.isMSAA(renderModes) ) {
+ sampleCountStr1 = "-samples "+getSampleCount();
+ } else {
+ sampleCountStr1 = "";
+ }
+ if( caps.getNumSamples() > 0 ) {
+ sampleCountStr2 = ", smsaa "+caps.getNumSamples();
+ } else {
+ sampleCountStr2 = "";
+ }
+ if( 0 <= quality ) {
+ qualityStr = ", q "+quality;
+ } else {
+ qualityStr = "";
+ }
+ if( getRenderState().isHintMaskSet(RenderState.BITHINT_BLENDING_ENABLED) ) {
+ blendStr = ", blend";
+ } else {
+ blendStr = "";
+ }
+ return String.format("%03.1f/%03.1f fps, %.1f ms/f, vsync %d, dpi %.1f, %s%s%s%s%s, a %d",
+ lfps, tfps, td, glad.getGL().getSwapInterval(), dpi, modeS, sampleCountStr1, sampleCountStr2,
+ qualityStr, blendStr, caps.getAlphaBits());
+ }
+
+ /**
+ * Return a formatted status string containing avg fps and avg frame duration.
+ * @param fpsCounter the counter, must not be null
+ * @return formatted status string
+ */
+ public static String getStatusText(final FPSCounter fpsCounter) {
+ final float lfps = fpsCounter.getLastFPS();
+ final float tfps = fpsCounter.getTotalFPS();
+ final float td = (float)fpsCounter.getLastFPSPeriod() / (float)fpsCounter.getUpdateFPSFrames();
+ return String.format("%03.1f/%03.1f fps, %.1f ms/f", lfps, tfps, td);
+ }
+
+ /**
+ * Write current read drawable (screen) to a PNG file.
+ * @see #getScreenshotCount()
+ */
+ public void screenshot(final GL gl, final int renderModes, final String prefix) {
+ final RegionRenderer renderer = getRenderer();
+ final String modeS = Region.getRenderModeString(renderModes);
+ final String filename = String.format((Locale)null, "%s-shot%03d-%03dx%03d-S_%s_%02d.png",
+ prefix, shotCount++, renderer.getWidth(), renderer.getHeight(),
+ modeS, getSampleCount());
+ gl.glFinish(); // just make sure rendering finished ..
+ if(screenshot.readPixels(gl, false)) {
+ screenshot.write(new File(filename));
+ System.err.println("Wrote: "+filename);
+ }
+ }
+ private int shotCount = 0;
+
+ /** Return the number of {@link #screenshot(GL, int, String)}s being taken. */
+ public int getScreenshotCount() { return shotCount; }
+
+ private static final PMVMatrixSetup defaultPMVMatrixSetup = new PMVMatrixSetup() {
+ @Override
+ public void set(final PMVMatrix pmv, final int x, final int y, final int width, final int height) {
+ final float ratio = (float)width/(float)height;
+ pmv.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
+ pmv.glLoadIdentity();
+ pmv.gluPerspective(DEFAULT_ANGLE, ratio, DEFAULT_ZNEAR, DEFAULT_ZFAR);
+ pmv.glTranslatef(0f, 0f, DEFAULT_SCENE_DIST);
+
+ pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
+ pmv.glLoadIdentity();
+
+ // Scale (back) to have normalized plane dimensions, 1 for the greater of width and height.
+ final AABBox planeBox0 = new AABBox();
+ setPlaneBox(planeBox0, pmv, x, y, width, height);
+ final float sx = planeBox0.getWidth();
+ final float sy = planeBox0.getHeight();
+ final float sxy = sx > sy ? sx : sy;
+ pmv.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
+ pmv.glScalef(sxy, sxy, 1f);
+ pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
+ }
+
+ @Override
+ public void setPlaneBox(final AABBox planeBox, final PMVMatrix pmv, final int x, final int y, final int width, final int height) {
+ final float orthoDist = -DEFAULT_SCENE_DIST;
+ final float[] obj00Coord = new float[3];
+ final float[] obj11Coord = new float[3];
+ final int[] viewport = { x, y, width, height };
+
+ winToPlaneCoord(pmv, viewport, DEFAULT_ZNEAR, DEFAULT_ZFAR, x, y, orthoDist, obj00Coord);
+ winToPlaneCoord(pmv, viewport, DEFAULT_ZNEAR, DEFAULT_ZFAR, width, height, orthoDist, obj11Coord);
+
+ planeBox.setSize( obj00Coord[0], // lx
+ obj00Coord[1], // ly
+ obj00Coord[2], // lz
+ obj11Coord[0], // hx
+ obj11Coord[1], // hy
+ obj11Coord[2] );// hz }
+ }
+ };
+ private PMVMatrixSetup pmvMatrixSetup = defaultPMVMatrixSetup;
+}