From a81fff28e9380ebce877d974d402ef613b5ea850 Mon Sep 17 00:00:00 2001
From: Sven Gothel <sgothel@jausoft.com>
Date: Sun, 19 Mar 2023 08:04:48 +0100
Subject: Add Scene.PMVMatrixSetup for programmable PMVMatrix setup in Scene
 replacing fixed projection params; Simplify API requirements ..

Scene:
- Add Scene.PMVMatrixSetup and use it throughout Scene/Shape for reshape(), setupMatrix()
  and Shape's  win<->obj coordinate mappings.

- Default Scene.PMVMatrixSetup is no more moving origin to bottom-left, kept at screen-center.

- Add waitUntilDisplayer(), allowing to wait until 1st display is completed,
  ensuring certain states are well set (init, reshape and 1st display on Shapes, plane-box, ..)

Shape:
- Rename setPosition() -> moveTo()

- Add getBounds(GLProfile) to retrieve AABBox before rendering,
  - Enhance validateImpl(..) to allow this mode @ initial validation (only)
---
 .../classes/com/jogamp/graph/ui/gl/Scene.java      | 457 ++++++++++-----------
 .../classes/com/jogamp/graph/ui/gl/Shape.java      | 124 +++---
 2 files changed, 301 insertions(+), 280 deletions(-)

(limited to 'src/graphui/classes/com/jogamp/graph/ui/gl')

diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java b/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java
index 9c9334182..7e98df635 100644
--- a/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java
+++ b/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java
@@ -27,6 +27,7 @@
  */
 package com.jogamp.graph.ui.gl;
 
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -37,8 +38,10 @@ 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;
@@ -75,6 +78,9 @@ import com.jogamp.opengl.util.PMVMatrix;
  * - {@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 {
@@ -87,12 +93,11 @@ public final class Scene implements GLEventListener {
     /** 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 final float sceneDist, zNear, zFar;
-    private final float projAngle = DEFAULT_ANGLE;
     private float[] clearColor = null;
     private int clearMask;
 
@@ -100,10 +105,8 @@ public final class Scene implements GLEventListener {
 
     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, pre {@link #translate(PMVMatrix)} to origin. */
-    private final AABBox planeBoxCtr = new AABBox(0f, 0f, 0f, 0f, 0f, 0f);
     /** 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 planBoxBL = new AABBox(0f, 0f, 0f, 0f, 0f, 0f);
+    private final AABBox planeBox = new AABBox(0f, 0f, 0f, 0f, 0f, 0f);
 
     private volatile Shape activeShape = null;
 
@@ -119,60 +122,24 @@ public final class Scene implements GLEventListener {
 
     /**
      * Create a new scene with an internally created RegionRenderer
-     * and using default values {@link #DEFAULT_SCENE_DIST}, {@link #DEFAULT_ZNEAR} and {@link #DEFAULT_ZFAR}.
+     * and using default values {@link #DEFAULT_SCENE_DIST}, {@link #DEFAULT_ANGLE}, {@link #DEFAULT_ZNEAR} and {@link #DEFAULT_ZFAR}.
      */
     public Scene() {
-        this(createRenderer(), DEFAULT_SCENE_DIST, DEFAULT_ZNEAR, DEFAULT_ZFAR);
-    }
-
-    /**
-     * Create a new scene with given projection values and an internally created RegionRenderer.
-     * @param sceneDist scene distance on z-axis to projection, consider using {@link #DEFAULT_SCENE_DIST}.
-     * @param zNear projection z-near value, consider using {@link #DEFAULT_ZNEAR}
-     * @param zFar projection z-far value, consider using {@link #DEFAULT_ZFAR}
-     */
-    public Scene(final float sceneDist, final float zNear, final float zFar) {
-        this(createRenderer(), sceneDist, zNear, zFar);
+        this(createRenderer());
     }
 
     /**
      * Create a new scene taking ownership of the given RegionRenderer
-     * and using default values {@link #DEFAULT_SCENE_DIST}, {@link #DEFAULT_ZNEAR} and {@link #DEFAULT_ZFAR}.
+     * and using default values {@link #DEFAULT_SCENE_DIST}, {@link #DEFAULT_ANGLE}, {@link #DEFAULT_ZNEAR} and {@link #DEFAULT_ZFAR}.
      */
     public Scene(final RegionRenderer renderer) {
-        this(renderer, DEFAULT_SCENE_DIST, DEFAULT_ZNEAR, DEFAULT_ZFAR);
-    }
-
-    /**
-     * Create a new scene with given projection values and taking ownership of the given RegionRenderer.
-     * @param renderer RegionRenderer to use and own
-     * @param sceneDist scene distance on z-axis to projection, consider using {@link #DEFAULT_SCENE_DIST}.
-     * @param zNear projection z-near value, consider using {@link #DEFAULT_ZNEAR}
-     * @param zFar projection z-far value, consider using {@link #DEFAULT_ZFAR}
-     */
-    public Scene(final RegionRenderer renderer, final float sceneDist, final float zNear, final float zFar) {
         if( null == renderer ) {
             throw new IllegalArgumentException("Null RegionRenderer");
         }
         this.renderer = renderer;
-        this.sceneDist = sceneDist;
-        this.zFar = zFar;
-        this.zNear = zNear;
         this.sampleCount[0] = 4;
     }
 
-    /** Return z-axis distance of scene to projection, see {@link #DEFAULT_SCENE_DIST}. */
-    public float getProjSceneDist() { return sceneDist; }
-
-    /** Return projection angle in degrees, see {@link #DEFAULT_ANGLE}. */
-    public float getProjAngle() { return projAngle; }
-
-    /** Return projection z-near value, see {@link #DEFAULT_ZNEAR}. */
-    public float getProjZNear() { return zNear; }
-
-    /** Return projection z-far value, see {@link #DEFAULT_ZFAR}. */
-    public float getProjZFar() { return zFar; }
-
     /** Returns the associated RegionRenderer */
     public RegionRenderer getRenderer() { return renderer; }
 
@@ -352,8 +319,24 @@ public final class Scene implements GLEventListener {
         } else {
             renderer.enable(gl, false);
         }
+        synchronized ( syncDisplayedOnce ) {
+            displayedOnce = true;
+            syncDisplayedOnce.notifyAll();
+        }
+    }
+
+    private volatile boolean displayedOnce = false;
+    private final Object syncDisplayedOnce = new Object();
 
-        renderer.enable(gl, false);
+    /** 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) { }
+            }
+        }
     }
 
     /**
@@ -367,18 +350,21 @@ public final class Scene implements GLEventListener {
      * </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 void pickShape(final int glWinX, final int glWinY, final float[] objPos, final Shape[] shape, final Runnable runnable) {
-        shape[0] = pickShapeImpl(glWinX, glWinY, objPos);
+    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 float[] objPos) {
+    private Shape pickShapeImpl(final int glWinX, final int glWinY, final PMVMatrix pmv, final float[] objPos) {
         final float winZ0 = 0f;
         final float winZ1 = 0.3f;
         /**
@@ -386,7 +372,6 @@ public final class Scene implements GLEventListener {
             gl.glReadPixels( x, y, 1, 1, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_FLOAT, winZRB);
             winZ1 = winZRB.get(0); // dir
         */
-        final PMVMatrix pmv = new PMVMatrix();
         setupMatrix(pmv);
 
         final Ray ray = new Ray();
@@ -401,7 +386,6 @@ public final class Scene implements GLEventListener {
                 pmv.glPushMatrix();
                 uiShape.setTransform(pmv);
                 final boolean ok = pmv.gluUnProjectRay(glWinX, glWinY, winZ0, winZ1, getViewport(), 0, ray);
-                pmv.glPopMatrix();
                 if( ok ) {
                     final AABBox sbox = uiShape.getBounds();
                     if( sbox.intersectsRay(ray) ) {
@@ -413,6 +397,7 @@ public final class Scene implements GLEventListener {
                         return uiShape;
                     }
                 }
+                pmv.glPopMatrix(); // we leave the stack open if picked above, allowing the modelview shape transform to be reused
             }
         }
         return null;
@@ -501,11 +486,13 @@ public final class Scene implements GLEventListener {
      * @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 winToObjCoord(final Shape shape, final int glWinX, final int glWinY, final float[] objPos, final Runnable runnable) {
-        if( null != shape && shape.winToObjCoord(this, glWinX, glWinY, objPos) ) {
+    public void winToObjCoord(final Shape shape, final int glWinX, final int glWinY, final PMVMatrix pmv, final float[] objPos, final Runnable runnable) {
+        if( null != shape && shape.winToObjCoord(pmvMatrixSetup, renderer.getViewport(), glWinX, glWinY, pmv, objPos) ) {
             runnable.run();
         }
     }
@@ -523,6 +510,10 @@ public final class Scene implements GLEventListener {
     @Override
     public void dispose(final GLAutoDrawable drawable) {
         System.err.println("SceneUIController: dispose");
+        synchronized ( syncDisplayedOnce ) {
+            displayedOnce = false;
+            syncDisplayedOnce.notifyAll();
+        }
         if( drawable instanceof GLWindow ) {
             final GLWindow glw = (GLWindow) drawable;
             detachInputListenerFrom(glw);
@@ -537,119 +528,102 @@ public final class Scene implements GLEventListener {
     }
 
     /**
-     *
-     * @param pmv
-     * @param view
-     * @param zNear
-     * @param zFar
-     * @param orthoX
-     * @param orthoY
-     * @param orthoDist
-     * @param winZ
-     * @param objPos float[3] storage for object coord result
+     * 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
+     * </p>
+     * <p>
+     * {@link GLMatrixFunc#GL_PROJECTION} is setup using perspective {@link Scene#DEFAULT_ANGLE} with {@link Scene#DEFAULT_ZNEAR} and {@link Scene#DEFAULT_ZFAR}.
+     * </p>
+     * <p>
+     * Further {@link GLMatrixFunc#GL_MODELVIEW} is translated to given {@link Scene#DEFAULT_SCENE_DIST}.
+     * </p>
      */
-    public static void winToObjCoord(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);
+    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 x TODO
+         * @param y TODO
+         */
+        void setPlaneBox(final AABBox planeBox, final PMVMatrix pmv, int x, int y, final int width, final int height);
     }
 
-    /**
-     * Map given window surface-size to object coordinates relative to this scene and {@link #getProjSceneDist() and projection settings.
-     * @param width surface width in pixel
-     * @param height surface height in pixel
-     * @param objSceneSize float[2] storage for object surface size result
-     */
-    public void surfaceToObjSize(final int width, final int height, final float[/*2*/] objSceneSize) {
-        final int[] viewport = { 0, 0, width, height };
+    /** Return the default or {@link #setPMVMatrixSetup(PMVMatrixSetup)} {@link PMVMatrixSetup}. */
+    public final PMVMatrixSetup getPMVMatrixSetup() { return pmvMatrixSetup; }
 
-        final PMVMatrix pmv = new PMVMatrix();
-        setupMatrix(pmv, width, height);
-        {
-            final float orthoDist = -sceneDist;
-            final float[] obj00Coord = new float[3];
-            final float[] obj11Coord = new float[3];
-            final float[] winZ = new float[1];
-
-            winToObjCoord(pmv, viewport, zNear, zFar, 0f, 0f, orthoDist, winZ, obj00Coord);
-            winToObjCoord(pmv, viewport, zNear, zFar, width, height, orthoDist, winZ, obj11Coord);
-            objSceneSize[0] = obj11Coord[0] - obj00Coord[0];
-            objSceneSize[1] = obj11Coord[1] - obj00Coord[1];
-        }
-    }
+    /** Set a custom {@link PMVMatrixSetup}. */
+    public final void setPMVMatrixSetup(final PMVMatrixSetup setup) { pmvMatrixSetup = setup; }
 
     /**
-     * Reshape scene {@link #setupMatrix(PMVMatrix, int, int)}.
-     * <p>
-     * {@link GLMatrixFunc#GL_PROJECTION} is setup using perspective {@link #getProjAngle()} with {@link #getProjZNear()} and {@link #getProjZFar()}.
-     * </p>
+     * Reshape scene using {@link #setupMatrix(PMVMatrix, int, int, int, int)} using {@link PMVMatrixSetup}.
      * <p>
-     * {@link GLMatrixFunc#GL_MODELVIEW} is translated to given {@link #getProjSceneDist()}
-     * and and origin 0/0 becomes the bottom-left corner.
+     * {@inheritDoc}
      * </p>
-     * @see #setupMatrix(PMVMatrix, int, int)
+     * @see PMVMatrixSetup
+     * @see #setPMVMatrixSetup(PMVMatrixSetup)
+     * @see #setupMatrix(PMVMatrix, int, int, int, int)
      * @see #getBounds()
      * @see #getBoundsCenter()
      */
-    @SuppressWarnings("unused")
     @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);
 
-        final PMVMatrix pmv = renderer.getMatrix();
-        setupMatrix0(pmv, width, height);
-        pmv.glTranslatef(0f, 0f, sceneDist);
-        {
-            final float orthoDist = -sceneDist;
-            final float[] obj00Coord = new float[3];
-            final float[] obj11Coord = new float[3];
-            final float[] winZ = new float[1];
-
-            winToObjCoord(pmv, getViewport(), zNear, zFar, 0f, 0f, orthoDist, winZ, obj00Coord);
-            winToObjCoord(pmv, getViewport(), zNear, zFar, width, height, orthoDist, winZ, obj11Coord);
-
-            pmv.glTranslatef(obj00Coord[0], obj00Coord[1], 0f); // bottom-left corder origin 0/0
-
-            planeBoxCtr.setSize( obj00Coord[0],  // lx
-                                 obj00Coord[1],  // ly
-                                 obj00Coord[2],  // lz
-                                 obj11Coord[0],  // hx
-                                 obj11Coord[1],  // hy
-                                 obj11Coord[2] );// hz
-
-            planBoxBL.setSize(   0,                      // lx
-                                 0,                      // ly
-                                 0,                      // lz
-                                 planeBoxCtr.getWidth(), // hx
-                                 planeBoxCtr.getHeight(),// hy
-                                 planeBoxCtr.getDepth());// hz
-
-            if( true || DEBUG ) {
-                System.err.printf("Reshape: zNear %f,  zFar %f, sceneDist %f%n", zNear, zFar, sceneDist);
-                System.err.printf("Reshape: Frustum: %s%n", pmv.glGetFrustum());
-                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]);
-                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]);
-                System.err.printf("Reshape: scenePlaneBox: %s%n", planeBoxCtr);
-            }
-        }
+        setupMatrix(renderer.getMatrix(), x, y, width, height);
+        pmvMatrixSetup.setPlaneBox(planeBox, renderer.getMatrix(), x, y, width, height);
+    }
 
-        if( false ) {
-            final float[] sceneScale = new float[3];
-            final float[] scenePlaneOrigin = new float[3];
-            scenePlaneOrigin[0] = planeBoxCtr.getMinX() * sceneDist;
-            scenePlaneOrigin[1] = planeBoxCtr.getMinY() * sceneDist;
-            scenePlaneOrigin[2] = planeBoxCtr.getMinZ() * sceneDist;
-            sceneScale[0] = ( planeBoxCtr.getWidth() * sceneDist ) / width;
-            sceneScale[1] = ( planeBoxCtr.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, planeBoxCtr.getWidth(), planeBoxCtr.getHeight(),
-                    width, height,
-                    sceneScale[0], sceneScale[1], sceneScale[2]);
-        }
+    /**
+     * 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)}. */
@@ -666,94 +640,79 @@ public final class Scene implements GLEventListener {
     /** Borrow the current {@link PMVMatrix}. */
     public PMVMatrix getMatrix() { return renderer.getMatrix(); }
 
-    /** Translate current matrix to {@link #getBounds()}'s origin (minx/miny) and {@link #getProjSceneDist()}, a convenience method. */
-    private void translate(final PMVMatrix pmv) {
-        pmv.glTranslatef(planeBoxCtr.getMinX(), planeBoxCtr.getMinY(), sceneDist);
-    }
-
     /**
-     * Setup {@link PMVMatrix} {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW}
-     * using explicit surface width and height before {@link #reshape(GLAutoDrawable, int, int, int, int)} happened, a convenience method.
+     * Describing the scene's object model-dimensions of the plane at scene-distance covering the visible viewport rectangle.
      * <p>
-     * {@link GLMatrixFunc#GL_PROJECTION} is setup using perspective {@link #getProjAngle()} with {@link #getProjZNear()} and {@link #getProjZFar()}.
+     * The value is evaluated at {@link #reshape(GLAutoDrawable, int, int, int, int)} via {@link }
      * </p>
      * <p>
-     * {@link GLMatrixFunc#GL_MODELVIEW} is translated to given {@link #getProjSceneDist()}
-     * and and origin 0/0 becomes the bottom-left corner.
+     * {@link AABBox#getWidth()} and {@link AABBox#getHeight()} define scene's dimension covered by surface size.
      * </p>
      * <p>
-     * At the end of operations, the {@link GLMatrixFunc#GL_MODELVIEW} matrix is selected.
+     * {@link AABBox} is setup via {@link #getPMVMatrixSetup()}'s {@link PMVMatrixSetup#setPlaneBox(AABBox, PMVMatrix, int, int, int, int)}.
      * </p>
-     * @param pmv the {@link PMVMatrix} to setup
-     * @param surface_width explicit surface width
-     * @param surface_height explicit surface height
      */
-    public void setupMatrix(final PMVMatrix pmv, final int surface_width, final int surface_height) {
-        setupMatrix0(pmv, surface_width, surface_height);
-        translate(pmv);
-    }
-    private void setupMatrix0(final PMVMatrix pmv, final int surface_width, final int surface_height) {
-        final float ratio = (float)surface_width/(float)surface_height;
-        pmv.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
-        pmv.glLoadIdentity();
-        pmv.gluPerspective(projAngle, ratio, zNear, zFar);
-
-        pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
-        pmv.glLoadIdentity();
-    }
+    public AABBox getBounds() { return planeBox; }
 
     /**
-     * Setup {@link PMVMatrix} {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW}
-     * using implicit {@link #getViewport()} surface dimension after {@link #reshape(GLAutoDrawable, int, int, int, int)} happened, a convenience method.
-     * <p>
-     * {@link GLMatrixFunc#GL_PROJECTION} is setup using perspective {@link #getProjAngle()} with {@link #getProjZNear()} and {@link #getProjZFar()}.
-     * </p>
-     * <p>
-     * {@link GLMatrixFunc#GL_MODELVIEW} is translated to given {@link #getProjSceneDist()}
-     * and and origin 0/0 becomes the bottom-left corner.
-     * </p>
-     * <p>
-     * At the end of operations, the {@link GLMatrixFunc#GL_MODELVIEW} matrix is selected.
-     * </p>
-     * @param pmv the {@link PMVMatrix} to setup
-     * @param surface_width explicit surface width
-     * @param surface_height explicit surface height
+     *
+     * @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 void setupMatrix(final PMVMatrix pmv) {
-        setupMatrix(pmv, getWidth(), getHeight());
+    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);
     }
 
     /**
-     * Describing the scene's object model-dimensions of the near-plane parallel at its scene-distance {@link #getProjSceneDist()}
-     * having the origin 0/0 on the bottom-left corner.
-     * <p>
-     * The value is evaluated at {@link #reshape(GLAutoDrawable, int, int, int, int)}.
-     * </p>
-     * <p>
-     * {@link AABBox#getWidth()} and {@link AABBox#getHeight()} define scene's dimension covered by surface size.
-     * </p>
+     * Map given window surface-size to object coordinates relative to this scene using
+     * the give projection parameters.
+     * @param viewport
+     * @param zNear
+     * @param zFar
+     * @param objOrthoDist
+     * @param objSceneSize float[2] storage for object surface size result
      */
-    public AABBox getBounds() { return planBoxBL; }
+    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];
+        }
+    }
 
     /**
-     * Describing the scene's object model-dimensions of the near-plane parallel at its scene-distance {@link #getProjSceneDist()}
-     * having the origin 0/0 in the center of the screen.
-     * <p>
-     * The value is evaluated at {@link #reshape(GLAutoDrawable, int, int, int, int)} before translating to the bottom-left origin 0/0,
-     * i.e. its minimum values are negative of half dimension.
-     * </p>
-     * <p>
-     * {@link AABBox#getWidth()} and {@link AABBox#getHeight()} define scene's dimension covered by surface size.
-     * </p>
+     * Map given window surface-size to object coordinates relative to this scene using
+     * the default {@link PMVMatrixSetup}.
+     * @param viewport
+     * @param objSceneSize float[2] storage for object surface size result
      */
-    public AABBox getBoundsCenter() { return planeBoxCtr; }
+    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 release() {
-        setActiveShape(null);
+    public void releaseActiveShape() {
+        activeShape = null;
     }
     private void setActiveShape(final Shape shape) {
         activeShape = shape;
@@ -770,10 +729,11 @@ public final class Scene implements GLEventListener {
                     // 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;
-                    winToObjCoord(shape, glWinX, glWinY, objPos, () -> {
-                        shape.dispatchGestureEvent(Scene.this, gh, glWinX, glWinY, objPos);
+                    winToObjCoord(shape, glWinX, glWinY, pmv, objPos, () -> {
+                        shape.dispatchGestureEvent(gh, glWinX, glWinY, pmv, renderer.getViewport(), objPos);
                     });
                 }
             }
@@ -788,7 +748,7 @@ public final class Scene implements GLEventListener {
      */
     final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY) {
         if( null == activeShape ) {
-            dispatchMouseEventPickShape(e, glWinX, glWinY, true);
+            dispatchMouseEventPickShape(e, glWinX, glWinY);
         } else {
             dispatchMouseEventForShape(activeShape, e, glWinX, glWinY);
         }
@@ -798,17 +758,18 @@ public final class Scene implements GLEventListener {
      * @param e original Newt {@link MouseEvent}
      * @param glWinX in GL window coordinates, origin bottom-left
      * @param glWinY in GL window coordinates, origin bottom-left
-     * @param setActive
      */
-    final void dispatchMouseEventPickShape(final MouseEvent e, final int glWinX, final int glWinY, final boolean setActive) {
+    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 };
-        pickShape(glWinX, glWinY, objPos, shape, () -> {
-               if( setActive ) {
-                   setActiveShape(shape[0]);
-               }
+        if( null == pickShape(glWinX, glWinY, pmv, objPos, shape, () -> {
+               setActiveShape(shape[0]);
                shape[0].dispatchMouseEvent(e, glWinX, glWinY, objPos);
-           } );
+           } ) )
+        {
+           releaseActiveShape();
+        }
     }
     /**
      * Dispatch event to shape
@@ -818,8 +779,9 @@ public final class Scene implements GLEventListener {
      * @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];
-        winToObjCoord(shape, glWinX, glWinY, objPos, () -> { shape.dispatchMouseEvent(e, glWinX, glWinY, objPos); });
+        winToObjCoord(shape, glWinX, glWinY, pmv, objPos, () -> { shape.dispatchMouseEvent(e, glWinX, glWinY, objPos); });
     }
 
     private class SBCMouseListener implements MouseListener {
@@ -850,7 +812,7 @@ public final class Scene implements GLEventListener {
             dispatchMouseEvent(e, glWinX, glWinY);
             if( 1 == e.getPointerCount() ) {
                 // Release active shape: last pointer has been lifted!
-                release();
+                releaseActiveShape();
                 clear();
             }
         }
@@ -861,9 +823,9 @@ public final class Scene implements GLEventListener {
             final int glWinX = e.getX();
             final int glWinY = getHeight() - e.getY() - 1;
             // activeId should have been released by mouseRelease() already!
-            dispatchMouseEventPickShape(e, glWinX, glWinY, false);
+            dispatchMouseEventPickShape(e, glWinX, glWinY);
             // Release active shape: last pointer has been lifted!
-            release();
+            releaseActiveShape();
             clear();
         }
 
@@ -887,7 +849,7 @@ public final class Scene implements GLEventListener {
             // flip to GL window coordinates
             final int glWinX = lx;
             final int glWinY = getHeight() - ly - 1;
-            dispatchMouseEventPickShape(e, glWinX, glWinY, true);
+            dispatchMouseEvent(e, glWinX, glWinY);
         }
 
         @Override
@@ -897,12 +859,16 @@ public final class Scene implements GLEventListener {
                 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) {
-            release();
+            releaseActiveShape();
             clear();
         }
     }
@@ -948,4 +914,35 @@ public final class Scene implements GLEventListener {
             return String.format("%03.1f/%03.1f fps, %.1f ms/f", lfps, tfps, td);
     }
 
+    private 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();
+        }
+
+        @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];
+
+                winToPlaneCoord(pmv, getViewport(), DEFAULT_ZNEAR, DEFAULT_ZFAR, x, y, orthoDist, obj00Coord);
+                winToPlaneCoord(pmv, getViewport(), 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;
 }
diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java b/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java
index 140ac8cc8..56ba928d7 100644
--- a/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java
+++ b/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java
@@ -37,7 +37,6 @@ import com.jogamp.graph.curve.OutlineShape;
 import com.jogamp.graph.curve.Region;
 import com.jogamp.graph.curve.opengl.GLRegion;
 import com.jogamp.graph.curve.opengl.RegionRenderer;
-import com.jogamp.graph.font.Font;
 import com.jogamp.graph.geom.Vertex;
 import com.jogamp.graph.geom.Vertex.Factory;
 import com.jogamp.graph.geom.plane.AffineTransform;
@@ -47,7 +46,6 @@ import com.jogamp.newt.event.MouseAdapter;
 import com.jogamp.newt.event.NEWTEvent;
 import com.jogamp.newt.event.PinchToZoomGesture;
 import com.jogamp.newt.event.MouseEvent;
-import com.jogamp.newt.event.MouseEvent.PointerClass;
 import com.jogamp.newt.event.MouseListener;
 import com.jogamp.opengl.math.FloatUtil;
 import com.jogamp.opengl.math.Quaternion;
@@ -77,7 +75,7 @@ public abstract class Shape {
     protected static final int DIRTY_SHAPE    = 1 << 0 ;
     protected static final int DIRTY_STATE    = 1 << 1 ;
 
-    private final Factory<? extends Vertex> vertexFactory;
+    protected final Factory<? extends Vertex> vertexFactory;
     private final int renderModes;
     protected final AABBox box;
 
@@ -176,13 +174,13 @@ public abstract class Shape {
         markShapeDirty();
     }
 
-    public void setPosition(final float tx, final float ty, final float tz) {
+    public final void moveTo(final float tx, final float ty, final float tz) {
         position[0] = tx;
         position[1] = ty;
         position[2] = tz;
         // System.err.println("UIShape.setTranslate: "+tx+"/"+ty+"/"+tz+": "+toString());
     }
-    public void move(final float tx, final float ty, final float tz) {
+    public final void move(final float tx, final float ty, final float tz) {
         position[0] += tx;
         position[1] += ty;
         position[2] += tz;
@@ -249,10 +247,27 @@ public abstract class Shape {
      */
     public final AABBox getBounds() { return box; }
 
+    /**
+     * Returns the unscaled bounding {@link AABBox} for this shape.
+     *
+     * This variant differs from {@link #getBounds()} as it
+     * returns a valid {@link AABBox} even before {@link #draw(GL2ES2, RegionRenderer, int[]) draw(..)}
+     * and having an OpenGL instance available.
+     *
+     * @see #getBounds()
+     */
+    public final AABBox getBounds(final GLProfile glp) {
+        if( null == region ) {
+            // initial creation of region, producing a valid box
+            validateImpl(glp, null);
+        }
+        return box;
+    }
+
     public final int getRenderModes() { return renderModes; }
 
-    public GLRegion getRegion(final GL2ES2 gl, final RegionRenderer renderer) {
-        validate(gl, renderer);
+    public GLRegion getRegion(final GL2ES2 gl) {
+        validate(gl);
         return region;
     }
 
@@ -270,7 +285,7 @@ public abstract class Shape {
      * @param renderer the used {@link RegionRenderer}, also source of {@link RegionRenderer#getMatrix()} and {@link RegionRenderer#getViewport()}.
      * @param sampleCount sample count if used by Graph renderModes
      */
-    public void drawShape(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) {
+    public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) {
         final float r, g, b, a;
         final boolean isPressed = isPressed(), isToggleOn = isToggleOn();
         final boolean modBaseColor = !Region.hasColorChannel( renderModes ) && !Region.hasColorTexture( renderModes );
@@ -324,7 +339,7 @@ public abstract class Shape {
             }
         }
         renderer.getRenderState().setColorStatic(r, g, b, a);
-        getRegion(gl, renderer).draw(gl, renderer, sampleCount);
+        getRegion(gl).draw(gl, renderer, sampleCount);
     }
 
     protected GLRegion createGLRegion(final GLProfile glp) {
@@ -335,24 +350,21 @@ public abstract class Shape {
      * Validates the shape's underlying {@link GLRegion}.
      *
      * @param gl
-     * @param renderer
      */
-    public final void validate(final GL2ES2 gl, final RegionRenderer renderer) {
+    public final void validate(final GL2ES2 gl) {
+        validateImpl(gl.getGLProfile(), gl);
+    }
+    private final void validateImpl(final GLProfile glp, final GL2ES2 gl) {
         if( isShapeDirty() || null == region ) {
             box.reset();
             if( null == region ) {
-                region = createGLRegion(gl.getGLProfile());
-            } else {
-                region.clear(gl);
-            }
-            addShapeToRegion(gl, renderer);
-            if( DRAW_DEBUG_BOX ) {
+                region = createGLRegion(glp);
+            } else if( null != gl ) {
                 region.clear(gl);
-                final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory());
-                shape.setSharpness(shapesSharpness);
-                shape.setIsQuadraticNurbs();
-                region.addOutlineShape(shape, null, rgbaColor);
+            } else {
+                throw new IllegalArgumentException("GL is null on non-initial validation");
             }
+            addShapeToRegion();
             region.setQuality(regionQuality);
             dirty &= ~(DIRTY_SHAPE|DIRTY_STATE);
         } else if( isStateDirty() ) {
@@ -402,7 +414,7 @@ public abstract class Shape {
      * @param viewport the int[4] viewport
      * @param surfaceSize int[2] target surface size
      * @return true for successful gluProject(..) operation, otherwise false
-     * @see #getSurfaceSize(Scene, int[])
+     * @see #getSurfaceSize(com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup, int[], PMVMatrix, int[])
      */
     public boolean getSurfaceSize(final PMVMatrix pmv, final int[/*4*/] viewport, final int[/*2*/] surfaceSize) {
         boolean res = false;
@@ -426,22 +438,26 @@ public abstract class Shape {
     }
 
     /**
-     * Retrieve window surface size of this shape using a local {@link PMVMatrix}.
+     * Retrieve surface (view) size of this shape.
      * <p>
-     * The {@link Scene} has be {@link Scene#reshape(com.jogamp.opengl.GLAutoDrawable, int, int, int, int) reshape(..)}ed once.
+     * The given {@link PMVMatrix} will be {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) setup} properly for this shape
+     * including this shape's {@link #setTransform(PMVMatrix)}.
      * </p>
-     * @param scene {@link Scene} source of viewport and local {@link PMVMatrix} {@link Scene#setupMatrix(PMVMatrix) setupMatrix(..)}.
+     * @param pmvMatrixSetup {@link Scene.PMVMatrixSetup} to {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) setup} given {@link PMVMatrix} {@code pmv}.
+     * @param viewport used viewport for {@link PMVMatrix#gluProject(float, float, float, int[], int, float[], int)}
+     * @param pmv a new {@link PMVMatrix} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) be setup},
+     *            {@link #setTransform(PMVMatrix) shape-transformed} and can be reused by the caller.
      * @param surfaceSize int[2] target surface size
      * @return true for successful gluProject(..) operation, otherwise false
      * @see #getSurfaceSize(PMVMatrix, int[], int[])
      */
-    public boolean getSurfaceSize(final Scene scene, final int[/*2*/] surfaceSize) {
-        final PMVMatrix pmv = new PMVMatrix();
-        scene.setupMatrix(pmv);
+    public boolean getSurfaceSize(final Scene.PMVMatrixSetup pmvMatrixSetup, final int[/*4*/] viewport, final PMVMatrix pmv, final int[/*2*/] surfaceSize) {
+        pmvMatrixSetup.set(pmv, viewport[0], viewport[1], viewport[2], viewport[3]);
         setTransform(pmv);
-        return getSurfaceSize(pmv, scene.getViewport(), surfaceSize);
+        return getSurfaceSize(pmv, viewport, surfaceSize);
     }
 
+
     /**
      * Map given object coordinate relative to this shape to window coordinates.
      * <p>
@@ -454,7 +470,7 @@ public abstract class Shape {
      * @param objPos float[3] object position relative to this shape's center
      * @param glWinPos int[2] target window position of objPos relative to this shape
      * @return true for successful gluProject(..) operation, otherwise false
-     * @see #objToWinCoord(Scene, float[], int[])
+     * @see #objToWinCoord(com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup, int[], float[], PMVMatrix, int[])
      */
     public boolean objToWinCoord(final PMVMatrix pmv, final int[/*4*/] viewport, final float[/*3*/] objPos, final int[/*2*/] glWinPos) {
         boolean res = false;
@@ -474,19 +490,22 @@ public abstract class Shape {
     /**
      * Map given object coordinate relative to this shape to window coordinates.
      * <p>
-     * The {@link Scene} has be {@link Scene#reshape(com.jogamp.opengl.GLAutoDrawable, int, int, int, int) reshape(..)}ed once.
+     * The given {@link PMVMatrix} will be {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) setup} properly for this shape
+     * including this shape's {@link #setTransform(PMVMatrix)}.
      * </p>
-     * @param scene {@link Scene} source of viewport and local {@link PMVMatrix} {@link Scene#setupMatrix(PMVMatrix) setupMatrix(..)}.
+     * @param pmvMatrixSetup {@link Scene.PMVMatrixSetup} to {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) setup} given {@link PMVMatrix} {@code pmv}.
+     * @param viewport used viewport for {@link PMVMatrix#gluProject(float, float, float, int[], int, float[], int)}
      * @param objPos float[3] object position relative to this shape's center
+     * @param pmv a new {@link PMVMatrix} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) be setup},
+     *            {@link #setTransform(PMVMatrix) shape-transformed} and can be reused by the caller.
      * @param glWinPos int[2] target window position of objPos relative to this shape
      * @return true for successful gluProject(..) operation, otherwise false
      * @see #objToWinCoord(PMVMatrix, int[], float[], int[])
      */
-    public boolean objToWinCoord(final Scene scene, final float[/*3*/] objPos, final int[/*2*/] glWinPos) {
-        final PMVMatrix pmv = new PMVMatrix();
-        scene.setupMatrix(pmv);
+    public boolean objToWinCoord(final Scene.PMVMatrixSetup pmvMatrixSetup, final int[/*4*/] viewport, final float[/*3*/] objPos, final PMVMatrix pmv, final int[/*2*/] glWinPos) {
+        pmvMatrixSetup.set(pmv, viewport[0], viewport[1], viewport[2], viewport[3]);
         setTransform(pmv);
-        return this.objToWinCoord(pmv, scene.getViewport(), objPos, glWinPos);
+        return this.objToWinCoord(pmv, viewport, objPos, glWinPos);
     }
 
     /**
@@ -502,7 +521,7 @@ public abstract class Shape {
      * @param glWinY in GL window coordinates, origin bottom-left
      * @param objPos float[3] target object position of glWinX/glWinY relative to this shape
      * @return true for successful gluProject(..) and gluUnProject(..) operations, otherwise false
-     * @see #winToObjCoord(Scene, int, int, float[])
+     * @see #winToObjCoord(com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup, int[], int, int, PMVMatrix, float[])
      */
     public boolean winToObjCoord(final PMVMatrix pmv, final int[/*4*/] viewport, final int glWinX, final int glWinY, final float[/*3*/] objPos) {
         boolean res = false;
@@ -520,23 +539,26 @@ public abstract class Shape {
     }
 
     /**
-     * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate
-     * using a local {@link PMVMatrix}.
+     * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate.
      * <p>
-     * The {@link Scene} has be {@link Scene#reshape(com.jogamp.opengl.GLAutoDrawable, int, int, int, int) reshape(..)}ed once.
+     * The given {@link PMVMatrix} will be {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) setup} properly for this shape
+     * including this shape's {@link #setTransform(PMVMatrix)}.
      * </p>
+     * @param pmvMatrixSetup {@link Scene.PMVMatrixSetup} to {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) setup} given {@link PMVMatrix} {@code pmv}.
+     * @param viewport used viewport for {@link PMVMatrix#gluUnProject(float, float, float, int[], int, float[], int)}
      * @param scene {@link Scene} source of viewport and local {@link PMVMatrix} {@link Scene#setupMatrix(PMVMatrix) setupMatrix(..)}.
      * @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 #setTransform(PMVMatrix) shape-transformed} and can be reused by the caller.
      * @param objPos float[3] target object position of glWinX/glWinY relative to this shape
-     * @return @return true for successful gluProject(..) and gluUnProject(..) operations, otherwise false
+     * @return true for successful gluProject(..) and gluUnProject(..) operations, otherwise false
      * @see #winToObjCoord(PMVMatrix, int[], int, int, float[])
      */
-    public boolean winToObjCoord(final Scene scene, final int glWinX, final int glWinY, final float[/*3*/] objPos) {
-        final PMVMatrix pmv = new PMVMatrix();
-        scene.setupMatrix(pmv);
+    public boolean winToObjCoord(final Scene.PMVMatrixSetup pmvMatrixSetup, final int[/*4*/] viewport, final int glWinX, final int glWinY, final PMVMatrix pmv, final float[/*3*/] objPos) {
+        pmvMatrixSetup.set(pmv, viewport[0], viewport[1], viewport[2], viewport[3]);
         setTransform(pmv);
-        return this.winToObjCoord(pmv, scene.getViewport(), glWinX, glWinY, objPos);
+        return this.winToObjCoord(pmv, viewport, glWinX, glWinY, objPos);
     }
 
     public float[] getColor() {
@@ -912,18 +934,20 @@ public abstract class Shape {
     }
 
     /**
-     * @param renderer TODO
      * @param e original Newt {@link GestureEvent}
      * @param glWinX x-position in OpenGL model space
      * @param glWinY y-position in OpenGL model space
+     * @param pmv well formed PMVMatrix for this shape
+     * @param viewport the viewport
+     * @param objPos object position of mouse event relative to this shape
      */
-    /* pp */ final void dispatchGestureEvent(final Scene scene, final GestureEvent e, final int glWinX, final int glWinY, final float[] objPos) {
+    /* pp */ final void dispatchGestureEvent(final GestureEvent e, final int glWinX, final int glWinY, final PMVMatrix pmv, final int[] viewport, final float[] objPos) {
         if( resizable && e instanceof PinchToZoomGesture.ZoomEvent ) {
             final PinchToZoomGesture.ZoomEvent ze = (PinchToZoomGesture.ZoomEvent) e;
             final float pixels = ze.getDelta() * ze.getScale(); //
             final float[] objPos2 = { 0f, 0f, 0f };
             final int winX2 = glWinX + Math.round(pixels);
-            final boolean ok = winToObjCoord(scene, winX2, glWinY, objPos2);
+            final boolean ok = winToObjCoord(pmv, viewport, winX2, glWinY, objPos2);
             final float dx = objPos2[0];
             final float dy = objPos2[1];
             final float sx = scale[0] + ( dx/box.getWidth() ); // bottom-right
@@ -933,7 +957,7 @@ public abstract class Shape {
                         inResize, ok, glWinX, glWinY, objPos[0], objPos[1], objPos[2], position[0], position[1], position[2],
                         dx, dy, sx, sy);
             }
-            if( ok && resize_sxy_min <= sx && sx <= resize_sxy_max && resize_sxy_min <= sy && sy <= resize_sxy_max ) {
+            if( ok && resize_sxy_min <= sx && resize_sxy_min <= sy ) { // avoid scale flip
                 if( DEBUG ) {
                     System.err.printf("PinchZoom: pixels %f, obj %4d/%4d, %.3f/%.3f/%.3f %.3f/%.3f/%.3f + %.3f/%.3f -> %.3f/%.3f%n",
                             pixels, glWinX, glWinY, objPos[0], objPos[1], objPos[2], position[0], position[1], position[2],
@@ -958,7 +982,7 @@ public abstract class Shape {
 
     protected abstract void clearImpl(GL2ES2 gl, RegionRenderer renderer);
     protected abstract void destroyImpl(GL2ES2 gl, RegionRenderer renderer);
-    protected abstract void addShapeToRegion(GL2ES2 gl, RegionRenderer renderer);
+    protected abstract void addShapeToRegion();
 
     //
     //
-- 
cgit v1.2.3