From 4f32f3aba62a73cafecec8af461cff4d0d475882 Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Fri, 31 Mar 2023 11:06:44 +0200 Subject: 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. --- .../classes/com/jogamp/graph/ui/GraphShape.java | 200 ++++ src/graphui/classes/com/jogamp/graph/ui/Scene.java | 1073 ++++++++++++++++++ src/graphui/classes/com/jogamp/graph/ui/Shape.java | 1191 ++++++++++++++++++++ .../classes/com/jogamp/graph/ui/gl/GraphShape.java | 200 ---- .../classes/com/jogamp/graph/ui/gl/Scene.java | 1073 ------------------ .../classes/com/jogamp/graph/ui/gl/Shape.java | 1191 -------------------- .../com/jogamp/graph/ui/gl/shapes/Button.java | 208 ---- .../com/jogamp/graph/ui/gl/shapes/CrossHair.java | 99 -- .../com/jogamp/graph/ui/gl/shapes/GLButton.java | 170 --- .../com/jogamp/graph/ui/gl/shapes/GlyphShape.java | 204 ---- .../com/jogamp/graph/ui/gl/shapes/ImageButton.java | 68 -- .../com/jogamp/graph/ui/gl/shapes/Label.java | 226 ---- .../com/jogamp/graph/ui/gl/shapes/MediaButton.java | 139 --- .../com/jogamp/graph/ui/gl/shapes/Rectangle.java | 104 -- .../com/jogamp/graph/ui/gl/shapes/RoundButton.java | 129 --- .../jogamp/graph/ui/gl/shapes/TexSeqButton.java | 82 -- .../classes/com/jogamp/graph/ui/shapes/Button.java | 208 ++++ .../com/jogamp/graph/ui/shapes/CrossHair.java | 99 ++ .../com/jogamp/graph/ui/shapes/GLButton.java | 170 +++ .../com/jogamp/graph/ui/shapes/GlyphShape.java | 204 ++++ .../com/jogamp/graph/ui/shapes/ImageButton.java | 68 ++ .../classes/com/jogamp/graph/ui/shapes/Label.java | 225 ++++ .../com/jogamp/graph/ui/shapes/MediaButton.java | 139 +++ .../com/jogamp/graph/ui/shapes/Rectangle.java | 104 ++ .../com/jogamp/graph/ui/shapes/RoundButton.java | 129 +++ .../com/jogamp/graph/ui/shapes/TexSeqButton.java | 82 ++ 26 files changed, 3892 insertions(+), 3893 deletions(-) create mode 100644 src/graphui/classes/com/jogamp/graph/ui/GraphShape.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/Scene.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/Shape.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/GraphShape.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Button.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/shapes/CrossHair.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GLButton.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GlyphShape.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/shapes/ImageButton.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Label.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/shapes/MediaButton.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Rectangle.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/shapes/RoundButton.java delete mode 100644 src/graphui/classes/com/jogamp/graph/ui/gl/shapes/TexSeqButton.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/shapes/Button.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/shapes/CrossHair.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/shapes/GLButton.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/shapes/GlyphShape.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/shapes/ImageButton.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/shapes/Label.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/shapes/MediaButton.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/shapes/Rectangle.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/shapes/RoundButton.java create mode 100644 src/graphui/classes/com/jogamp/graph/ui/shapes/TexSeqButton.java (limited to 'src/graphui/classes/com/jogamp/graph') diff --git a/src/graphui/classes/com/jogamp/graph/ui/GraphShape.java b/src/graphui/classes/com/jogamp/graph/ui/GraphShape.java new file mode 100644 index 000000000..6efd7f5f4 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/GraphShape.java @@ -0,0 +1,200 @@ +/** + * 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.util.ArrayList; +import java.util.List; + +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.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.opengl.util.texture.TextureSequence; + +/** + * Graph based {@link GLRegion} UI {@link Shape} + *

+ * GraphUI is GPU based and resolution independent. + *

+ *

+ * GraphUI is intended to become an immediate- and retained-mode API. + *

+ * @see Scene + */ +public abstract class GraphShape extends Shape { + protected final Factory vertexFactory; + + protected final int renderModes; + protected GLRegion region = null; + protected float oshapeSharpness = OutlineShape.DEFAULT_SHARPNESS; + private int regionQuality = Region.MAX_QUALITY; + private final List dirtyRegions = new ArrayList(); + + /** + * Create a Graph based {@link GLRegion} UI {@link Shape}. + * + * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. + */ + public GraphShape(final int renderModes) { + super(); + this.vertexFactory = OutlineShape.getDefaultVertexFactory(); + this.renderModes = renderModes; + } + + /** Return Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. */ + public final int getRenderModes() { return renderModes; } + + public final int getQuality() { return regionQuality; } + public final void setQuality(final int q) { + this.regionQuality = q; + if( null != region ) { + region.setQuality(q); + } + } + public final void setSharpness(final float sharpness) { + this.oshapeSharpness = sharpness; + markShapeDirty(); + } + public final float getSharpness() { + return oshapeSharpness; + } + + @Override + public boolean hasColorChannel() { + return Region.hasColorChannel(renderModes) || Region.hasColorTexture(renderModes); + } + + private final void clearDirtyRegions(final GL2ES2 gl) { + for(final GLRegion r : dirtyRegions) { + r.destroy(gl); + } + dirtyRegions.clear(); + } + + @Override + protected final void clearImpl0(final GL2ES2 gl, final RegionRenderer renderer) { + clearImpl(gl, renderer); + clearDirtyRegions(gl); + if( null != region ) { + region.clear(gl); + } + } + + @Override + protected final void destroyImpl0(final GL2ES2 gl, final RegionRenderer renderer) { + destroyImpl(gl, renderer); + clearDirtyRegions(gl); + if( null != region ) { + region.destroy(gl); + region = null; + } + } + + @Override + protected final void drawImpl0(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount, final float[] rgba) { + if( null != rgba ) { + renderer.getRenderState().setColorStatic(rgba); + } + region.draw(gl, renderer, sampleCount); + } + + protected GLRegion createGLRegion(final GLProfile glp) { + return GLRegion.create(glp, renderModes, null); + } + + @Override + protected final void validateImpl(final GLProfile glp, final GL2ES2 gl) { + if( null != gl ) { + clearDirtyRegions(gl); + } + if( isShapeDirty() || null == region ) { + if( null == region ) { + region = createGLRegion(glp); + } else if( null == gl ) { + dirtyRegions.add(region); + region = createGLRegion(glp); + } else { + region.clear(gl); + } + addShapeToRegion(); + if( hasDebugBox() ) { + addDebugOutline(); + } + region.setQuality(regionQuality); + } else if( isStateDirty() ) { + region.markStateDirty(); + } + } + + private final float[] dbgColor = {0.3f, 0.3f, 0.3f, 0.5f}; + + protected void addDebugOutline() { + final OutlineShape shape = new OutlineShape(vertexFactory); + final float x1 = box.getMinX(); + final float x2 = box.getMaxX(); + final float y1 = box.getMinY(); + final float y2 = box.getMaxY(); + final float z = box.getCenter()[2]; // 0; // box.getMinZ() + 0.025f; + { + // Outer OutlineShape as Winding.CCW. + shape.moveTo(x1, y1, z); + shape.lineTo(x2, y1, z); + shape.lineTo(x2, y2, z); + shape.lineTo(x1, y2, z); + shape.lineTo(x1, y1, z); + shape.closeLastOutline(true); + shape.addEmptyOutline(); + } + { + // Inner OutlineShape as Winding.CW. + final float dxy0 = box.getWidth() < box.getHeight() ? box.getWidth() : box.getHeight(); + final float dxy = dxy0 * getDebugBox(); + shape.moveTo(x1+dxy, y1+dxy, z); + shape.lineTo(x1+dxy, y2-dxy, z); + shape.lineTo(x2-dxy, y2-dxy, z); + shape.lineTo(x2-dxy, y1+dxy, z); + shape.lineTo(x1+dxy, y1+dxy, z); + shape.closeLastOutline(true); + } + shape.setIsQuadraticNurbs(); + shape.setSharpness(oshapeSharpness); + region.addOutlineShape(shape, null, dbgColor); + } + + protected void clearImpl(final GL2ES2 gl, final RegionRenderer renderer) { } + + protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { } + + protected abstract void addShapeToRegion(); + +} 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 + *

+ * GraphUI is GPU based and resolution independent. + *

+ *

+ * GraphUI is intended to become an immediate- and retained-mode API. + *

+ *

+ * 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)} + *

+ *

+ * {@link #setPMVMatrixSetup(PMVMatrixSetup)} maybe used to provide a custom {@link PMVMatrix} setup. + *

+ * @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 shapes = new ArrayList(); + 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 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 shapes) { + for(final Shape s : shapes) { + addShape(s); + } + } + /** Removes all given shapes, keeps them alive. */ + public void removeShapes(final Collection shapes) { + for(final Shape s : shapes) { + removeShape(s); + } + } + /** Removes all given shapes and destroys them. */ + public void removeShapes(final GL2ES2 gl, final Collection 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 + * {@inheritDoc} + *

+ * @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 shapeZAscComparator = new Comparator() { + @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 + * Implementation also issues {@link RegionRenderer#destroy(GL2ES2)} if set + * and {@link #detachInputListenerFrom(GLWindow)} in case the drawable is of type {@link GLWindow}. + *

+ *

+ * {@inheritDoc} + *

+ */ + @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 + * If {@link Shape} was found the given action is performed. + *

+ *

+ * Method performs on current thread and returns after probing every {@link Shape}. + *

+ * @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. + *

+ * If {@link Shape} was found the given action is performed on the rendering thread. + *

+ *

+ * Method is non blocking and performs on rendering-thread, it returns immediately. + *

+ * @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}. + *

+ * At the end of operations, the {@link GLMatrixFunc#GL_MODELVIEW} matrix has to be selected. + *

+ *

+ * Implementation is being called by {@link Scene#setupMatrix(PMVMatrix, int, int, int, int)} + * and hence {@link Scene#reshape(GLAutoDrawable, int, int, int, int)}. + *

+ *

+ * Custom implementations can be set via {@link Scene#setPMVMatrixSetup(PMVMatrixSetup)}. + *

+ *

+ * The default implementation is described below: + *

    + *
  • {@link GLMatrixFunc#GL_PROJECTION} Matrix + *
      + *
    • Identity
    • + *
    • Perspective {@link Scene#DEFAULT_ANGLE} with {@link Scene#DEFAULT_ZNEAR} and {@link Scene#DEFAULT_ZFAR}
    • + *
    • Translated to given {@link Scene#DEFAULT_SCENE_DIST}
    • + *
    • Scale (back) to have normalized {@link Scene#getBounds() plane dimensions}, 1 for the greater of width and height.
    • + *
  • + *
  • {@link GLMatrixFunc#GL_MODELVIEW} Matrix + *
      + *
    • identity
    • + *
  • + *
+ *

+ */ + public static interface PMVMatrixSetup { + /** + * Setup {@link PMVMatrix}'s {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW}. + *

+ * See {@link PMVMatrixSetup} for details. + *

+ *

+ * At the end of operations, the {@link GLMatrixFunc#GL_MODELVIEW} matrix is selected. + *

+ * @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. + *

+ * Will be called by {@link Scene#reshape(GLAutoDrawable, int, int, int, int)} after {@link #set(PMVMatrix, int, int, int, int)}. + *

+ * @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. + *

+ * The value is evaluated at {@link #reshape(GLAutoDrawable, int, int, int, int)} via {@link } + *

+ *

+ * {@link AABBox#getWidth()} and {@link AABBox#getHeight()} define scene's dimension covered by surface size. + *

+ *

+ * {@link AABBox} is setup via {@link #getPMVMatrixSetup()}'s {@link PMVMatrixSetup#setPlaneBox(AABBox, PMVMatrix, int, int, int, int)}. + *

+ *

+ * The default {@link PMVMatrixSetup} implementation scales to normalized plane dimensions, 1 for the greater of width and height. + *

+ */ + 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; +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/Shape.java b/src/graphui/classes/com/jogamp/graph/ui/Shape.java new file mode 100644 index 000000000..e777dacff --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/Shape.java @@ -0,0 +1,1191 @@ +/** + * 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.util.ArrayList; + +import com.jogamp.nativewindow.NativeWindowException; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.fixedfunc.GLMatrixFunc; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.newt.event.GestureHandler.GestureEvent; +import com.jogamp.newt.event.GestureHandler.GestureListener; +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.MouseListener; +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.math.Quaternion; +import com.jogamp.opengl.math.VectorUtil; +import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.opengl.util.PMVMatrix; + +/** + * Generic UI Shape, potentially using a Graph via {@link GraphShape} or other means of representing content. + *

+ * A shape includes the following build-in user-interactions + * - drag shape w/ 1-pointer click, see {@link #setDraggable(boolean)} + * - resize shape w/ 1-pointer click and drag in 1/4th bottom-left and bottom-right corner, see {@link #setResizable(boolean)}. + *

+ *

+ * A shape is expected to have its 0/0 origin in its bottom-left corner, otherwise the drag-zoom sticky-edge will not work as expected. + *

+ *

+ * GraphUI is GPU based and resolution independent. + *

+ *

+ * GraphUI is intended to become an immediate- and retained-mode API. + *

+ * @see Scene + */ +public abstract class Shape { + public static interface Listener { + void run(final Shape shape); + } + protected static final boolean DEBUG_DRAW = false; + private static final boolean DEBUG = false; + + private static final int DIRTY_SHAPE = 1 << 0 ; + private static final int DIRTY_STATE = 1 << 1 ; + + protected final AABBox box; + + private final float[] position = new float[] { 0f, 0f, 0f }; + private final Quaternion rotation = new Quaternion(); + private final float[] rotPivot = new float[] { 0f, 0f, 0f }; + private final float[] scale = new float[] { 1f, 1f, 1f }; + + private volatile int dirty = DIRTY_SHAPE | DIRTY_STATE; + private final Object dirtySync = new Object(); + + /** Default base-color w/o color channel, will be modulated w/ pressed- and toggle color */ + protected final float[] rgbaColor = {0.75f, 0.75f, 0.75f, 1.0f}; + /** Default pressed color-factor w/o color channel, modulated base-color. 0.75 * 1.2 = 0.9 */ + protected final float[] pressedRGBAModulate = {1.2f, 1.2f, 1.2f, 0.7f}; + /** Default toggle color-factor w/o color channel, modulated base-color. 0.75 * 1.13 ~ 0.85 */ + protected final float[] toggleOnRGBAModulate = {1.13f, 1.13f, 1.13f, 1.0f}; + /** Default toggle color-factor w/o color channel, modulated base-color. 0.75 * 0.86 ~ 0.65 */ + protected final float[] toggleOffRGBAModulate = {0.86f, 0.86f, 0.86f, 1.0f}; + + private int name = -1; + + private boolean down = false; + private boolean toggle = false; + private boolean toggleable = false; + private boolean draggable = true; + private boolean resizable = true; + private boolean enabled = true; + private float dbgbox_thickness = 0f; // fractional thickness of bounds, 0f for no debug box + private ArrayList mouseListeners = new ArrayList(); + + private Listener onMoveListener = null; + + public Shape() { + this.box = new AABBox(); + } + + /** Set a symbolic name for this shape for identification. Default is -1 for noname. */ + public final void setName(final int name) { this.name = name; } + /** Return the optional symbolic name for this shape. */ + public final int getName() { return this.name; } + + /** Returns true if this shape is enabled and hence visible, otherwise false. */ + public final boolean isEnabled() { return enabled; } + /** Enable or disable this shape, i.e. its visibility. */ + public final void setEnabled(final boolean v) { enabled = v; } + + /** + * Sets the {@link #getBounds()} fractional thickness of the debug box ranging [0..1], 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 = Math.min(1f, Math.max(0f, v)); + } + /** Returns true if a debug box has been enabled via {@link #setDebugBox(float)}. */ + public final boolean hasDebugBox() { return !FloatUtil.isZero(dbgbox_thickness); } + /** Returns the fractional thickness of the debug box ranging [0..1], see {@link #setDebugBox(float)}. */ + public final float getDebugBox() { return dbgbox_thickness; } + + /** + * Clears all data and reset all states as if this instance was newly created + * @param gl TODO + * @param renderer TODO + */ + public final void clear(final GL2ES2 gl, final RegionRenderer renderer) { + synchronized ( dirtySync ) { + clearImpl0(gl, renderer); + position[0] = 0f; + position[1] = 0f; + position[2] = 0f; + rotation.setIdentity(); + rotPivot[0] = 0f; + rotPivot[1] = 0f; + rotPivot[2] = 0f; + scale[0] = 1f; + scale[1] = 1f; + scale[2] = 1f; + box.reset(); + markShapeDirty(); + } + } + + /** + * Destroys all data + * @param gl + * @param renderer + */ + public final void destroy(final GL2ES2 gl, final RegionRenderer renderer) { + destroyImpl0(gl, renderer); + position[0] = 0f; + position[1] = 0f; + position[2] = 0f; + rotation.setIdentity(); + rotPivot[0] = 0f; + rotPivot[1] = 0f; + rotPivot[2] = 0f; + scale[0] = 1f; + scale[1] = 1f; + scale[2] = 1f; + box.reset(); + markShapeDirty(); + } + + public final void onMove(final Listener l) { onMoveListener = l; } + + /** Move to scaled position. Position ends up in PMVMatrix unmodified. */ + public final void moveTo(final float tx, final float ty, final float tz) { + position[0] = tx; + position[1] = ty; + position[2] = tz; + if( null != onMoveListener ) { + onMoveListener.run(this); + } + // System.err.println("Shape.setTranslate: "+tx+"/"+ty+"/"+tz+": "+toString()); + } + + /** Move about scaled distance. Position ends up in PMVMatrix unmodified. */ + public final void move(final float dtx, final float dty, final float dtz) { + position[0] += dtx; + position[1] += dty; + position[2] += dtz; + if( null != onMoveListener ) { + onMoveListener.run(this); + } + // System.err.println("Shape.translate: "+tx+"/"+ty+"/"+tz+": "+toString()); + } + + /** Returns float[3] position, i.e. scaled translation as set via {@link #moveTo(float, float, float) or {@link #move(float, float, float)}}. */ + public final float[] getPosition() { return position; } + + /** Returns {@link Quaternion} for rotation. */ + public final Quaternion getRotation() { return rotation; } + /** Return float[3] unscaled rotation origin, aka pivot. */ + public final float[] getRotationPivot() { return rotPivot; } + /** Set unscaled rotation origin, aka pivot. Usually the {@link #getBounds()} center and should be set while {@link #validateImpl(GLProfile, GL2ES2)}. */ + public final void setRotationPivot(final float rx, final float ry, final float rz) { + rotPivot[0] = rx; + rotPivot[1] = ry; + rotPivot[2] = rz; + } + /** + * Set unscaled rotation origin, aka pivot. Usually the {@link #getBounds()} center and should be set while {@link #validateImpl(GLProfile, GL2ES2)}. + * @param pivot float[3] rotation origin + */ + public final void setRotationPivot(final float[/*3*/] pivot) { + System.arraycopy(pivot, 0, rotPivot, 0, 3); + } + + /** + * Set scale factor to given scale. + * @see #scale(float, float, float) + * @see #getScale() + */ + public final void setScale(final float sx, final float sy, final float sz) { + scale[0] = sx; + scale[1] = sy; + scale[2] = sz; + } + /** + * Multiply current scale factor by given scale. + * @see #setScale(float, float, float) + * @see #getScale() + */ + public final void scale(final float sx, final float sy, final float sz) { + scale[0] *= sx; + scale[1] *= sy; + scale[2] *= sz; + } + /** + * Returns float[3] scale factors. + * @see #setScale(float, float, float) + * @see #scale(float, float, float) + */ + public final float[] getScale() { return scale; } + /** Returns X-axis scale factor. */ + public final float getScaleX() { return scale[0]; } + /** Returns Y-axis scale factor. */ + public final float getScaleY() { return scale[1]; } + /** Returns Z-axis scale factor. */ + public final float getScaleZ() { return scale[2]; } + + /** + * Marks the shape dirty, causing next {@link #draw(GL2ES2, RegionRenderer, int[]) draw()} + * to recreate the Graph shape and reset the region. + */ + public final void markShapeDirty() { + synchronized ( dirtySync ) { + dirty |= DIRTY_SHAPE; + } + } + + /** + * Marks the rendering state dirty, causing next {@link #draw(GL2ES2, RegionRenderer, int[]) draw()} + * to notify the Graph region to reselect shader and repaint potentially used FBOs. + */ + public final void markStateDirty() { + synchronized ( dirtySync ) { + dirty |= DIRTY_STATE; + } + } + + protected final boolean isShapeDirty() { + return 0 != ( dirty & DIRTY_SHAPE ) ; + } + protected final boolean isStateDirty() { + return 0 != ( dirty & DIRTY_STATE ) ; + } + + /** + * Returns the unscaled bounding {@link AABBox} for this shape, borrowing internal instance. + * + * The returned {@link AABBox} will only cover this unscaled shape + * after an initial call to {@link #draw(GL2ES2, RegionRenderer, int[]) draw(..)} + * or {@link #validate(GL2ES2)}. + * + * @see #getBounds(GLProfile) + */ + public final AABBox getBounds() { return box; } + + /** + * Returns the scaled width of the bounding {@link AABBox} for this shape. + * + * The returned width will only cover the scaled shape + * after an initial call to {@link #draw(GL2ES2, RegionRenderer, int[]) draw(..)} + * or {@link #validate(GL2ES2)}. + * + * @see #getBounds() + */ + public final float getScaledWidth() { + return box.getWidth() * getScaleX(); + } + + /** + * Returns the scaled height of the bounding {@link AABBox} for this shape. + * + * The returned height will only cover the scaled shape + * after an initial call to {@link #draw(GL2ES2, RegionRenderer, int[]) draw(..)} + * or {@link #validate(GL2ES2)}. + * + * @see #getBounds() + */ + public final float getScaledHeight() { + return box.getHeight() * getScaleY(); + } + + /** + * 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) { + validate(glp); + return box; + } + + /** Experimental selection draw command used by {@link Scene}. */ + public void drawToSelect(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { + synchronized ( dirtySync ) { + validate(gl); + drawImpl0(gl, renderer, sampleCount, null); + } + } + + private final float[] rgba_tmp = { 0, 0, 0, 1 }; + + /** + * Renders the shape. + *

+ * {@link #setTransform(PMVMatrix)} is expected to be completed beforehand. + *

+ * @param gl the current GL object + * @param renderer {@link RegionRenderer} which might be used for Graph Curve Rendering, also source of {@link RegionRenderer#getMatrix()} and {@link RegionRenderer#getViewport()}. + * @param sampleCount sample count if used by Graph renderModes + */ + public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { + final boolean isPressed = isPressed(), isToggleOn = isToggleOn(); + final float[] rgba; + if( hasColorChannel() ) { + if( isPressed ) { + rgba = pressedRGBAModulate; + } else if( isToggleable() ) { + if( isToggleOn ) { + rgba = toggleOnRGBAModulate; + } else { + rgba = toggleOffRGBAModulate; + } + } else { + rgba = rgbaColor; + } + } else { + rgba = rgba_tmp; + if( isPressed ) { + rgba[0] = rgbaColor[0]*pressedRGBAModulate[0]; + rgba[1] = rgbaColor[1]*pressedRGBAModulate[1]; + rgba[2] = rgbaColor[2]*pressedRGBAModulate[2]; + rgba[3] = rgbaColor[3]*pressedRGBAModulate[3]; + } else if( isToggleable() ) { + if( isToggleOn ) { + rgba[0] = rgbaColor[0]*toggleOnRGBAModulate[0]; + rgba[1] = rgbaColor[1]*toggleOnRGBAModulate[1]; + rgba[2] = rgbaColor[2]*toggleOnRGBAModulate[2]; + rgba[3] = rgbaColor[3]*toggleOnRGBAModulate[3]; + } else { + rgba[0] = rgbaColor[0]*toggleOffRGBAModulate[0]; + rgba[1] = rgbaColor[1]*toggleOffRGBAModulate[1]; + rgba[2] = rgbaColor[2]*toggleOffRGBAModulate[2]; + rgba[3] = rgbaColor[3]*toggleOffRGBAModulate[3]; + } + } else { + rgba[0] = rgbaColor[0]; + rgba[1] = rgbaColor[1]; + rgba[2] = rgbaColor[2]; + rgba[3] = rgbaColor[3]; + } + } + synchronized ( dirtySync ) { + validate(gl); + drawImpl0(gl, renderer, sampleCount, rgba); + } + } + + /** + * Validates the shape's underlying {@link GLRegion}. + *

+ * If the region is dirty, it gets {@link GLRegion#clear(GL2ES2) cleared} and is reused. + *

+ * @param gl current {@link GL2ES2} object + * @see #validate(GLProfile) + */ + public final void validate(final GL2ES2 gl) { + synchronized ( dirtySync ) { + if( isShapeDirty() ) { + box.reset(); + } + validateImpl(gl.getGLProfile(), gl); + dirty = 0; + } + } + + /** + * Validates the shape's underlying {@link GLRegion} w/o a current {@link GL2ES2} object + *

+ * If the region is dirty a new region is created + * and the old one gets pushed to a dirty-list to get disposed when a GL context is available. + *

+ * @see #validate(GL2ES2) + */ + public final void validate(final GLProfile glp) { + synchronized ( dirtySync ) { + if( isShapeDirty() ) { + box.reset(); + } + validateImpl(glp, null); + dirty = 0; + } + } + + /** + * Setup the pre-selected {@link GLMatrixFunc#GL_MODELVIEW} {@link PMVMatrix} for this object. + * - Scale shape from its center position + * - Rotate shape around optional scaled pivot, see {@link #setRotationPivot(float[])}), otherwise rotate around its scaled center (default) + *

+ * Shape's origin should be bottom-left @ 0/0 to have build-in drag-zoom work properly. + *

+ * @param pmv the matrix + * @see #setRotationPivot(float[]) + * @see #getRotation() + * @see #moveTo(float, float, float) + * @see #setScale(float, float, float) + */ + public void setTransform(final PMVMatrix pmv) { + final boolean hasScale = !VectorUtil.isVec3Equal(scale, 0, VectorUtil.VEC3_ONE, 0, FloatUtil.EPSILON); + final boolean hasRotate = !rotation.isIdentity(); + final boolean hasRotPivot = !VectorUtil.isVec3Zero(rotPivot, 0, FloatUtil.EPSILON); + final float[] ctr = box.getCenter(); + final boolean sameScaleRotatePivot = hasScale && hasRotate && ( !hasRotPivot || VectorUtil.isVec3Equal(rotPivot, 0, ctr, 0, FloatUtil.EPSILON) ); + + pmv.glTranslatef(position[0], position[1], position[2]); // translate, scaled + + if( sameScaleRotatePivot ) { + // Scale shape from its center position and rotate around its center + pmv.glTranslatef(ctr[0]*scale[0], ctr[1]*scale[1], ctr[2]*scale[2]); // add-back center, scaled + pmv.glRotate(rotation); + pmv.glScalef(scale[0], scale[1], scale[2]); + pmv.glTranslatef(-ctr[0], -ctr[1], -ctr[2]); // move to center + } else if( hasRotate || hasScale ) { + if( hasRotate ) { + if( hasRotPivot ) { + // Rotate shape around its scaled pivot + pmv.glTranslatef(rotPivot[0]*scale[0], rotPivot[1]*scale[1], rotPivot[2]*scale[2]); // pivot back from rot-pivot, scaled + pmv.glRotate(rotation); + pmv.glTranslatef(-rotPivot[0]*scale[0], -rotPivot[1]*scale[1], -rotPivot[2]*scale[2]); // pivot to rot-pivot, scaled + } else { + // Rotate shape around its scaled center + pmv.glTranslatef(ctr[0]*scale[0], ctr[1]*scale[1], ctr[2]*scale[2]); // pivot back from center-pivot, scaled + pmv.glRotate(rotation); + pmv.glTranslatef(-ctr[0]*scale[0], -ctr[1]*scale[1], -ctr[2]*scale[2]); // pivot to center-pivot, scaled + } + } + if( hasScale ) { + // Scale shape from its center position + pmv.glTranslatef(ctr[0]*scale[0], ctr[1]*scale[1], ctr[2]*scale[2]); // add-back center, scaled + pmv.glScalef(scale[0], scale[1], scale[2]); + pmv.glTranslatef(-ctr[0], -ctr[1], -ctr[2]); // move to center + } + } + // TODO: Add alignment features. + } + + /** + * Retrieve surface (view) size of this shape. + *

+ * The given {@link PMVMatrix} has to be setup properly for this object, + * i.e. its {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW} for the surrounding scene + * including this shape's {@link #setTransform(PMVMatrix)}. + *

+ * @param pmv well formed {@link PMVMatrix}, e.g. could have been setup via {@link Scene#setupMatrix(PMVMatrix) setupMatrix(..)} and {@link #setTransform(PMVMatrix)}. + * @param viewport the int[4] viewport + * @param surfaceSize int[2] target surface size + * @return given int[2] {@code surfaceSize} for successful gluProject(..) operation, otherwise {@code null} + * @see #getSurfaceSize(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], PMVMatrix, int[]) + * @see #getSurfaceSize(Scene, PMVMatrix, int[]) + */ + public int[/*2*/] getSurfaceSize(final PMVMatrix pmv, final int[/*4*/] viewport, final int[/*2*/] surfaceSize) { + // System.err.println("Shape::getSurfaceSize.VP "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]); + final float[] winCoordHigh = new float[3]; + final float[] winCoordLow = new float[3]; + final float[] high = getBounds().getHigh(); + final float[] low = getBounds().getLow(); + + if( pmv.gluProject(high[0], high[1], high[2], viewport, 0, winCoordHigh, 0) ) { + // System.err.printf("Shape::surfaceSize.H: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), high[0], high[1], high[2], winCoordHigh[0], winCoordHigh[1], winCoordHigh[2]); + if( pmv.gluProject(low[0], low[1], low[2], viewport, 0, winCoordLow, 0) ) { + // System.err.printf("Shape::surfaceSize.L: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), low[0], low[1], low[2], winCoordLow[0], winCoordLow[1], winCoordLow[2]); + surfaceSize[0] = (int)(winCoordHigh[0] - winCoordLow[0]); + surfaceSize[1] = (int)(winCoordHigh[1] - winCoordLow[1]); + // System.err.printf("Shape::surfaceSize.S: shape %d: %f x %f -> %d x %d%n", getName(), winCoordHigh[0] - winCoordLow[0], winCoordHigh[1] - winCoordLow[1], surfaceSize[0], surfaceSize[1]); + return surfaceSize; + } + } + return null; + } + + /** + * Retrieve surface (view) size of this shape. + *

+ * 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)}. + *

+ * @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 given int[2] {@code surfaceSize} for successful gluProject(..) operation, otherwise {@code null} + * @see #getSurfaceSize(PMVMatrix, int[], int[]) + * @see #getSurfaceSize(Scene, PMVMatrix, int[]) + */ + public int[/*2*/] 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, viewport, surfaceSize); + } + + /** + * Retrieve surface (view) size of this shape. + *

+ * 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)}. + *

+ * @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. + * @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 given int[2] {@code surfaceSize} for successful gluProject(..) operation, otherwise {@code null} + * @see #getSurfaceSize(PMVMatrix, int[], int[]) + * @see #getSurfaceSize(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], PMVMatrix, int[]) + */ + public int[/*2*/] getSurfaceSize(final Scene scene, final PMVMatrix pmv, final int[/*2*/] surfaceSize) { + return getSurfaceSize(scene.getPMVMatrixSetup(), scene.getViewport(), pmv, surfaceSize); + } + + /** + * Retrieve pixel per scaled shape-coordinate unit, i.e. [px]/[obj]. + *

+ * 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)}. + *

+ * @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. + * @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 pixPerShape float[2] pixel per scaled shape-coordinate unit result storage + * @return given float[2] {@code pixPerShape} for successful gluProject(..) operation, otherwise {@code null} + * @see #getPixelPerShapeUnit(int[], float[]) + * @see #getSurfaceSize(Scene, PMVMatrix, int[]) + * @see #getScaledWidth() + * @see #getScaledHeight() + */ + public float[] getPixelPerShapeUnit(final Scene scene, final PMVMatrix pmv, final float[] pixPerShape) { + final int[] shapeSizePx = new int[2]; + if( null != getSurfaceSize(scene, new PMVMatrix(), shapeSizePx) ) { + return getPixelPerShapeUnit(shapeSizePx, pixPerShape); + } else { + return null; + } + } + + /** + * Retrieve pixel per scaled shape-coordinate unit, i.e. [px]/[obj]. + * @param shapeSizePx int[2] shape size in pixel as retrieved via e.g. {@link #getSurfaceSize(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], PMVMatrix, int[])} + * @param pixPerShape float[2] pixel scaled per shape-coordinate unit result storage + * @return given float[2] {@code pixPerShape} + * @see #getPixelPerShapeUnit(Scene, PMVMatrix, float[]) + * @see #getSurfaceSize(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], PMVMatrix, int[]) + * @see #getScaledWidth() + * @see #getScaledHeight() + */ + public float[] getPixelPerShapeUnit(final int[] shapeSizePx, final float[] pixPerShape) { + pixPerShape[0] = shapeSizePx[0] / getScaledWidth(); + pixPerShape[0] = shapeSizePx[1] / getScaledHeight(); + return pixPerShape; + } + + /** + * Map given object coordinate relative to this shape to window coordinates. + *

+ * The given {@link PMVMatrix} has to be setup properly for this object, + * i.e. its {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW} for the surrounding scene + * including this shape's {@link #setTransform(PMVMatrix)}. + *

+ * @param pmv well formed {@link PMVMatrix}, e.g. could have been setup via {@link Scene#setupMatrix(PMVMatrix) setupMatrix(..)} and {@link #setTransform(PMVMatrix)}. + * @param viewport the int[4] viewport + * @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 given int[2] {@code glWinPos} for successful gluProject(..) operation, otherwise {@code null} + * @see #shapeToWinCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], float[], PMVMatrix, int[]) + * @see #shapeToWinCoord(Scene, float[], PMVMatrix, int[]) + */ + public int[/*2*/] shapeToWinCoord(final PMVMatrix pmv, final int[/*4*/] viewport, final float[/*3*/] objPos, final int[/*2*/] glWinPos) { + // System.err.println("Shape::objToWinCoordgetSurfaceSize.VP "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]); + final float[] winCoord = new float[3]; + + if( pmv.gluProject(objPos[0], objPos[1], objPos[2], viewport, 0, winCoord, 0) ) { + // System.err.printf("Shape::objToWinCoord.0: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), objPos[0], objPos[1], objPos[2], winCoord[0], winCoord[1], winCoord[2]); + glWinPos[0] = (int)(winCoord[0]); + glWinPos[1] = (int)(winCoord[1]); + // System.err.printf("Shape::objToWinCoord.X: shape %d: %f / %f -> %d / %d%n", getName(), winCoord[0], winCoord[1], glWinPos[0], glWinPos[1]); + return glWinPos; + } + return null; + } + + /** + * Map given object coordinate relative to this shape to window coordinates. + *

+ * 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)}. + *

+ * @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 given int[2] {@code glWinPos} for successful gluProject(..) operation, otherwise {@code null} + * @see #shapeToWinCoord(PMVMatrix, int[], float[], int[]) + * @see #shapeToWinCoord(Scene, float[], PMVMatrix, int[]) + */ + public int[/*2*/] shapeToWinCoord(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.shapeToWinCoord(pmv, viewport, objPos, glWinPos); + } + + /** + * Map given object coordinate relative to this shape to window coordinates. + *

+ * 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)}. + *

+ * @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. + * @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 given int[2] {@code glWinPos} for successful gluProject(..) operation, otherwise {@code null} + * @see #shapeToWinCoord(PMVMatrix, int[], float[], int[]) + * @see #shapeToWinCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], float[], PMVMatrix, int[]) + */ + public int[/*2*/] shapeToWinCoord(final Scene scene, final float[/*3*/] objPos, final PMVMatrix pmv, final int[/*2*/] glWinPos) { + return this.shapeToWinCoord(scene.getPMVMatrixSetup(), scene.getViewport(), objPos, pmv, glWinPos); + } + + /** + * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate. + *

+ * The given {@link PMVMatrix} has to be setup properly for this object, + * i.e. its {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW} for the surrounding scene + * including this shape's {@link #setTransform(PMVMatrix)}. + *

+ * @param pmv well formed {@link PMVMatrix}, e.g. could have been setup via {@link Scene#setupMatrix(PMVMatrix) setupMatrix(..)} and {@link #setTransform(PMVMatrix)}. + * @param viewport the int[4] viewport + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param objPos float[3] target object position of glWinX/glWinY relative to this shape + * @return given float[3] {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} + * @see #winToShapeCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], int, int, PMVMatrix, float[]) + * @see #winToShapeCoord(Scene, int, int, PMVMatrix, float[]) + */ + public float[/*3*/] winToShapeCoord(final PMVMatrix pmv, final int[/*4*/] viewport, final int glWinX, final int glWinY, final float[/*3*/] objPos) { + final float[] ctr = getBounds().getCenter(); + final float[] tmp = new float[3]; + + if( pmv.gluProject(ctr[0], ctr[1], ctr[2], viewport, 0, tmp, 0) ) { + // System.err.printf("Shape::winToObjCoord.0: shape %d: obj [%15.10ff, %15.10ff, %15.10ff] -> win [%d / %d -> %7.2ff, %7.2ff, %7.2ff, diff %7.2ff x %7.2ff]%n", getName(), ctr[0], ctr[1], ctr[2], glWinX, glWinY, tmp[0], tmp[1], tmp[2], glWinX-tmp[0], glWinY-tmp[1]); + if( pmv.gluUnProject(glWinX, glWinY, tmp[2], viewport, 0, objPos, 0) ) { + // System.err.printf("Shape::winToObjCoord.X: shape %d: win [%d, %d, %7.2ff] -> obj [%15.10ff, %15.10ff, %15.10ff]%n", getName(), glWinX, glWinY, tmp[2], objPos[0], objPos[1], objPos[2]); + return objPos; + } + } + return null; + } + + /** + * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate. + *

+ * 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)}. + *

+ * @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 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 given float[3] {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} + * @see #winToShapeCoord(PMVMatrix, int[], int, int, float[]) + * @see #winToShapeCoord(Scene, int, int, PMVMatrix, float[]) + */ + public float[/*3*/] winToShapeCoord(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.winToShapeCoord(pmv, viewport, glWinX, glWinY, objPos); + } + + /** + * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate. + *

+ * 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)}. + *

+ * @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. + * @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 given float[3] {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} + * @see #winToShapeCoord(PMVMatrix, int[], int, int, float[]) + * @see #winToShapeCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], int, int, PMVMatrix, float[]) + */ + public float[/*3*/] winToShapeCoord(final Scene scene, final int glWinX, final int glWinY, final PMVMatrix pmv, final float[/*3*/] objPos) { + return this.winToShapeCoord(scene.getPMVMatrixSetup(), scene.getViewport(), glWinX, glWinY, pmv, objPos); + } + + public float[] getColor() { + return rgbaColor; + } + + /** + * Set base color. + *

+ * Default base-color w/o color channel, will be modulated w/ pressed- and toggle color + *

+ */ + public final void setColor(final float r, final float g, final float b, final float a) { + this.rgbaColor[0] = r; + this.rgbaColor[1] = g; + this.rgbaColor[2] = b; + this.rgbaColor[3] = a; + } + + /** + * Set pressed color. + *

+ * Default pressed color-factor w/o color channel, modulated base-color. 0.75 * 1.2 = 0.9 + *

+ */ + public final void setPressedColorMod(final float r, final float g, final float b, final float a) { + this.pressedRGBAModulate[0] = r; + this.pressedRGBAModulate[1] = g; + this.pressedRGBAModulate[2] = b; + this.pressedRGBAModulate[3] = a; + } + + /** + * Set toggle-on color. + *

+ * Default toggle-on color-factor w/o color channel, modulated base-color. 0.75 * 1.13 ~ 0.85 + *

+ */ + public final void setToggleOnColorMod(final float r, final float g, final float b, final float a) { + this.toggleOnRGBAModulate[0] = r; + this.toggleOnRGBAModulate[1] = g; + this.toggleOnRGBAModulate[2] = b; + this.toggleOnRGBAModulate[3] = a; + } + + /** + * Set toggle-off color. + *

+ * Default toggle-off color-factor w/o color channel, modulated base-color. 0.75 * 0.86 ~ 0.65 + *

+ */ + public final void setToggleOffColorMod(final float r, final float g, final float b, final float a) { + this.toggleOffRGBAModulate[0] = r; + this.toggleOffRGBAModulate[1] = g; + this.toggleOffRGBAModulate[2] = b; + this.toggleOffRGBAModulate[3] = a; + } + + @Override + public final String toString() { + return getClass().getSimpleName()+"["+getSubString()+"]"; + } + + public String getSubString() { + final String pivotS; + if( !VectorUtil.isVec3Zero(rotPivot, 0, FloatUtil.EPSILON) ) { + pivotS = "pivot["+rotPivot[0]+", "+rotPivot[1]+", "+rotPivot[2]+"], "; + } else { + pivotS = ""; + } + final String scaleS; + if( !VectorUtil.isVec3Equal(scale, 0, VectorUtil.VEC3_ONE, 0, FloatUtil.EPSILON) ) { + scaleS = "scale["+scale[0]+", "+scale[1]+", "+scale[2]+"], "; + } else { + scaleS = "scale 1, "; + } + final String rotateS; + if( !rotation.isIdentity() ) { + rotateS = "rot "+rotation+", "; + } else { + rotateS = ""; + } + return "enabled "+enabled+", toggle[able "+toggleable+", state "+toggle+"], pos["+position[0]+", "+position[1]+", "+position[2]+ + "], "+pivotS+scaleS+rotateS+ + "box "+box; + } + + // + // Input + // + + public void setPressed(final boolean b) { + this.down = b; + markStateDirty(); + } + public boolean isPressed() { + return this.down; + } + + public void setToggleable(final boolean toggleable) { + this.toggleable = toggleable; + } + + /** + * Returns true if this shape is toggable, + * i.e. rendered w/ {@link #setToggleOnColorMod(float, float, float, float)} or {@link #setToggleOffColorMod(float, float, float, float)}. + */ + public boolean isToggleable() { + return toggleable; + } + public void setToggle(final boolean v) { + toggle = v; + markStateDirty(); + } + public void toggle() { + if( isToggleable() ) { + toggle = !toggle; + } + markStateDirty(); + } + public boolean isToggleOn() { return toggle; } + + /** + * Set whether this shape is draggable, + * i.e. translated by 1-pointer-click and drag. + *

+ * Default draggable is true. + *

+ */ + public void setDraggable(final boolean draggable) { + this.draggable = draggable; + } + public boolean isDraggable() { + return draggable; + } + + /** + * Set whether this shape is resizable, + * i.e. zoomed by 1-pointer-click and drag in 1/4th bottom-left and bottom-right corner. + *

+ * Default resizable is true. + *

+ */ + public void setResizable(final boolean resizable) { + this.resizable = resizable; + } + public boolean isResizable() { + return resizable; + } + + public final void addMouseListener(final MouseGestureListener l) { + if(l == null) { + return; + } + @SuppressWarnings("unchecked") + final ArrayList clonedListeners = (ArrayList) mouseListeners.clone(); + clonedListeners.add(l); + mouseListeners = clonedListeners; + } + + public final void removeMouseListener(final MouseGestureListener l) { + if (l == null) { + return; + } + @SuppressWarnings("unchecked") + final ArrayList clonedListeners = (ArrayList) mouseListeners.clone(); + clonedListeners.remove(l); + mouseListeners = clonedListeners; + } + + /** + * Combining {@link MouseListener} and {@link GestureListener} + */ + public static interface MouseGestureListener extends MouseListener, GestureListener { + } + + /** + * Convenient adapter combining dummy implementation for {@link MouseListener} and {@link GestureListener} + */ + public static abstract class MouseGestureAdapter extends MouseAdapter implements MouseGestureListener { + @Override + public void gestureDetected(final GestureEvent gh) { + } + } + + /** + * {@link Shape} event info for propagated {@link NEWTEvent}s + * containing reference of {@link #shape the intended shape} as well as + * the {@link #objPos rotated relative position} to this shape. + * The latter is normalized to bottom-left zero origin, allowing easier usage. + */ + public static class EventInfo { + /** The associated {@link Shape} for this event */ + public final Shape shape; + /** The relative object coordinate of glWinX/glWinY to the associated {@link Shape}. */ + public final float[] objPos; + /** The GL window coordinates, origin bottom-left */ + public final int[] winPos; + /** The drag delta of the relative object coordinate of glWinX/glWinY to the associated {@link Shape}. */ + public final float[] objDrag = { 0f, 0f }; + /** The drag delta of GL window coordinates, origin bottom-left */ + public final int[] winDrag = { 0, 0 }; + + /** + * Ctor + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param shape associated shape + * @param objPos relative object coordinate of glWinX/glWinY to the associated shape. + */ + EventInfo(final int glWinX, final int glWinY, final Shape shape, final float[] objPos) { + this.winPos = new int[] { glWinX, glWinY }; + this.shape = shape; + this.objPos = objPos; + } + + @Override + public String toString() { + return "EventDetails[winPos ["+winPos[0]+", "+winPos[1]+"], objPos ["+objPos[0]+", "+objPos[1]+", "+objPos[2]+"], "+shape+"]"; + } + } + + private boolean dragFirst = false; + private final float[] objDraggedFirst = { 0f, 0f }; // b/c its relative to Shape and we stick to it + private final int[] winDraggedLast = { 0, 0 }; // b/c its absolute window pos + private boolean inDrag = false; + private int inResize = 0; // 1 br, 2 bl + private static final float resize_sxy_min = 1f/200f; // 1/2% - TODO: Maybe customizable? + private static final float resize_section = 1f/5f; // resize action in a corner + + /** + * Dispatch given NEWT mouse event to this shape + * @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 objPos object position of mouse event relative to this shape + */ + /* pp */ final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY, final float[] objPos) { + final Shape.EventInfo shapeEvent = new EventInfo(glWinX, glWinY, this, objPos); + + final short eventType = e.getEventType(); + if( 1 == e.getPointerCount() ) { + switch( eventType ) { + case MouseEvent.EVENT_MOUSE_CLICKED: + toggle(); + break; + case MouseEvent.EVENT_MOUSE_PRESSED: + dragFirst = true; + setPressed(true); + break; + case MouseEvent.EVENT_MOUSE_RELEASED: + // Release active shape: last pointer has been lifted! + setPressed(false); + inDrag = false; + inResize = 0; + break; + } + } + switch( eventType ) { + case MouseEvent.EVENT_MOUSE_DRAGGED: { + // 1 pointer drag and potential drag-resize + if(dragFirst) { + objDraggedFirst[0] = objPos[0]; + objDraggedFirst[1] = objPos[1]; + winDraggedLast[0] = glWinX; + winDraggedLast[1] = glWinY; + dragFirst=false; + + final float ix = objPos[0]; + final float iy = objPos[1]; + final float minx_br = box.getMaxX() - box.getWidth() * resize_section; + final float miny_br = box.getMinY(); + final float maxx_br = box.getMaxX(); + final float maxy_br = box.getMinY() + box.getHeight() * resize_section; + if( minx_br <= ix && ix <= maxx_br && + miny_br <= iy && iy <= maxy_br ) { + inResize = 1; // bottom-right + } else { + final float minx_bl = box.getMinX(); + final float miny_bl = box.getMinY(); + final float maxx_bl = box.getMinX() + box.getWidth() * resize_section; + final float maxy_bl = box.getMinY() + box.getHeight() * resize_section; + if( minx_bl <= ix && ix <= maxx_bl && + miny_bl <= iy && iy <= maxy_bl ) { + inResize = 2; // bottom-left + } else { + inDrag = true; + } + } + if( DEBUG ) { + System.err.printf("DragFirst: drag %b, resize %d, obj[%.4f, %.4f, %.4f], drag +[%.4f, %.4f]%n", + inDrag, inResize, objPos[0], objPos[1], objPos[2], shapeEvent.objDrag[0], shapeEvent.objDrag[1]); + System.err.printf("DragFirst: %s%n", this); + } + return; + } + shapeEvent.objDrag[0] = objPos[0] - objDraggedFirst[0]; + shapeEvent.objDrag[1] = objPos[1] - objDraggedFirst[1]; + shapeEvent.winDrag[0] = glWinX - winDraggedLast[0]; + shapeEvent.winDrag[1] = glWinY - winDraggedLast[1]; + winDraggedLast[0] = glWinX; + winDraggedLast[1] = glWinY; + if( 1 == e.getPointerCount() ) { + final float sdx = shapeEvent.objDrag[0] * scale[0]; // apply scale, since operation + final float sdy = shapeEvent.objDrag[1] * scale[1]; // is from a scaled-model-viewpoint + if( 0 != inResize && resizable ) { + final float bw = box.getWidth(); + final float bh = box.getHeight(); + final float sx; + if( 1 == inResize ) { + sx = scale[0] + sdx/bw; // bottom-right + } else { + sx = scale[0] - sdx/bw; // bottom-left + } + final float sy = scale[1] - sdy/bh; + if( resize_sxy_min <= sx && resize_sxy_min <= sy ) { // avoid scale flip + if( DEBUG ) { + System.err.printf("DragZoom: resize %d, win[%4d, %4d], obj[%.4f, %.4f, %.4f], dxy +[%.4f, %.4f], sdxy +[%.4f, %.4f], scale [%.4f, %.4f] -> [%.4f, %.4f]%n", + inResize, glWinX, glWinY, objPos[0], objPos[1], objPos[2], + shapeEvent.objDrag[0], shapeEvent.objDrag[1], sdx, sdy, + scale[0], scale[1], sx, sy); + } + if( 1 == inResize ) { + move( 0, sdy, 0f); // bottom-right, sticky left- and top-edge + } else { + move( sdx, sdy, 0f); // bottom-left, sticky right- and top-edge + } + setScale(sx, sy, scale[2]); + } + return; // FIXME: pass through event? Issue zoom event? + } else if( inDrag && draggable ) { + if( DEBUG ) { + System.err.printf("DragMove: win[%4d, %4d] +[%2d, %2d], obj[%.4f, %.4f, %.4f] +[%.4f, %.4f]%n", + glWinX, glWinY, shapeEvent.winDrag[0], shapeEvent.winDrag[1], + objPos[0], objPos[1], objPos[2], shapeEvent.objDrag[0], shapeEvent.objDrag[1]); + } + move( sdx, sdy, 0f); + // FIXME: Pass through event? Issue move event? + } + } + } + break; + } + e.setAttachment(shapeEvent); + + for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { + final MouseGestureListener l = mouseListeners.get(i); + switch( eventType ) { + case MouseEvent.EVENT_MOUSE_CLICKED: + l.mouseClicked(e); + break; + case MouseEvent.EVENT_MOUSE_ENTERED: + l.mouseEntered(e); + break; + case MouseEvent.EVENT_MOUSE_EXITED: + l.mouseExited(e); + break; + case MouseEvent.EVENT_MOUSE_PRESSED: + l.mousePressed(e); + break; + case MouseEvent.EVENT_MOUSE_RELEASED: + l.mouseReleased(e); + break; + case MouseEvent.EVENT_MOUSE_MOVED: + l.mouseMoved(e); + break; + case MouseEvent.EVENT_MOUSE_DRAGGED: + l.mouseDragged(e); + break; + case MouseEvent.EVENT_MOUSE_WHEEL_MOVED: + l.mouseWheelMoved(e); + break; + default: + throw new NativeWindowException("Unexpected mouse event type " + e.getEventType()); + } + } + } + + /** + * @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 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 int winX2 = glWinX + Math.round(pixels); + final float[] objPos2 = winToShapeCoord(pmv, viewport, winX2, glWinY, new float[3]); + if( null == objPos2 ) { + return; + } + final float dx = objPos2[0]; + final float dy = objPos2[1]; + final float sx = scale[0] + ( dx/box.getWidth() ); // bottom-right + final float sy = scale[1] + ( dy/box.getHeight() ); + if( DEBUG ) { + System.err.printf("DragZoom: resize %b, obj %4d/%4d, %.3f/%.3f/%.3f %.3f/%.3f/%.3f + %.3f/%.3f -> %.3f/%.3f%n", + inResize, glWinX, glWinY, objPos[0], objPos[1], objPos[2], position[0], position[1], position[2], + dx, dy, sx, sy); + } + if( 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], + dx, dy, sx, sy); + } + // move(dx, dy, 0f); + setScale(sx, sy, scale[2]); + } + return; // FIXME: pass through event? Issue zoom event? + } + final Shape.EventInfo shapeEvent = new EventInfo(glWinX, glWinY, this, objPos); + e.setAttachment(shapeEvent); + + for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { + mouseListeners.get(i).gestureDetected(e); + } + } + + // + // + // + + protected abstract void validateImpl(final GLProfile glp, final GL2ES2 gl); + + protected abstract void drawImpl0(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount, float[] rgba); + + protected abstract void clearImpl0(final GL2ES2 gl, final RegionRenderer renderer); + + protected abstract void destroyImpl0(final GL2ES2 gl, final RegionRenderer renderer); + + /** + * Returns true if implementation uses an extra color channel or texture + * which will be modulated with the passed rgba color {@link #drawImpl0(GL2ES2, RegionRenderer, int[], float[])}. + * + * Otherwise the base color will be modulated and passed to {@link #drawImpl0(GL2ES2, RegionRenderer, int[], float[])}. + */ + public abstract boolean hasColorChannel(); + + // + // + // +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/GraphShape.java b/src/graphui/classes/com/jogamp/graph/ui/gl/GraphShape.java deleted file mode 100644 index f2c60a12c..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/GraphShape.java +++ /dev/null @@ -1,200 +0,0 @@ -/** - * 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.gl; - -import java.util.ArrayList; -import java.util.List; - -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.geom.Vertex; -import com.jogamp.graph.geom.Vertex.Factory; -import com.jogamp.opengl.GL2ES2; -import com.jogamp.opengl.GLProfile; -import com.jogamp.opengl.math.geom.AABBox; -import com.jogamp.opengl.util.texture.TextureSequence; - -/** - * Graph based {@link GLRegion} UI {@link Shape} - *

- * GraphUI is GPU based and resolution independent. - *

- *

- * GraphUI is intended to become an immediate- and retained-mode API. - *

- * @see Scene - */ -public abstract class GraphShape extends Shape { - protected final Factory vertexFactory; - - protected final int renderModes; - protected GLRegion region = null; - protected float oshapeSharpness = OutlineShape.DEFAULT_SHARPNESS; - private int regionQuality = Region.MAX_QUALITY; - private final List dirtyRegions = new ArrayList(); - - /** - * Create a Graph based {@link GLRegion} UI {@link Shape}. - * - * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. - */ - public GraphShape(final int renderModes) { - super(); - this.vertexFactory = OutlineShape.getDefaultVertexFactory(); - this.renderModes = renderModes; - } - - /** Return Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. */ - public final int getRenderModes() { return renderModes; } - - public final int getQuality() { return regionQuality; } - public final void setQuality(final int q) { - this.regionQuality = q; - if( null != region ) { - region.setQuality(q); - } - } - public final void setSharpness(final float sharpness) { - this.oshapeSharpness = sharpness; - markShapeDirty(); - } - public final float getSharpness() { - return oshapeSharpness; - } - - @Override - public boolean hasColorChannel() { - return Region.hasColorChannel(renderModes) || Region.hasColorTexture(renderModes); - } - - private final void clearDirtyRegions(final GL2ES2 gl) { - for(final GLRegion r : dirtyRegions) { - r.destroy(gl); - } - dirtyRegions.clear(); - } - - @Override - protected final void clearImpl0(final GL2ES2 gl, final RegionRenderer renderer) { - clearImpl(gl, renderer); - clearDirtyRegions(gl); - if( null != region ) { - region.clear(gl); - } - } - - @Override - protected final void destroyImpl0(final GL2ES2 gl, final RegionRenderer renderer) { - destroyImpl(gl, renderer); - clearDirtyRegions(gl); - if( null != region ) { - region.destroy(gl); - region = null; - } - } - - @Override - protected final void drawImpl0(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount, final float[] rgba) { - if( null != rgba ) { - renderer.getRenderState().setColorStatic(rgba); - } - region.draw(gl, renderer, sampleCount); - } - - protected GLRegion createGLRegion(final GLProfile glp) { - return GLRegion.create(glp, renderModes, null); - } - - @Override - protected final void validateImpl(final GLProfile glp, final GL2ES2 gl) { - if( null != gl ) { - clearDirtyRegions(gl); - } - if( isShapeDirty() || null == region ) { - if( null == region ) { - region = createGLRegion(glp); - } else if( null == gl ) { - dirtyRegions.add(region); - region = createGLRegion(glp); - } else { - region.clear(gl); - } - addShapeToRegion(); - if( hasDebugBox() ) { - addDebugOutline(); - } - region.setQuality(regionQuality); - } else if( isStateDirty() ) { - region.markStateDirty(); - } - } - - private final float[] dbgColor = {0.3f, 0.3f, 0.3f, 0.5f}; - - protected void addDebugOutline() { - final OutlineShape shape = new OutlineShape(vertexFactory); - final float x1 = box.getMinX(); - final float x2 = box.getMaxX(); - final float y1 = box.getMinY(); - final float y2 = box.getMaxY(); - final float z = box.getCenter()[2]; // 0; // box.getMinZ() + 0.025f; - { - // Outer OutlineShape as Winding.CCW. - shape.moveTo(x1, y1, z); - shape.lineTo(x2, y1, z); - shape.lineTo(x2, y2, z); - shape.lineTo(x1, y2, z); - shape.lineTo(x1, y1, z); - shape.closeLastOutline(true); - shape.addEmptyOutline(); - } - { - // Inner OutlineShape as Winding.CW. - final float dxy0 = box.getWidth() < box.getHeight() ? box.getWidth() : box.getHeight(); - final float dxy = dxy0 * getDebugBox(); - shape.moveTo(x1+dxy, y1+dxy, z); - shape.lineTo(x1+dxy, y2-dxy, z); - shape.lineTo(x2-dxy, y2-dxy, z); - shape.lineTo(x2-dxy, y1+dxy, z); - shape.lineTo(x1+dxy, y1+dxy, z); - shape.closeLastOutline(true); - } - shape.setIsQuadraticNurbs(); - shape.setSharpness(oshapeSharpness); - region.addOutlineShape(shape, null, dbgColor); - } - - protected void clearImpl(final GL2ES2 gl, final RegionRenderer renderer) { } - - protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { } - - protected abstract void addShapeToRegion(); - -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java b/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java deleted file mode 100644 index 3326f9e97..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java +++ /dev/null @@ -1,1073 +0,0 @@ -/** - * 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.gl; - -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 - *

- * GraphUI is GPU based and resolution independent. - *

- *

- * GraphUI is intended to become an immediate- and retained-mode API. - *

- *

- * 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)} - *

- *

- * {@link #setPMVMatrixSetup(PMVMatrixSetup)} maybe used to provide a custom {@link PMVMatrix} setup. - *

- * @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 shapes = new ArrayList(); - 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 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 shapes) { - for(final Shape s : shapes) { - addShape(s); - } - } - /** Removes all given shapes, keeps them alive. */ - public void removeShapes(final Collection shapes) { - for(final Shape s : shapes) { - removeShape(s); - } - } - /** Removes all given shapes and destroys them. */ - public void removeShapes(final GL2ES2 gl, final Collection 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 - * {@inheritDoc} - *

- * @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 shapeZAscComparator = new Comparator() { - @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 - * Implementation also issues {@link RegionRenderer#destroy(GL2ES2)} if set - * and {@link #detachInputListenerFrom(GLWindow)} in case the drawable is of type {@link GLWindow}. - *

- *

- * {@inheritDoc} - *

- */ - @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 - * If {@link Shape} was found the given action is performed. - *

- *

- * Method performs on current thread and returns after probing every {@link Shape}. - *

- * @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. - *

- * If {@link Shape} was found the given action is performed on the rendering thread. - *

- *

- * Method is non blocking and performs on rendering-thread, it returns immediately. - *

- * @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}. - *

- * At the end of operations, the {@link GLMatrixFunc#GL_MODELVIEW} matrix has to be selected. - *

- *

- * Implementation is being called by {@link Scene#setupMatrix(PMVMatrix, int, int, int, int)} - * and hence {@link Scene#reshape(GLAutoDrawable, int, int, int, int)}. - *

- *

- * Custom implementations can be set via {@link Scene#setPMVMatrixSetup(PMVMatrixSetup)}. - *

- *

- * The default implementation is described below: - *

    - *
  • {@link GLMatrixFunc#GL_PROJECTION} Matrix - *
      - *
    • Identity
    • - *
    • Perspective {@link Scene#DEFAULT_ANGLE} with {@link Scene#DEFAULT_ZNEAR} and {@link Scene#DEFAULT_ZFAR}
    • - *
    • Translated to given {@link Scene#DEFAULT_SCENE_DIST}
    • - *
    • Scale (back) to have normalized {@link Scene#getBounds() plane dimensions}, 1 for the greater of width and height.
    • - *
  • - *
  • {@link GLMatrixFunc#GL_MODELVIEW} Matrix - *
      - *
    • identity
    • - *
  • - *
- *

- */ - public static interface PMVMatrixSetup { - /** - * Setup {@link PMVMatrix}'s {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW}. - *

- * See {@link PMVMatrixSetup} for details. - *

- *

- * At the end of operations, the {@link GLMatrixFunc#GL_MODELVIEW} matrix is selected. - *

- * @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. - *

- * Will be called by {@link Scene#reshape(GLAutoDrawable, int, int, int, int)} after {@link #set(PMVMatrix, int, int, int, int)}. - *

- * @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. - *

- * The value is evaluated at {@link #reshape(GLAutoDrawable, int, int, int, int)} via {@link } - *

- *

- * {@link AABBox#getWidth()} and {@link AABBox#getHeight()} define scene's dimension covered by surface size. - *

- *

- * {@link AABBox} is setup via {@link #getPMVMatrixSetup()}'s {@link PMVMatrixSetup#setPlaneBox(AABBox, PMVMatrix, int, int, int, int)}. - *

- *

- * The default {@link PMVMatrixSetup} implementation scales to normalized plane dimensions, 1 for the greater of width and height. - *

- */ - 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; -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java b/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java deleted file mode 100644 index 7d6f139ba..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java +++ /dev/null @@ -1,1191 +0,0 @@ -/** - * 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.gl; - -import java.util.ArrayList; - -import com.jogamp.nativewindow.NativeWindowException; -import com.jogamp.opengl.GL2ES2; -import com.jogamp.opengl.GLProfile; -import com.jogamp.opengl.fixedfunc.GLMatrixFunc; -import com.jogamp.graph.curve.opengl.RegionRenderer; -import com.jogamp.newt.event.GestureHandler.GestureEvent; -import com.jogamp.newt.event.GestureHandler.GestureListener; -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.MouseListener; -import com.jogamp.opengl.math.FloatUtil; -import com.jogamp.opengl.math.Quaternion; -import com.jogamp.opengl.math.VectorUtil; -import com.jogamp.opengl.math.geom.AABBox; -import com.jogamp.opengl.util.PMVMatrix; - -/** - * Generic UI Shape, potentially using a Graph via {@link GraphShape} or other means of representing content. - *

- * A shape includes the following build-in user-interactions - * - drag shape w/ 1-pointer click, see {@link #setDraggable(boolean)} - * - resize shape w/ 1-pointer click and drag in 1/4th bottom-left and bottom-right corner, see {@link #setResizable(boolean)}. - *

- *

- * A shape is expected to have its 0/0 origin in its bottom-left corner, otherwise the drag-zoom sticky-edge will not work as expected. - *

- *

- * GraphUI is GPU based and resolution independent. - *

- *

- * GraphUI is intended to become an immediate- and retained-mode API. - *

- * @see Scene - */ -public abstract class Shape { - public static interface Listener { - void run(final Shape shape); - } - protected static final boolean DEBUG_DRAW = false; - private static final boolean DEBUG = false; - - private static final int DIRTY_SHAPE = 1 << 0 ; - private static final int DIRTY_STATE = 1 << 1 ; - - protected final AABBox box; - - private final float[] position = new float[] { 0f, 0f, 0f }; - private final Quaternion rotation = new Quaternion(); - private final float[] rotPivot = new float[] { 0f, 0f, 0f }; - private final float[] scale = new float[] { 1f, 1f, 1f }; - - private volatile int dirty = DIRTY_SHAPE | DIRTY_STATE; - private final Object dirtySync = new Object(); - - /** Default base-color w/o color channel, will be modulated w/ pressed- and toggle color */ - protected final float[] rgbaColor = {0.75f, 0.75f, 0.75f, 1.0f}; - /** Default pressed color-factor w/o color channel, modulated base-color. 0.75 * 1.2 = 0.9 */ - protected final float[] pressedRGBAModulate = {1.2f, 1.2f, 1.2f, 0.7f}; - /** Default toggle color-factor w/o color channel, modulated base-color. 0.75 * 1.13 ~ 0.85 */ - protected final float[] toggleOnRGBAModulate = {1.13f, 1.13f, 1.13f, 1.0f}; - /** Default toggle color-factor w/o color channel, modulated base-color. 0.75 * 0.86 ~ 0.65 */ - protected final float[] toggleOffRGBAModulate = {0.86f, 0.86f, 0.86f, 1.0f}; - - private int name = -1; - - private boolean down = false; - private boolean toggle = false; - private boolean toggleable = false; - private boolean draggable = true; - private boolean resizable = true; - private boolean enabled = true; - private float dbgbox_thickness = 0f; // fractional thickness of bounds, 0f for no debug box - private ArrayList mouseListeners = new ArrayList(); - - private Listener onMoveListener = null; - - public Shape() { - this.box = new AABBox(); - } - - /** Set a symbolic name for this shape for identification. Default is -1 for noname. */ - public final void setName(final int name) { this.name = name; } - /** Return the optional symbolic name for this shape. */ - public final int getName() { return this.name; } - - /** Returns true if this shape is enabled and hence visible, otherwise false. */ - public final boolean isEnabled() { return enabled; } - /** Enable or disable this shape, i.e. its visibility. */ - public final void setEnabled(final boolean v) { enabled = v; } - - /** - * Sets the {@link #getBounds()} fractional thickness of the debug box ranging [0..1], 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 = Math.min(1f, Math.max(0f, v)); - } - /** Returns true if a debug box has been enabled via {@link #setDebugBox(float)}. */ - public final boolean hasDebugBox() { return !FloatUtil.isZero(dbgbox_thickness); } - /** Returns the fractional thickness of the debug box ranging [0..1], see {@link #setDebugBox(float)}. */ - public final float getDebugBox() { return dbgbox_thickness; } - - /** - * Clears all data and reset all states as if this instance was newly created - * @param gl TODO - * @param renderer TODO - */ - public final void clear(final GL2ES2 gl, final RegionRenderer renderer) { - synchronized ( dirtySync ) { - clearImpl0(gl, renderer); - position[0] = 0f; - position[1] = 0f; - position[2] = 0f; - rotation.setIdentity(); - rotPivot[0] = 0f; - rotPivot[1] = 0f; - rotPivot[2] = 0f; - scale[0] = 1f; - scale[1] = 1f; - scale[2] = 1f; - box.reset(); - markShapeDirty(); - } - } - - /** - * Destroys all data - * @param gl - * @param renderer - */ - public final void destroy(final GL2ES2 gl, final RegionRenderer renderer) { - destroyImpl0(gl, renderer); - position[0] = 0f; - position[1] = 0f; - position[2] = 0f; - rotation.setIdentity(); - rotPivot[0] = 0f; - rotPivot[1] = 0f; - rotPivot[2] = 0f; - scale[0] = 1f; - scale[1] = 1f; - scale[2] = 1f; - box.reset(); - markShapeDirty(); - } - - public final void onMove(final Listener l) { onMoveListener = l; } - - /** Move to scaled position. Position ends up in PMVMatrix unmodified. */ - public final void moveTo(final float tx, final float ty, final float tz) { - position[0] = tx; - position[1] = ty; - position[2] = tz; - if( null != onMoveListener ) { - onMoveListener.run(this); - } - // System.err.println("Shape.setTranslate: "+tx+"/"+ty+"/"+tz+": "+toString()); - } - - /** Move about scaled distance. Position ends up in PMVMatrix unmodified. */ - public final void move(final float dtx, final float dty, final float dtz) { - position[0] += dtx; - position[1] += dty; - position[2] += dtz; - if( null != onMoveListener ) { - onMoveListener.run(this); - } - // System.err.println("Shape.translate: "+tx+"/"+ty+"/"+tz+": "+toString()); - } - - /** Returns float[3] position, i.e. scaled translation as set via {@link #moveTo(float, float, float) or {@link #move(float, float, float)}}. */ - public final float[] getPosition() { return position; } - - /** Returns {@link Quaternion} for rotation. */ - public final Quaternion getRotation() { return rotation; } - /** Return float[3] unscaled rotation origin, aka pivot. */ - public final float[] getRotationPivot() { return rotPivot; } - /** Set unscaled rotation origin, aka pivot. Usually the {@link #getBounds()} center and should be set while {@link #validateImpl(GLProfile, GL2ES2)}. */ - public final void setRotationPivot(final float rx, final float ry, final float rz) { - rotPivot[0] = rx; - rotPivot[1] = ry; - rotPivot[2] = rz; - } - /** - * Set unscaled rotation origin, aka pivot. Usually the {@link #getBounds()} center and should be set while {@link #validateImpl(GLProfile, GL2ES2)}. - * @param pivot float[3] rotation origin - */ - public final void setRotationPivot(final float[/*3*/] pivot) { - System.arraycopy(pivot, 0, rotPivot, 0, 3); - } - - /** - * Set scale factor to given scale. - * @see #scale(float, float, float) - * @see #getScale() - */ - public final void setScale(final float sx, final float sy, final float sz) { - scale[0] = sx; - scale[1] = sy; - scale[2] = sz; - } - /** - * Multiply current scale factor by given scale. - * @see #setScale(float, float, float) - * @see #getScale() - */ - public final void scale(final float sx, final float sy, final float sz) { - scale[0] *= sx; - scale[1] *= sy; - scale[2] *= sz; - } - /** - * Returns float[3] scale factors. - * @see #setScale(float, float, float) - * @see #scale(float, float, float) - */ - public final float[] getScale() { return scale; } - /** Returns X-axis scale factor. */ - public final float getScaleX() { return scale[0]; } - /** Returns Y-axis scale factor. */ - public final float getScaleY() { return scale[1]; } - /** Returns Z-axis scale factor. */ - public final float getScaleZ() { return scale[2]; } - - /** - * Marks the shape dirty, causing next {@link #draw(GL2ES2, RegionRenderer, int[]) draw()} - * to recreate the Graph shape and reset the region. - */ - public final void markShapeDirty() { - synchronized ( dirtySync ) { - dirty |= DIRTY_SHAPE; - } - } - - /** - * Marks the rendering state dirty, causing next {@link #draw(GL2ES2, RegionRenderer, int[]) draw()} - * to notify the Graph region to reselect shader and repaint potentially used FBOs. - */ - public final void markStateDirty() { - synchronized ( dirtySync ) { - dirty |= DIRTY_STATE; - } - } - - protected final boolean isShapeDirty() { - return 0 != ( dirty & DIRTY_SHAPE ) ; - } - protected final boolean isStateDirty() { - return 0 != ( dirty & DIRTY_STATE ) ; - } - - /** - * Returns the unscaled bounding {@link AABBox} for this shape, borrowing internal instance. - * - * The returned {@link AABBox} will only cover this unscaled shape - * after an initial call to {@link #draw(GL2ES2, RegionRenderer, int[]) draw(..)} - * or {@link #validate(GL2ES2)}. - * - * @see #getBounds(GLProfile) - */ - public final AABBox getBounds() { return box; } - - /** - * Returns the scaled width of the bounding {@link AABBox} for this shape. - * - * The returned width will only cover the scaled shape - * after an initial call to {@link #draw(GL2ES2, RegionRenderer, int[]) draw(..)} - * or {@link #validate(GL2ES2)}. - * - * @see #getBounds() - */ - public final float getScaledWidth() { - return box.getWidth() * getScaleX(); - } - - /** - * Returns the scaled height of the bounding {@link AABBox} for this shape. - * - * The returned height will only cover the scaled shape - * after an initial call to {@link #draw(GL2ES2, RegionRenderer, int[]) draw(..)} - * or {@link #validate(GL2ES2)}. - * - * @see #getBounds() - */ - public final float getScaledHeight() { - return box.getHeight() * getScaleY(); - } - - /** - * 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) { - validate(glp); - return box; - } - - /** Experimental selection draw command used by {@link Scene}. */ - public void drawToSelect(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { - synchronized ( dirtySync ) { - validate(gl); - drawImpl0(gl, renderer, sampleCount, null); - } - } - - private final float[] rgba_tmp = { 0, 0, 0, 1 }; - - /** - * Renders the shape. - *

- * {@link #setTransform(PMVMatrix)} is expected to be completed beforehand. - *

- * @param gl the current GL object - * @param renderer {@link RegionRenderer} which might be used for Graph Curve Rendering, also source of {@link RegionRenderer#getMatrix()} and {@link RegionRenderer#getViewport()}. - * @param sampleCount sample count if used by Graph renderModes - */ - public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { - final boolean isPressed = isPressed(), isToggleOn = isToggleOn(); - final float[] rgba; - if( hasColorChannel() ) { - if( isPressed ) { - rgba = pressedRGBAModulate; - } else if( isToggleable() ) { - if( isToggleOn ) { - rgba = toggleOnRGBAModulate; - } else { - rgba = toggleOffRGBAModulate; - } - } else { - rgba = rgbaColor; - } - } else { - rgba = rgba_tmp; - if( isPressed ) { - rgba[0] = rgbaColor[0]*pressedRGBAModulate[0]; - rgba[1] = rgbaColor[1]*pressedRGBAModulate[1]; - rgba[2] = rgbaColor[2]*pressedRGBAModulate[2]; - rgba[3] = rgbaColor[3]*pressedRGBAModulate[3]; - } else if( isToggleable() ) { - if( isToggleOn ) { - rgba[0] = rgbaColor[0]*toggleOnRGBAModulate[0]; - rgba[1] = rgbaColor[1]*toggleOnRGBAModulate[1]; - rgba[2] = rgbaColor[2]*toggleOnRGBAModulate[2]; - rgba[3] = rgbaColor[3]*toggleOnRGBAModulate[3]; - } else { - rgba[0] = rgbaColor[0]*toggleOffRGBAModulate[0]; - rgba[1] = rgbaColor[1]*toggleOffRGBAModulate[1]; - rgba[2] = rgbaColor[2]*toggleOffRGBAModulate[2]; - rgba[3] = rgbaColor[3]*toggleOffRGBAModulate[3]; - } - } else { - rgba[0] = rgbaColor[0]; - rgba[1] = rgbaColor[1]; - rgba[2] = rgbaColor[2]; - rgba[3] = rgbaColor[3]; - } - } - synchronized ( dirtySync ) { - validate(gl); - drawImpl0(gl, renderer, sampleCount, rgba); - } - } - - /** - * Validates the shape's underlying {@link GLRegion}. - *

- * If the region is dirty, it gets {@link GLRegion#clear(GL2ES2) cleared} and is reused. - *

- * @param gl current {@link GL2ES2} object - * @see #validate(GLProfile) - */ - public final void validate(final GL2ES2 gl) { - synchronized ( dirtySync ) { - if( isShapeDirty() ) { - box.reset(); - } - validateImpl(gl.getGLProfile(), gl); - dirty = 0; - } - } - - /** - * Validates the shape's underlying {@link GLRegion} w/o a current {@link GL2ES2} object - *

- * If the region is dirty a new region is created - * and the old one gets pushed to a dirty-list to get disposed when a GL context is available. - *

- * @see #validate(GL2ES2) - */ - public final void validate(final GLProfile glp) { - synchronized ( dirtySync ) { - if( isShapeDirty() ) { - box.reset(); - } - validateImpl(glp, null); - dirty = 0; - } - } - - /** - * Setup the pre-selected {@link GLMatrixFunc#GL_MODELVIEW} {@link PMVMatrix} for this object. - * - Scale shape from its center position - * - Rotate shape around optional scaled pivot, see {@link #setRotationPivot(float[])}), otherwise rotate around its scaled center (default) - *

- * Shape's origin should be bottom-left @ 0/0 to have build-in drag-zoom work properly. - *

- * @param pmv the matrix - * @see #setRotationPivot(float[]) - * @see #getRotation() - * @see #moveTo(float, float, float) - * @see #setScale(float, float, float) - */ - public void setTransform(final PMVMatrix pmv) { - final boolean hasScale = !VectorUtil.isVec3Equal(scale, 0, VectorUtil.VEC3_ONE, 0, FloatUtil.EPSILON); - final boolean hasRotate = !rotation.isIdentity(); - final boolean hasRotPivot = !VectorUtil.isVec3Zero(rotPivot, 0, FloatUtil.EPSILON); - final float[] ctr = box.getCenter(); - final boolean sameScaleRotatePivot = hasScale && hasRotate && ( !hasRotPivot || VectorUtil.isVec3Equal(rotPivot, 0, ctr, 0, FloatUtil.EPSILON) ); - - pmv.glTranslatef(position[0], position[1], position[2]); // translate, scaled - - if( sameScaleRotatePivot ) { - // Scale shape from its center position and rotate around its center - pmv.glTranslatef(ctr[0]*scale[0], ctr[1]*scale[1], ctr[2]*scale[2]); // add-back center, scaled - pmv.glRotate(rotation); - pmv.glScalef(scale[0], scale[1], scale[2]); - pmv.glTranslatef(-ctr[0], -ctr[1], -ctr[2]); // move to center - } else if( hasRotate || hasScale ) { - if( hasRotate ) { - if( hasRotPivot ) { - // Rotate shape around its scaled pivot - pmv.glTranslatef(rotPivot[0]*scale[0], rotPivot[1]*scale[1], rotPivot[2]*scale[2]); // pivot back from rot-pivot, scaled - pmv.glRotate(rotation); - pmv.glTranslatef(-rotPivot[0]*scale[0], -rotPivot[1]*scale[1], -rotPivot[2]*scale[2]); // pivot to rot-pivot, scaled - } else { - // Rotate shape around its scaled center - pmv.glTranslatef(ctr[0]*scale[0], ctr[1]*scale[1], ctr[2]*scale[2]); // pivot back from center-pivot, scaled - pmv.glRotate(rotation); - pmv.glTranslatef(-ctr[0]*scale[0], -ctr[1]*scale[1], -ctr[2]*scale[2]); // pivot to center-pivot, scaled - } - } - if( hasScale ) { - // Scale shape from its center position - pmv.glTranslatef(ctr[0]*scale[0], ctr[1]*scale[1], ctr[2]*scale[2]); // add-back center, scaled - pmv.glScalef(scale[0], scale[1], scale[2]); - pmv.glTranslatef(-ctr[0], -ctr[1], -ctr[2]); // move to center - } - } - // TODO: Add alignment features. - } - - /** - * Retrieve surface (view) size of this shape. - *

- * The given {@link PMVMatrix} has to be setup properly for this object, - * i.e. its {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW} for the surrounding scene - * including this shape's {@link #setTransform(PMVMatrix)}. - *

- * @param pmv well formed {@link PMVMatrix}, e.g. could have been setup via {@link Scene#setupMatrix(PMVMatrix) setupMatrix(..)} and {@link #setTransform(PMVMatrix)}. - * @param viewport the int[4] viewport - * @param surfaceSize int[2] target surface size - * @return given int[2] {@code surfaceSize} for successful gluProject(..) operation, otherwise {@code null} - * @see #getSurfaceSize(com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup, int[], PMVMatrix, int[]) - * @see #getSurfaceSize(Scene, PMVMatrix, int[]) - */ - public int[/*2*/] getSurfaceSize(final PMVMatrix pmv, final int[/*4*/] viewport, final int[/*2*/] surfaceSize) { - // System.err.println("Shape::getSurfaceSize.VP "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]); - final float[] winCoordHigh = new float[3]; - final float[] winCoordLow = new float[3]; - final float[] high = getBounds().getHigh(); - final float[] low = getBounds().getLow(); - - if( pmv.gluProject(high[0], high[1], high[2], viewport, 0, winCoordHigh, 0) ) { - // System.err.printf("Shape::surfaceSize.H: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), high[0], high[1], high[2], winCoordHigh[0], winCoordHigh[1], winCoordHigh[2]); - if( pmv.gluProject(low[0], low[1], low[2], viewport, 0, winCoordLow, 0) ) { - // System.err.printf("Shape::surfaceSize.L: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), low[0], low[1], low[2], winCoordLow[0], winCoordLow[1], winCoordLow[2]); - surfaceSize[0] = (int)(winCoordHigh[0] - winCoordLow[0]); - surfaceSize[1] = (int)(winCoordHigh[1] - winCoordLow[1]); - // System.err.printf("Shape::surfaceSize.S: shape %d: %f x %f -> %d x %d%n", getName(), winCoordHigh[0] - winCoordLow[0], winCoordHigh[1] - winCoordLow[1], surfaceSize[0], surfaceSize[1]); - return surfaceSize; - } - } - return null; - } - - /** - * Retrieve surface (view) size of this shape. - *

- * 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)}. - *

- * @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 given int[2] {@code surfaceSize} for successful gluProject(..) operation, otherwise {@code null} - * @see #getSurfaceSize(PMVMatrix, int[], int[]) - * @see #getSurfaceSize(Scene, PMVMatrix, int[]) - */ - public int[/*2*/] 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, viewport, surfaceSize); - } - - /** - * Retrieve surface (view) size of this shape. - *

- * 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)}. - *

- * @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. - * @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 given int[2] {@code surfaceSize} for successful gluProject(..) operation, otherwise {@code null} - * @see #getSurfaceSize(PMVMatrix, int[], int[]) - * @see #getSurfaceSize(com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup, int[], PMVMatrix, int[]) - */ - public int[/*2*/] getSurfaceSize(final Scene scene, final PMVMatrix pmv, final int[/*2*/] surfaceSize) { - return getSurfaceSize(scene.getPMVMatrixSetup(), scene.getViewport(), pmv, surfaceSize); - } - - /** - * Retrieve pixel per scaled shape-coordinate unit, i.e. [px]/[obj]. - *

- * 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)}. - *

- * @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. - * @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 pixPerShape float[2] pixel per scaled shape-coordinate unit result storage - * @return given float[2] {@code pixPerShape} for successful gluProject(..) operation, otherwise {@code null} - * @see #getPixelPerShapeUnit(int[], float[]) - * @see #getSurfaceSize(Scene, PMVMatrix, int[]) - * @see #getScaledWidth() - * @see #getScaledHeight() - */ - public float[] getPixelPerShapeUnit(final Scene scene, final PMVMatrix pmv, final float[] pixPerShape) { - final int[] shapeSizePx = new int[2]; - if( null != getSurfaceSize(scene, new PMVMatrix(), shapeSizePx) ) { - return getPixelPerShapeUnit(shapeSizePx, pixPerShape); - } else { - return null; - } - } - - /** - * Retrieve pixel per scaled shape-coordinate unit, i.e. [px]/[obj]. - * @param shapeSizePx int[2] shape size in pixel as retrieved via e.g. {@link #getSurfaceSize(com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup, int[], PMVMatrix, int[])} - * @param pixPerShape float[2] pixel scaled per shape-coordinate unit result storage - * @return given float[2] {@code pixPerShape} - * @see #getPixelPerShapeUnit(Scene, PMVMatrix, float[]) - * @see #getSurfaceSize(com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup, int[], PMVMatrix, int[]) - * @see #getScaledWidth() - * @see #getScaledHeight() - */ - public float[] getPixelPerShapeUnit(final int[] shapeSizePx, final float[] pixPerShape) { - pixPerShape[0] = shapeSizePx[0] / getScaledWidth(); - pixPerShape[0] = shapeSizePx[1] / getScaledHeight(); - return pixPerShape; - } - - /** - * Map given object coordinate relative to this shape to window coordinates. - *

- * The given {@link PMVMatrix} has to be setup properly for this object, - * i.e. its {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW} for the surrounding scene - * including this shape's {@link #setTransform(PMVMatrix)}. - *

- * @param pmv well formed {@link PMVMatrix}, e.g. could have been setup via {@link Scene#setupMatrix(PMVMatrix) setupMatrix(..)} and {@link #setTransform(PMVMatrix)}. - * @param viewport the int[4] viewport - * @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 given int[2] {@code glWinPos} for successful gluProject(..) operation, otherwise {@code null} - * @see #shapeToWinCoord(com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup, int[], float[], PMVMatrix, int[]) - * @see #shapeToWinCoord(Scene, float[], PMVMatrix, int[]) - */ - public int[/*2*/] shapeToWinCoord(final PMVMatrix pmv, final int[/*4*/] viewport, final float[/*3*/] objPos, final int[/*2*/] glWinPos) { - // System.err.println("Shape::objToWinCoordgetSurfaceSize.VP "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]); - final float[] winCoord = new float[3]; - - if( pmv.gluProject(objPos[0], objPos[1], objPos[2], viewport, 0, winCoord, 0) ) { - // System.err.printf("Shape::objToWinCoord.0: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), objPos[0], objPos[1], objPos[2], winCoord[0], winCoord[1], winCoord[2]); - glWinPos[0] = (int)(winCoord[0]); - glWinPos[1] = (int)(winCoord[1]); - // System.err.printf("Shape::objToWinCoord.X: shape %d: %f / %f -> %d / %d%n", getName(), winCoord[0], winCoord[1], glWinPos[0], glWinPos[1]); - return glWinPos; - } - return null; - } - - /** - * Map given object coordinate relative to this shape to window coordinates. - *

- * 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)}. - *

- * @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 given int[2] {@code glWinPos} for successful gluProject(..) operation, otherwise {@code null} - * @see #shapeToWinCoord(PMVMatrix, int[], float[], int[]) - * @see #shapeToWinCoord(Scene, float[], PMVMatrix, int[]) - */ - public int[/*2*/] shapeToWinCoord(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.shapeToWinCoord(pmv, viewport, objPos, glWinPos); - } - - /** - * Map given object coordinate relative to this shape to window coordinates. - *

- * 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)}. - *

- * @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. - * @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 given int[2] {@code glWinPos} for successful gluProject(..) operation, otherwise {@code null} - * @see #shapeToWinCoord(PMVMatrix, int[], float[], int[]) - * @see #shapeToWinCoord(com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup, int[], float[], PMVMatrix, int[]) - */ - public int[/*2*/] shapeToWinCoord(final Scene scene, final float[/*3*/] objPos, final PMVMatrix pmv, final int[/*2*/] glWinPos) { - return this.shapeToWinCoord(scene.getPMVMatrixSetup(), scene.getViewport(), objPos, pmv, glWinPos); - } - - /** - * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate. - *

- * The given {@link PMVMatrix} has to be setup properly for this object, - * i.e. its {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW} for the surrounding scene - * including this shape's {@link #setTransform(PMVMatrix)}. - *

- * @param pmv well formed {@link PMVMatrix}, e.g. could have been setup via {@link Scene#setupMatrix(PMVMatrix) setupMatrix(..)} and {@link #setTransform(PMVMatrix)}. - * @param viewport the int[4] viewport - * @param glWinX in GL window coordinates, origin bottom-left - * @param glWinY in GL window coordinates, origin bottom-left - * @param objPos float[3] target object position of glWinX/glWinY relative to this shape - * @return given float[3] {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} - * @see #winToShapeCoord(com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup, int[], int, int, PMVMatrix, float[]) - * @see #winToShapeCoord(Scene, int, int, PMVMatrix, float[]) - */ - public float[/*3*/] winToShapeCoord(final PMVMatrix pmv, final int[/*4*/] viewport, final int glWinX, final int glWinY, final float[/*3*/] objPos) { - final float[] ctr = getBounds().getCenter(); - final float[] tmp = new float[3]; - - if( pmv.gluProject(ctr[0], ctr[1], ctr[2], viewport, 0, tmp, 0) ) { - // System.err.printf("Shape::winToObjCoord.0: shape %d: obj [%15.10ff, %15.10ff, %15.10ff] -> win [%d / %d -> %7.2ff, %7.2ff, %7.2ff, diff %7.2ff x %7.2ff]%n", getName(), ctr[0], ctr[1], ctr[2], glWinX, glWinY, tmp[0], tmp[1], tmp[2], glWinX-tmp[0], glWinY-tmp[1]); - if( pmv.gluUnProject(glWinX, glWinY, tmp[2], viewport, 0, objPos, 0) ) { - // System.err.printf("Shape::winToObjCoord.X: shape %d: win [%d, %d, %7.2ff] -> obj [%15.10ff, %15.10ff, %15.10ff]%n", getName(), glWinX, glWinY, tmp[2], objPos[0], objPos[1], objPos[2]); - return objPos; - } - } - return null; - } - - /** - * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate. - *

- * 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)}. - *

- * @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 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 given float[3] {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} - * @see #winToShapeCoord(PMVMatrix, int[], int, int, float[]) - * @see #winToShapeCoord(Scene, int, int, PMVMatrix, float[]) - */ - public float[/*3*/] winToShapeCoord(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.winToShapeCoord(pmv, viewport, glWinX, glWinY, objPos); - } - - /** - * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate. - *

- * 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)}. - *

- * @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. - * @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 given float[3] {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} - * @see #winToShapeCoord(PMVMatrix, int[], int, int, float[]) - * @see #winToShapeCoord(com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup, int[], int, int, PMVMatrix, float[]) - */ - public float[/*3*/] winToShapeCoord(final Scene scene, final int glWinX, final int glWinY, final PMVMatrix pmv, final float[/*3*/] objPos) { - return this.winToShapeCoord(scene.getPMVMatrixSetup(), scene.getViewport(), glWinX, glWinY, pmv, objPos); - } - - public float[] getColor() { - return rgbaColor; - } - - /** - * Set base color. - *

- * Default base-color w/o color channel, will be modulated w/ pressed- and toggle color - *

- */ - public final void setColor(final float r, final float g, final float b, final float a) { - this.rgbaColor[0] = r; - this.rgbaColor[1] = g; - this.rgbaColor[2] = b; - this.rgbaColor[3] = a; - } - - /** - * Set pressed color. - *

- * Default pressed color-factor w/o color channel, modulated base-color. 0.75 * 1.2 = 0.9 - *

- */ - public final void setPressedColorMod(final float r, final float g, final float b, final float a) { - this.pressedRGBAModulate[0] = r; - this.pressedRGBAModulate[1] = g; - this.pressedRGBAModulate[2] = b; - this.pressedRGBAModulate[3] = a; - } - - /** - * Set toggle-on color. - *

- * Default toggle-on color-factor w/o color channel, modulated base-color. 0.75 * 1.13 ~ 0.85 - *

- */ - public final void setToggleOnColorMod(final float r, final float g, final float b, final float a) { - this.toggleOnRGBAModulate[0] = r; - this.toggleOnRGBAModulate[1] = g; - this.toggleOnRGBAModulate[2] = b; - this.toggleOnRGBAModulate[3] = a; - } - - /** - * Set toggle-off color. - *

- * Default toggle-off color-factor w/o color channel, modulated base-color. 0.75 * 0.86 ~ 0.65 - *

- */ - public final void setToggleOffColorMod(final float r, final float g, final float b, final float a) { - this.toggleOffRGBAModulate[0] = r; - this.toggleOffRGBAModulate[1] = g; - this.toggleOffRGBAModulate[2] = b; - this.toggleOffRGBAModulate[3] = a; - } - - @Override - public final String toString() { - return getClass().getSimpleName()+"["+getSubString()+"]"; - } - - public String getSubString() { - final String pivotS; - if( !VectorUtil.isVec3Zero(rotPivot, 0, FloatUtil.EPSILON) ) { - pivotS = "pivot["+rotPivot[0]+", "+rotPivot[1]+", "+rotPivot[2]+"], "; - } else { - pivotS = ""; - } - final String scaleS; - if( !VectorUtil.isVec3Equal(scale, 0, VectorUtil.VEC3_ONE, 0, FloatUtil.EPSILON) ) { - scaleS = "scale["+scale[0]+", "+scale[1]+", "+scale[2]+"], "; - } else { - scaleS = "scale 1, "; - } - final String rotateS; - if( !rotation.isIdentity() ) { - rotateS = "rot "+rotation+", "; - } else { - rotateS = ""; - } - return "enabled "+enabled+", toggle[able "+toggleable+", state "+toggle+"], pos["+position[0]+", "+position[1]+", "+position[2]+ - "], "+pivotS+scaleS+rotateS+ - "box "+box; - } - - // - // Input - // - - public void setPressed(final boolean b) { - this.down = b; - markStateDirty(); - } - public boolean isPressed() { - return this.down; - } - - public void setToggleable(final boolean toggleable) { - this.toggleable = toggleable; - } - - /** - * Returns true if this shape is toggable, - * i.e. rendered w/ {@link #setToggleOnColorMod(float, float, float, float)} or {@link #setToggleOffColorMod(float, float, float, float)}. - */ - public boolean isToggleable() { - return toggleable; - } - public void setToggle(final boolean v) { - toggle = v; - markStateDirty(); - } - public void toggle() { - if( isToggleable() ) { - toggle = !toggle; - } - markStateDirty(); - } - public boolean isToggleOn() { return toggle; } - - /** - * Set whether this shape is draggable, - * i.e. translated by 1-pointer-click and drag. - *

- * Default draggable is true. - *

- */ - public void setDraggable(final boolean draggable) { - this.draggable = draggable; - } - public boolean isDraggable() { - return draggable; - } - - /** - * Set whether this shape is resizable, - * i.e. zoomed by 1-pointer-click and drag in 1/4th bottom-left and bottom-right corner. - *

- * Default resizable is true. - *

- */ - public void setResizable(final boolean resizable) { - this.resizable = resizable; - } - public boolean isResizable() { - return resizable; - } - - public final void addMouseListener(final MouseGestureListener l) { - if(l == null) { - return; - } - @SuppressWarnings("unchecked") - final ArrayList clonedListeners = (ArrayList) mouseListeners.clone(); - clonedListeners.add(l); - mouseListeners = clonedListeners; - } - - public final void removeMouseListener(final MouseGestureListener l) { - if (l == null) { - return; - } - @SuppressWarnings("unchecked") - final ArrayList clonedListeners = (ArrayList) mouseListeners.clone(); - clonedListeners.remove(l); - mouseListeners = clonedListeners; - } - - /** - * Combining {@link MouseListener} and {@link GestureListener} - */ - public static interface MouseGestureListener extends MouseListener, GestureListener { - } - - /** - * Convenient adapter combining dummy implementation for {@link MouseListener} and {@link GestureListener} - */ - public static abstract class MouseGestureAdapter extends MouseAdapter implements MouseGestureListener { - @Override - public void gestureDetected(final GestureEvent gh) { - } - } - - /** - * {@link Shape} event info for propagated {@link NEWTEvent}s - * containing reference of {@link #shape the intended shape} as well as - * the {@link #objPos rotated relative position} to this shape. - * The latter is normalized to bottom-left zero origin, allowing easier usage. - */ - public static class EventInfo { - /** The associated {@link Shape} for this event */ - public final Shape shape; - /** The relative object coordinate of glWinX/glWinY to the associated {@link Shape}. */ - public final float[] objPos; - /** The GL window coordinates, origin bottom-left */ - public final int[] winPos; - /** The drag delta of the relative object coordinate of glWinX/glWinY to the associated {@link Shape}. */ - public final float[] objDrag = { 0f, 0f }; - /** The drag delta of GL window coordinates, origin bottom-left */ - public final int[] winDrag = { 0, 0 }; - - /** - * Ctor - * @param glWinX in GL window coordinates, origin bottom-left - * @param glWinY in GL window coordinates, origin bottom-left - * @param shape associated shape - * @param objPos relative object coordinate of glWinX/glWinY to the associated shape. - */ - EventInfo(final int glWinX, final int glWinY, final Shape shape, final float[] objPos) { - this.winPos = new int[] { glWinX, glWinY }; - this.shape = shape; - this.objPos = objPos; - } - - @Override - public String toString() { - return "EventDetails[winPos ["+winPos[0]+", "+winPos[1]+"], objPos ["+objPos[0]+", "+objPos[1]+", "+objPos[2]+"], "+shape+"]"; - } - } - - private boolean dragFirst = false; - private final float[] objDraggedFirst = { 0f, 0f }; // b/c its relative to Shape and we stick to it - private final int[] winDraggedLast = { 0, 0 }; // b/c its absolute window pos - private boolean inDrag = false; - private int inResize = 0; // 1 br, 2 bl - private static final float resize_sxy_min = 1f/200f; // 1/2% - TODO: Maybe customizable? - private static final float resize_section = 1f/5f; // resize action in a corner - - /** - * Dispatch given NEWT mouse event to this shape - * @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 objPos object position of mouse event relative to this shape - */ - /* pp */ final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY, final float[] objPos) { - final Shape.EventInfo shapeEvent = new EventInfo(glWinX, glWinY, this, objPos); - - final short eventType = e.getEventType(); - if( 1 == e.getPointerCount() ) { - switch( eventType ) { - case MouseEvent.EVENT_MOUSE_CLICKED: - toggle(); - break; - case MouseEvent.EVENT_MOUSE_PRESSED: - dragFirst = true; - setPressed(true); - break; - case MouseEvent.EVENT_MOUSE_RELEASED: - // Release active shape: last pointer has been lifted! - setPressed(false); - inDrag = false; - inResize = 0; - break; - } - } - switch( eventType ) { - case MouseEvent.EVENT_MOUSE_DRAGGED: { - // 1 pointer drag and potential drag-resize - if(dragFirst) { - objDraggedFirst[0] = objPos[0]; - objDraggedFirst[1] = objPos[1]; - winDraggedLast[0] = glWinX; - winDraggedLast[1] = glWinY; - dragFirst=false; - - final float ix = objPos[0]; - final float iy = objPos[1]; - final float minx_br = box.getMaxX() - box.getWidth() * resize_section; - final float miny_br = box.getMinY(); - final float maxx_br = box.getMaxX(); - final float maxy_br = box.getMinY() + box.getHeight() * resize_section; - if( minx_br <= ix && ix <= maxx_br && - miny_br <= iy && iy <= maxy_br ) { - inResize = 1; // bottom-right - } else { - final float minx_bl = box.getMinX(); - final float miny_bl = box.getMinY(); - final float maxx_bl = box.getMinX() + box.getWidth() * resize_section; - final float maxy_bl = box.getMinY() + box.getHeight() * resize_section; - if( minx_bl <= ix && ix <= maxx_bl && - miny_bl <= iy && iy <= maxy_bl ) { - inResize = 2; // bottom-left - } else { - inDrag = true; - } - } - if( DEBUG ) { - System.err.printf("DragFirst: drag %b, resize %d, obj[%.4f, %.4f, %.4f], drag +[%.4f, %.4f]%n", - inDrag, inResize, objPos[0], objPos[1], objPos[2], shapeEvent.objDrag[0], shapeEvent.objDrag[1]); - System.err.printf("DragFirst: %s%n", this); - } - return; - } - shapeEvent.objDrag[0] = objPos[0] - objDraggedFirst[0]; - shapeEvent.objDrag[1] = objPos[1] - objDraggedFirst[1]; - shapeEvent.winDrag[0] = glWinX - winDraggedLast[0]; - shapeEvent.winDrag[1] = glWinY - winDraggedLast[1]; - winDraggedLast[0] = glWinX; - winDraggedLast[1] = glWinY; - if( 1 == e.getPointerCount() ) { - final float sdx = shapeEvent.objDrag[0] * scale[0]; // apply scale, since operation - final float sdy = shapeEvent.objDrag[1] * scale[1]; // is from a scaled-model-viewpoint - if( 0 != inResize && resizable ) { - final float bw = box.getWidth(); - final float bh = box.getHeight(); - final float sx; - if( 1 == inResize ) { - sx = scale[0] + sdx/bw; // bottom-right - } else { - sx = scale[0] - sdx/bw; // bottom-left - } - final float sy = scale[1] - sdy/bh; - if( resize_sxy_min <= sx && resize_sxy_min <= sy ) { // avoid scale flip - if( DEBUG ) { - System.err.printf("DragZoom: resize %d, win[%4d, %4d], obj[%.4f, %.4f, %.4f], dxy +[%.4f, %.4f], sdxy +[%.4f, %.4f], scale [%.4f, %.4f] -> [%.4f, %.4f]%n", - inResize, glWinX, glWinY, objPos[0], objPos[1], objPos[2], - shapeEvent.objDrag[0], shapeEvent.objDrag[1], sdx, sdy, - scale[0], scale[1], sx, sy); - } - if( 1 == inResize ) { - move( 0, sdy, 0f); // bottom-right, sticky left- and top-edge - } else { - move( sdx, sdy, 0f); // bottom-left, sticky right- and top-edge - } - setScale(sx, sy, scale[2]); - } - return; // FIXME: pass through event? Issue zoom event? - } else if( inDrag && draggable ) { - if( DEBUG ) { - System.err.printf("DragMove: win[%4d, %4d] +[%2d, %2d], obj[%.4f, %.4f, %.4f] +[%.4f, %.4f]%n", - glWinX, glWinY, shapeEvent.winDrag[0], shapeEvent.winDrag[1], - objPos[0], objPos[1], objPos[2], shapeEvent.objDrag[0], shapeEvent.objDrag[1]); - } - move( sdx, sdy, 0f); - // FIXME: Pass through event? Issue move event? - } - } - } - break; - } - e.setAttachment(shapeEvent); - - for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { - final MouseGestureListener l = mouseListeners.get(i); - switch( eventType ) { - case MouseEvent.EVENT_MOUSE_CLICKED: - l.mouseClicked(e); - break; - case MouseEvent.EVENT_MOUSE_ENTERED: - l.mouseEntered(e); - break; - case MouseEvent.EVENT_MOUSE_EXITED: - l.mouseExited(e); - break; - case MouseEvent.EVENT_MOUSE_PRESSED: - l.mousePressed(e); - break; - case MouseEvent.EVENT_MOUSE_RELEASED: - l.mouseReleased(e); - break; - case MouseEvent.EVENT_MOUSE_MOVED: - l.mouseMoved(e); - break; - case MouseEvent.EVENT_MOUSE_DRAGGED: - l.mouseDragged(e); - break; - case MouseEvent.EVENT_MOUSE_WHEEL_MOVED: - l.mouseWheelMoved(e); - break; - default: - throw new NativeWindowException("Unexpected mouse event type " + e.getEventType()); - } - } - } - - /** - * @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 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 int winX2 = glWinX + Math.round(pixels); - final float[] objPos2 = winToShapeCoord(pmv, viewport, winX2, glWinY, new float[3]); - if( null == objPos2 ) { - return; - } - final float dx = objPos2[0]; - final float dy = objPos2[1]; - final float sx = scale[0] + ( dx/box.getWidth() ); // bottom-right - final float sy = scale[1] + ( dy/box.getHeight() ); - if( DEBUG ) { - System.err.printf("DragZoom: resize %b, obj %4d/%4d, %.3f/%.3f/%.3f %.3f/%.3f/%.3f + %.3f/%.3f -> %.3f/%.3f%n", - inResize, glWinX, glWinY, objPos[0], objPos[1], objPos[2], position[0], position[1], position[2], - dx, dy, sx, sy); - } - if( 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], - dx, dy, sx, sy); - } - // move(dx, dy, 0f); - setScale(sx, sy, scale[2]); - } - return; // FIXME: pass through event? Issue zoom event? - } - final Shape.EventInfo shapeEvent = new EventInfo(glWinX, glWinY, this, objPos); - e.setAttachment(shapeEvent); - - for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { - mouseListeners.get(i).gestureDetected(e); - } - } - - // - // - // - - protected abstract void validateImpl(final GLProfile glp, final GL2ES2 gl); - - protected abstract void drawImpl0(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount, float[] rgba); - - protected abstract void clearImpl0(final GL2ES2 gl, final RegionRenderer renderer); - - protected abstract void destroyImpl0(final GL2ES2 gl, final RegionRenderer renderer); - - /** - * Returns true if implementation uses an extra color channel or texture - * which will be modulated with the passed rgba color {@link #drawImpl0(GL2ES2, RegionRenderer, int[], float[])}. - * - * Otherwise the base color will be modulated and passed to {@link #drawImpl0(GL2ES2, RegionRenderer, int[], float[])}. - */ - public abstract boolean hasColorChannel(); - - // - // - // -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Button.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Button.java deleted file mode 100644 index 2f2e4c469..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Button.java +++ /dev/null @@ -1,208 +0,0 @@ -/** - * 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.gl.shapes; - -import com.jogamp.opengl.GL2ES2; - -import com.jogamp.graph.curve.OutlineShape; -import com.jogamp.graph.curve.Region; -import com.jogamp.graph.curve.opengl.RegionRenderer; -import com.jogamp.graph.font.Font; -import com.jogamp.graph.geom.plane.AffineTransform; -import com.jogamp.graph.ui.gl.GraphShape; -import com.jogamp.opengl.math.geom.AABBox; - -import jogamp.graph.ui.shapes.Label0; - -/** - * A GraphUI text labeled {@link RoundButton} {@link GraphShape} - *

- * GraphUI is GPU based and resolution independent. - *

- *

- * This button is rendered with a round oval shape. - * To render it rectangular, {@link #setCorner(float)} to zero. - *

- */ -public class Button extends RoundButton { - /** {@value} */ - public static final float DEFAULT_SPACING_X = 0.12f; - /** {@value} */ - public static final float DEFAULT_SPACING_Y = 0.42f; - - private static final float DEFAULT_2PASS_LABEL_ZOFFSET = -0.005f; // -0.05f; - private float twoPassLabelZOffset = DEFAULT_2PASS_LABEL_ZOFFSET; - - private final Label0 label; - private float spacingX = DEFAULT_SPACING_X; - private float spacingY = DEFAULT_SPACING_Y; - - private final AffineTransform tempT1 = new AffineTransform(); - private final AffineTransform tempT2 = new AffineTransform(); - private final AffineTransform tempT3 = new AffineTransform(); - - public Button(final int renderModes, final Font labelFont, - final String labelText, final float width, - final float height) { - super(renderModes | Region.COLORCHANNEL_RENDERING_BIT, width, height); - this.label = new Label0(labelFont, labelText, new float[] { 1.33f, 1.33f, 1.33f, 1.0f }); // 0.75 * 1.33 = 1.0 - setColor(0.75f, 0.75f, 0.75f, 1.0f); - setPressedColorMod(0.9f, 0.9f, 0.9f, 0.7f); - setToggleOffColorMod(0.65f, 0.65f, 0.65f, 1.0f); - setToggleOnColorMod(0.85f, 0.85f, 0.85f, 1.0f); - } - - public Font getFont() { return label.getFont(); } - public String getLaben() { return label.getText(); } - - @Override - public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { - // Setup poly offset for z-fighting - // gl.glEnable(GL.GL_POLYGON_OFFSET_FILL); - // gl.glPolygonOffset(0f, 1f); - super.draw(gl, renderer, sampleCount); - // gl.glDisable(GL.GL_POLYGON_OFFSET_FILL); - } - - @Override - protected void addShapeToRegion() { - final OutlineShape shape = new OutlineShape(vertexFactory); - if(corner == 0.0f) { - createSharpOutline(shape, twoPassLabelZOffset); - } else { - createCurvedOutline(shape, twoPassLabelZOffset); - } - shape.setIsQuadraticNurbs(); - shape.setSharpness(oshapeSharpness); - region.addOutlineShape(shape, null, rgbaColor); - box.resize(shape.getBounds()); - - // Precompute text-box size .. guessing pixelSize - final float lw = box.getWidth() * ( 1f - spacingX ) ; - final float lh = box.getHeight() * ( 1f - spacingY ) ; - final AABBox lbox0_em = label.getFont().getGlyphBounds(label.getText(), tempT1, tempT2); - final float lsx = lw / lbox0_em.getWidth(); - final float lsy = lh / lbox0_em.getHeight(); - final float lScale = lsx < lsy ? lsx : lsy; - - // Setting left-corner transform using text-box in font em-size [0..1] - final AABBox lbox1_s = new AABBox(lbox0_em).scale2(lScale, new float[3]); - // Center text .. (share same center w/ button) - final float[] lctr = lbox1_s.getCenter(); - final float[] ctr = box.getCenter(); - final float[] ltxy = new float[] { ctr[0] - lctr[0], ctr[1] - lctr[1] }; - - if( DEBUG_DRAW ) { - System.err.println("Button: dim "+width+" x "+height+", spacing "+spacingX+", "+spacingY); - System.err.println("Button: net-text "+lw+" x "+lh); - System.err.println("Button: shape "+box); - System.err.println("Button: text_em "+lbox0_em+" em, "+label.getText()); - System.err.println("Button: lscale "+lsx+" x "+lsy+" -> "+lScale); - System.err.printf ("Button: text_s %s%n", lbox1_s); - System.err.printf ("Button: ltxy %f / %f, %f / %f%n", ltxy[0], ltxy[1], ltxy[0] * lScale, ltxy[1] * lScale); - } - - final AABBox lbox2 = label.addShapeToRegion(lScale, region, ltxy, tempT1, tempT2, tempT3); - if( DEBUG_DRAW ) { - System.err.printf("Button.X: lbox2 %s%n", lbox2); - } - - setRotationPivot( ctr ); - - if( DEBUG_DRAW ) { - System.err.println("XXX.Button: Added Shape: "+shape+", "+box); - } - } - - public float get2PassLabelZOffset() { return twoPassLabelZOffset; } - - public void set2PassLabelZOffset(final float v) { - twoPassLabelZOffset = v; - markShapeDirty(); - } - - public final float getSpacingX() { return spacingX; } - public final float getSpacingY() { return spacingY; } - - /** - * In percent of text label - * @param spacingX spacing in percent on X, default is {@link #DEFAULT_SPACING_X} - * @param spacingY spacing in percent on Y, default is {@link #DEFAULT_SPACING_Y} - */ - public final void setSpacing(final float spacingX, final float spacingY) { - if ( spacingX < 0.0f ) { - this.spacingX = 0.0f; - } else if ( spacingX > 1.0f ) { - this.spacingX = 1.0f; - } else { - this.spacingX = spacingX; - } - if ( spacingY < 0.0f ) { - this.spacingY = 0.0f; - } else if ( spacingY > 1.0f ) { - this.spacingY = 1.0f; - } else { - this.spacingY = spacingY; - } - markShapeDirty(); - } - - public final float[] getLabelColor() { - return label.getColor(); - } - - public final void setLabelColor(final float r, final float g, final float b) { - label.setColor(r, g, b, 1.0f); - markShapeDirty(); - } - - public final void setFont(final Font labelFont) { - if( !label.getFont().equals(labelFont) ) { - label.setFont(labelFont); - markShapeDirty(); - } - } - public final void setLabel(final String labelText) { - if( !label.getText().equals(labelText) ) { - label.setText(labelText); - markShapeDirty(); - } - } - public final void setLabel(final Font labelFont, final String labelText) { - if( !label.getText().equals(labelText) || !label.getFont().equals(labelFont) ) { - label.setFont(labelFont); - label.setText(labelText); - markShapeDirty(); - } - } - - @Override - public String getSubString() { - return super.getSubString()+", "+ label + ", " + "spacing["+spacingX+", "+spacingY+"]"; - } -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/CrossHair.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/CrossHair.java deleted file mode 100644 index 534e6fc7b..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/CrossHair.java +++ /dev/null @@ -1,99 +0,0 @@ -/** - * 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.gl.shapes; - -import com.jogamp.graph.curve.OutlineShape; -import com.jogamp.graph.ui.gl.GraphShape; - -/** - * A GraphUI Crosshair {@link GraphShape} - *

- * GraphUI is GPU based and resolution independent. - *

- */ -public class CrossHair extends GraphShape { - private float width, height, lineWidth; - - public CrossHair(final int renderModes, final float width, final float height, final float linewidth) { - super(renderModes); - this.width = width; - this.height = height; - this.lineWidth = linewidth; - } - - public final float getWidth() { return width; } - public final float getHeight() { return height; } - public final float getLineWidth() { return lineWidth; } - - public void setDimension(final float width, final float height, final float lineWidth) { - this.width = width; - this.height = height; - this.lineWidth = lineWidth; - markShapeDirty(); - } - - @Override - protected void addShapeToRegion() { - final OutlineShape shape = new OutlineShape(vertexFactory); - - final float lwh = lineWidth/2f; - - final float tw = getWidth(); - final float th = getHeight(); - final float twh = tw/2f; - final float thh = th/2f; - - final float ctrX = 0f, ctrY = 0f; - final float ctrZ = 0f; - - // middle vertical (CCW!) - shape.moveTo(ctrX-lwh, ctrY-thh, ctrZ); - shape.lineTo(ctrX+lwh, ctrY-thh, ctrZ); - shape.lineTo(ctrX+lwh, ctrY+thh, ctrZ); - shape.lineTo(ctrX-lwh, ctrY+thh, ctrZ); - shape.closePath(); - - // middle horizontal (CCW!) - shape.moveTo(ctrX-twh, ctrY-lwh, ctrZ); - shape.lineTo(ctrX+twh, ctrY-lwh, ctrZ); - shape.lineTo(ctrX+twh, ctrY+lwh, ctrZ); - shape.lineTo(ctrX-twh, ctrY+lwh, ctrZ); - shape.closePath(); - - shape.setIsQuadraticNurbs(); - shape.setSharpness(oshapeSharpness); - region.addOutlineShape(shape, null, rgbaColor); - - box.resize(shape.getBounds()); - } - - @Override - public String getSubString() { - return super.getSubString()+", dim "+getWidth() + " x " + getHeight(); - } -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GLButton.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GLButton.java deleted file mode 100644 index 5fdb7991b..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GLButton.java +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Copyright 2014-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.gl.shapes; - -import com.jogamp.opengl.GL; -import com.jogamp.opengl.GL2ES2; -import com.jogamp.opengl.GLCapabilities; -import com.jogamp.opengl.GLCapabilitiesImmutable; -import com.jogamp.opengl.GLContext; -import com.jogamp.opengl.GLDrawable; -import com.jogamp.opengl.GLDrawableFactory; -import com.jogamp.opengl.GLEventListener; -import com.jogamp.opengl.GLOffscreenAutoDrawable; -import com.jogamp.graph.curve.opengl.RegionRenderer; -import com.jogamp.graph.ui.gl.GraphShape; -import com.jogamp.opengl.FBObject; -import com.jogamp.opengl.util.texture.ImageSequence; -import com.jogamp.opengl.util.texture.Texture; - -/** - * A GraphUI {@link GLEventListener} based {@link TexSeqButton} {@link GraphShape}. - *

- * GraphUI is GPU based and resolution independent. - *

- *

- * The {@link GLEventListener} is rendered via an {@link GLOffscreenAutoDrawable.FBO} into an {@link ImageSequence}. - *

- *

- * This button is rendered with a round oval shape. - * To render it rectangular, {@link #setCorner(float)} to zero. - *

- */ -public class GLButton extends TexSeqButton { - private final GLEventListener glel; - private final boolean useAlpha; - private volatile int fboWidth = 200; - private volatile int fboHeight = 200; - private volatile GLOffscreenAutoDrawable.FBO fboGLAD = null; - private boolean animateGLEL = false; - - public GLButton(final int renderModes, final float width, final float height, - final int textureUnit, final GLEventListener glel, final boolean useAlpha) { - super(renderModes, width, height, new ImageSequence(textureUnit, true)); - this.glel = glel; - this.useAlpha = useAlpha; - - setColor(0.95f, 0.95f, 0.95f, 1.0f); - setPressedColorMod(1f, 1f, 1f, 0.9f); - setToggleOffColorMod(0.8f, 0.8f, 0.8f, 1.0f); - setToggleOnColorMod(1.0f, 1.0f, 1.0f, 1.0f); - - // fake surface-size, will be overriden in initial FBO setup @ display - this.fboWidth = 320; - this.fboHeight = Math.round( 640 * height / width ); - } - - public final void setAnimate(final boolean v) { animateGLEL = v; } - public final boolean getAnimate() { return animateGLEL; } - - public final void setFBOSize(final int fboWidth, final int fboHeight) { - this.fboWidth = fboWidth; - this.fboHeight = fboHeight; - } - - public final GLOffscreenAutoDrawable.FBO getFBOAutoDrawable() { return fboGLAD; } - - @Override - protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { - ((ImageSequence)texSeq).destroy(gl); - fboGLAD.destroy(); - } - - @Override - public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { - final int[/*2*/] surfaceSize = getSurfaceSize(renderer.getMatrix(), renderer.getViewport(), new int[2]); - final boolean got_sz = null != surfaceSize && 0 < surfaceSize[0] && 0 < surfaceSize[1]; - - if( null == fboGLAD ) { - final ImageSequence imgSeq = (ImageSequence)texSeq; - - final GLContext ctx = gl.getContext(); - final GLDrawable drawable = ctx.getGLDrawable(); - final GLCapabilitiesImmutable reqCaps = drawable.getRequestedGLCapabilities(); - final GLCapabilities caps = (GLCapabilities) reqCaps.cloneMutable(); - caps.setFBO(true); - caps.setDoubleBuffered(false); - if( !useAlpha ) { - caps.setAlphaBits(0); - } - final GLDrawableFactory factory = GLDrawableFactory.getFactory(caps.getGLProfile()); - - // System.err.println("XXX FBO initSurfaceSize got_sz "+got_sz+", "+fboWidth+" x "+fboHeight+" -> "+surfaceSize[0]+" x "+surfaceSize[1]); - if( got_sz ) { - // override with real surface-size - fboWidth = surfaceSize[0]; - fboHeight = surfaceSize[1]; - } - fboGLAD = (GLOffscreenAutoDrawable.FBO) factory.createOffscreenAutoDrawable( - drawable.getNativeSurface().getGraphicsConfiguration().getScreen().getDevice(), - caps, null, fboWidth, fboHeight); - fboWidth = 0; - fboHeight = 0; - fboGLAD.setSharedContext(ctx); - fboGLAD.setTextureUnit(imgSeq.getTextureUnit()); - fboGLAD.addGLEventListener(glel); - fboGLAD.display(); // 1st init! - - final FBObject.TextureAttachment texA01 = fboGLAD.getColorbuffer(GL.GL_FRONT).getTextureAttachment(); - final Texture tex = new Texture(texA01.getName(), imgSeq.getTextureTarget(), - fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), - false /* mustFlipVertically */); - imgSeq.addFrame(gl, tex); - markStateDirty(); - } else if( 0 != fboWidth*fboHeight ) { - fboGLAD.setSurfaceSize(fboWidth, fboHeight); - fboWidth = 0; - fboHeight = 0; - markStateDirty(); - } else if( got_sz && ( fboGLAD.getSurfaceWidth() != surfaceSize[0] || fboGLAD.getSurfaceHeight() != surfaceSize[1] ) ) { - // System.err.println("XXX FBO setSurfaceSize "+fboGLAD.getSurfaceWidth()+" x "+fboGLAD.getSurfaceHeight()+" -> "+surfaceSize[0]+" x "+surfaceSize[1]); - final ImageSequence imgSeq = (ImageSequence)texSeq; - - fboGLAD.setSurfaceSize(surfaceSize[0], surfaceSize[1]); - fboGLAD.display(); // re-init! - - imgSeq.destroy(gl); - final FBObject.TextureAttachment texA01 = fboGLAD.getColorbuffer(GL.GL_FRONT).getTextureAttachment(); - final Texture tex = new Texture(texA01.getName(), imgSeq.getTextureTarget(), - fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), - false /* mustFlipVertically */); - imgSeq.addFrame(gl, tex); - fboWidth = 0; - fboHeight = 0; - markStateDirty(); - } else if( animateGLEL ) { - fboGLAD.display(); - } - - super.draw(gl, renderer, sampleCount); - - if( animateGLEL ) { - markStateDirty(); // keep on going - } - } -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GlyphShape.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GlyphShape.java deleted file mode 100644 index 07c8ae0de..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GlyphShape.java +++ /dev/null @@ -1,204 +0,0 @@ -/** - * 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.gl.shapes; - -import java.util.List; - -import com.jogamp.graph.curve.OutlineShape; -import com.jogamp.graph.curve.Region; -import com.jogamp.graph.curve.opengl.GLRegion; -import com.jogamp.graph.font.Font; -import com.jogamp.graph.font.Font.Glyph; -import com.jogamp.graph.geom.plane.AffineTransform; -import com.jogamp.graph.ui.gl.GraphShape; -import com.jogamp.opengl.GLProfile; -import com.jogamp.opengl.math.Vec3f; -import com.jogamp.opengl.math.geom.AABBox; -import com.jogamp.opengl.util.texture.TextureSequence; - -/** - * Representing a single {@link Font.Glyph} as a {@link GraphShape} - * - * A GlyphShape is represented in font em-size [0..1] unscaled w/ bottom-left origin at 0/0 - * while preserving an intended position, see {@link #getOrigPos()} and {@link #getOrigPos(float)}. - * - * Scaling, if any, should be applied via {@link #setScale(float, float, float)} etc. - */ -public class GlyphShape extends GraphShape { - private final char symbol; - private final Glyph glyph; - private final Vec3f origPos; - - /** - * Creates a new GlyphShape - * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. - * @param symbol the represented character - * @param glyph the {@link Font.Glyph} - * @param x the intended unscaled X position of this Glyph, e.g. if part of a string - otherwise use zero. - * @param y the intended unscaled Y position of this Glyph, e.g. if part of a string - otherwise use zero. - * @see #processString(List, int, Font, String) - */ - public GlyphShape(final int renderModes, final char symbol, final Glyph glyph, final float x, final float y) { - super(renderModes); - this.symbol = symbol; - this.glyph = glyph; - this.origPos = new Vec3f(x, y, 0f); - if( glyph.isWhiteSpace() || null == glyph.getShape() ) { - setEnabled(false); - } - } - - /** - * Creates a new GlyphShape - * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. - * @param font the {@link Font} to lookup the symbol's {@link Font.Glyph} - * @param symbol the represented character - * @param x the intended unscaled X position of this Glyph, e.g. if part of a string - otherwise use zero. - * @param y the intended unscaled Y position of this Glyph, e.g. if part of a string - otherwise use zero. - */ - public GlyphShape(final int renderModes, final Font font, final char symbol, final float x, final float y) { - super(renderModes); - this.symbol = symbol; - this.glyph = font.getGlyph( font.getGlyphID(symbol) ); - this.origPos = new Vec3f(x, y, 0f); - if( glyph.isWhiteSpace() || null == glyph.getShape() ) { - setEnabled(false); - } - } - - /** Returns the char symbol to be rendered. */ - public char getSymbol() { - return symbol; - } - - /** - * Returns the {@link Font.Glyph} to be rendered. - */ - public Glyph getGlyph() { - return glyph; - } - - /** - * Returns the {@link Font} used to render the text - */ - public Font getFont() { - return glyph.getFont(); - } - - /** - * Returns the unscaled original position of this glyph, e.g. if part of a string, otherwise zero. - * - * Method borrows and returns the internal instance. - * - * @see #processString(List, int, Font, String) - */ - public Vec3f getOrigPos() { return origPos; } - - /** - * Returns the unscaled original position of this glyph, e.g. if part of a string, otherwise zero. - * - * @param s {@link Vec3f} storage to be returned - * @return storage containing the unscaled original position - * @see #processString(List, int, Font, String) - */ - public Vec3f getOrigPos(final Vec3f s) { return s.set(origPos); } - - /** - * Returns a copy of the scaled original position of this glyph, see {@link #getOrigPos(Vec3f)} - * @see #processString(List, int, Font, String) - */ - public Vec3f getOrigPos(final float scale) { return origPos.mul(scale); } - - /** - * Returns the scaled original position of this glyph, see {@link #getOrigPos(float)} - * @param s {@link Vec3f} storage to be returned - * @return storage containing the scaled original position - * @see #processString(List, int, Font, String) - */ - public Vec3f getOrigPos(final Vec3f s, final float scale) { return s.set(origPos).scale(scale); } - - /** Resets this Shape's position to the scaled original position, see {@link #getOrigPos(float)}. */ - public void resetPos(final float scale) { - moveTo(origPos.x() * scale, origPos.y() * scale, 0f); - } - - /** Resets this Shape's position to the scaled original position and {@link #setScale(float, float, float) set scale}, see {@link #resetPos(float)}. */ - public void resetPosAndScale(final float scale) { - moveTo(origPos.x() * scale, origPos.y() * scale, 0f); - setScale(scale, scale, 1f); - } - - /** Returns {@link Font#getLineHeight()}. */ - public float getLineHeight() { - return glyph.getFont().getLineHeight(); - } - - /** - * Process the given text resulting in a list of {@link GlyphShape}s with stored original position {@link #getOrigX()} and {@link #getOrigY()} each at font em-size [0..1]. - * @param res storage for resulting {@link GlyphShape}s. - * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. - * @param font {@link Font} used - * @param text text to be represented - * @return the bounding box of the given string by taking each glyph's font em-sized [0..1] OutlineShape into account. - * @see #getOrigX() - * @see #getOrigY() - */ - public static final AABBox processString(final List res, final int renderModes, final Font font, final String text) { - final Font.GlyphVisitor fgv = new Font.GlyphVisitor() { - @Override - public void visit(final char symbol, final Glyph glyph, final AffineTransform t) { - if( !glyph.isWhiteSpace() && null != glyph.getShape() ) { - res.add( new GlyphShape(renderModes, symbol, glyph, t.getTranslateX(), t.getTranslateY()) ); - } - } - }; - return font.processString(fgv, null, text, new AffineTransform(), new AffineTransform()); - } - - @Override - protected void addShapeToRegion() { - final OutlineShape shape = glyph.getShape(); - box.reset(); - if( null != shape ) { - final AABBox sbox = shape.getBounds(); - final AffineTransform tmp = new AffineTransform(); - // Enforce bottom-left origin @ 0/0 for good drag-zoom experience, - // but keep the underline (decline) intact! - tmp.setToTranslation(-sbox.getMinX(), -sbox.getMinY() + glyph.getBounds().getMinY()); - shape.setSharpness(oshapeSharpness); - region.addOutlineShape(shape, tmp, rgbaColor); - setRotationPivot( sbox.getCenter() ); - box.resize(tmp.transform(sbox, new AABBox())); - } - } - - @Override - public String getSubString() { - return super.getSubString()+", origPos " + origPos.x() + " / " + origPos.y() + ", '" + symbol + "'"; - } -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/ImageButton.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/ImageButton.java deleted file mode 100644 index d5718d98a..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/ImageButton.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2014-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.gl.shapes; - -import com.jogamp.opengl.GL2ES2; -import com.jogamp.graph.curve.opengl.RegionRenderer; -import com.jogamp.graph.ui.gl.GraphShape; -import com.jogamp.opengl.util.texture.ImageSequence; - -/** - * A GraphUI {@link ImageSequence} based {@link TexSeqButton} {@link GraphShape}. - *

- * GraphUI is GPU based and resolution independent. - *

- *

- * This button is rendered with a round oval shape. - * To render it rectangular, {@link #setCorner(float)} to zero. - *

- */ -public class ImageButton extends TexSeqButton { - - public ImageButton(final int renderModes, final float width, - final float height, final ImageSequence texSeq) { - super(renderModes, width, height, texSeq); - setColor(0.95f, 0.95f, 0.95f, 1.0f); - setPressedColorMod(1f, 1f, 1f, 0.9f); - setToggleOffColorMod(0.8f, 0.8f, 0.8f, 1.0f); - setToggleOnColorMod(1.0f, 1.0f, 1.0f, 1.0f); - } - - public final void setCurrentIdx(final int idx) { - ((ImageSequence)texSeq).setCurrentIdx(idx); - markStateDirty(); - } - - @Override - public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { - super.draw(gl, renderer, sampleCount); - if( !((ImageSequence)texSeq).getManualStepping() ) { - markStateDirty(); // keep on going - } - }; -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Label.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Label.java deleted file mode 100644 index cad8689ec..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Label.java +++ /dev/null @@ -1,226 +0,0 @@ -/** - * 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.gl.shapes; - -import com.jogamp.opengl.GL2ES2; -import com.jogamp.opengl.GLProfile; -import com.jogamp.opengl.math.FloatUtil; -import com.jogamp.opengl.math.geom.AABBox; -import com.jogamp.graph.curve.OutlineShape; -import com.jogamp.graph.curve.opengl.GLRegion; -import com.jogamp.graph.curve.opengl.RegionRenderer; -import com.jogamp.graph.font.Font; -import com.jogamp.graph.font.Font.Glyph; -import com.jogamp.graph.geom.plane.AffineTransform; -import com.jogamp.graph.ui.gl.GraphShape; - -/** - * A GraphUI text label {@link GraphShape} - *

- * GraphUI is GPU based and resolution independent. - *

- */ -public class Label extends GraphShape { - private Font font; - private float fontScale; - private String text; - - private final AffineTransform tempT1 = new AffineTransform(); - private final AffineTransform tempT2 = new AffineTransform(); - private final AffineTransform tempT3 = new AffineTransform(); - - /** - * Label ctor using a separate {@code fontScale} to scale the em-sized type glyphs - * @param renderModes region renderModes - * @param font the font - * @param fontScale font-scale factor, by which the em-sized type glyphs shall be scaled - * @param text the text to render - */ - public Label(final int renderModes, final Font font, final float fontScale, final String text) { - super(renderModes); - this.font = font; - this.fontScale = fontScale; - this.text = text; - } - - /** - * Label ctor using em-size type glyphs - * @param renderModes region renderModes - * @param font the font - * @param text the text to render - */ - public Label(final int renderModes, final Font font, final String text) { - super(renderModes); - this.font = font; - this.fontScale = 1f; - this.text = text; - } - - /** Return the text to be rendered. */ - public String getText() { - return text; - } - - /** - * Set the text to be rendered. Shape update is pending until next {@link #draw(GL2ES2, RegionRenderer, int[])} or {@link #validate(GL2ES2)}. - * @param text the text to be set. - * @return true if text has been updated, false if unchanged. - */ - public boolean setText(final String text) { - if( !this.text.equals(text) ) { - this.text = text; - markShapeDirty(); - return true; - } else { - return false; - } - } - - /** - * Set the text to be rendered and immediately updates the shape if necessary. - * @param gl {@link GL2ES2} to issue {@link #validate(GL2ES2)} in case text changed to immediately update shape and {@link #getBounds()} - * @param text the text to be set. - * @return true if text has been updated, false if unchanged. - */ - public boolean setText(final GL2ES2 gl, final String text) { - if( setText(text) ) { - validate(gl); - return true; - } else { - return false; - } - } - - /** - * Set the text to be rendered and immediately updates the shape if necessary. - * @param glp {@link GLProfile} to issue {@link #validate(GLProfile)} in case text changed to immediately update shape and {@link #getBounds()} - * @param text the text to be set. - * @return true if text has been updated, false if unchanged. - */ - public boolean setText(final GLProfile glp, final String text) { - if( setText(text) ) { - validate(glp); - return true; - } else { - return false; - } - } - - /** - * Return the {@link Font} used to render the text - */ - public Font getFont() { - return font; - } - - /** - * Set the {@link Font} used to render the text - * @param font the font to be set. - * @return true if font has been updated, false if unchanged. - */ - public boolean setFont(final Font font) { - if( !this.font.equals(font) ) { - this.font = font; - markShapeDirty(); - return true; - } else { - return false; - } - } - - /** - * Gets the font-scale factor, by which the em-sized type glyphs shall be scaled. - */ - public float getFontScale() { - return fontScale; - } - - /** Returns {@link Font#getLineHeight()} * {@link #getFontScale()}. */ - public float getLineHeight() { - return fontScale * font.getLineHeight(); - } - - /** Returns {@link Font#getLineHeight()} * {@link #getFontScale()} * {@link #getScaleY()}. */ - public float getScaledLineHeight() { - return getScaleY() * fontScale * font.getLineHeight(); - } - - /** - * Sets the font-scale factor, by which the em-sized type glyphs shall be scaled. - *

- * This will lead to a recreate the shape's region in case fontScale differs. - *

- *

- * Use {@link #scale(float, float, float)} for non-expensive shape scaling. - *

- * @param fontScale font-scale factor, by which the em-sized type glyphs shall be scaled - * @return true if font-scale has been updated, false if unchanged. - */ - public boolean setFontScale(final float fontScale) { - if( this.fontScale != fontScale ) { - this.fontScale = fontScale; - markShapeDirty(); - return true; - } else { - return false; - } - } - - @Override - protected GLRegion createGLRegion(final GLProfile glp) { - return GLRegion.create(glp, getRenderModes(), null, font, text); - } - - private final Font.GlyphVisitor glyphVisitor = new Font.GlyphVisitor() { - @Override - public void visit(final char symbol, final Glyph glyph, final AffineTransform t) { - if( glyph.isWhiteSpace() ) { - return; - } - final OutlineShape shape = glyph.getShape(); - shape.setSharpness(oshapeSharpness); - region.addOutlineShape(shape, t, rgbaColor); - } - }; - - @Override - protected void addShapeToRegion() { - AABBox fbox = font.getGlyphBounds(text, tempT2, tempT3); - tempT1.setToScale(fontScale, fontScale); - tempT1.translate(-fbox.getMinX(), -fbox.getMinY(), tempT2); // enforce bottom-left origin @ 0/0 for good drag-zoom experience - fbox = font.processString(glyphVisitor, tempT1, text, tempT2, tempT3); - setRotationPivot( fbox.getCenter() ); - box.copy(fbox); - } - - @Override - public String getSubString() { - final int m = Math.min(text.length(), 8); - return super.getSubString()+", fscale " + fontScale + ", '" + text.substring(0, m)+"'"; - } -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/MediaButton.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/MediaButton.java deleted file mode 100644 index 6b26c58b8..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/MediaButton.java +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright 2014-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.gl.shapes; - -import com.jogamp.opengl.GL2ES2; -import com.jogamp.common.util.InterruptSource; -import com.jogamp.graph.curve.opengl.RegionRenderer; -import com.jogamp.graph.ui.gl.GraphShape; -import com.jogamp.opengl.util.av.GLMediaPlayer; -import com.jogamp.opengl.util.av.GLMediaPlayer.GLMediaEventListener; -import com.jogamp.opengl.util.av.GLMediaPlayer.StreamException; -import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame; - -/** - * A GraphUI {@link GLMediaPlayer} based {@link TexSeqButton} {@link GraphShape}. - *

- * GraphUI is GPU based and resolution independent. - *

- *

- * This button is rendered with a round oval shape. - * To render it rectangular, {@link #setCorner(float)} to zero. - *

- */ -public class MediaButton extends TexSeqButton { - private boolean verbose = false; - - /** - * @param renderModes - * @param width - * @param height - * @param mPlayer - * @param mPlayerListener - */ - public MediaButton(final int renderModes, final float width, - final float height, final GLMediaPlayer mPlayer) { - super(renderModes, width, height, mPlayer); - setColor(0.8f, 0.8f, 0.8f, 1.0f); - setPressedColorMod(1.1f, 1.1f, 1.1f, 0.7f); - setToggleOffColorMod(0.8f, 0.8f, 0.8f, 1.0f); - setToggleOnColorMod(1.0f, 1.0f, 1.0f, 1.0f); - } - - public void setVerbose(final boolean v) { verbose = v; } - - /** - * Add the default {@link GLMediaEventListener} to {@link #getGLMediaPlayer() this class's GLMediaPlayer}. - */ - public void addDefaultEventListener() { - getGLMediaPlayer().addEventListener(defGLMediaEventListener); - } - - public final GLMediaPlayer getGLMediaPlayer() { return (GLMediaPlayer)texSeq; } - - private final GLMediaEventListener defGLMediaEventListener = new GLMediaEventListener() { - @Override - public void newFrameAvailable(final GLMediaPlayer ts, final TextureFrame newFrame, final long when) { - // texButton.markStateDirty(); - } - - @Override - public void attributesChanged(final GLMediaPlayer mp, final int event_mask, final long when) { - if( verbose ) { - System.err.println("MediaButton AttributesChanges: events_mask 0x"+Integer.toHexString(event_mask)+", when "+when); - System.err.println("MediaButton State: "+mp); - } - if( 0 != ( GLMediaEventListener.EVENT_CHANGE_INIT & event_mask ) ) { - resetGL = true; - } - if( 0 != ( GLMediaEventListener.EVENT_CHANGE_SIZE & event_mask ) ) { - // FIXME: mPlayer.resetGLState(); - } - if( 0 != ( GLMediaEventListener.EVENT_CHANGE_EOS & event_mask ) ) { - new InterruptSource.Thread() { - @Override - public void run() { - // loop for-ever .. - mp.seek(0); - mp.resume(); - } }.start(); - } else if( 0 != ( GLMediaEventListener.EVENT_CHANGE_ERR & event_mask ) ) { - final StreamException se = mp.getStreamException(); - if( null != se ) { - se.printStackTrace(); - } - } - } }; - - - @Override - protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { - ((GLMediaPlayer)texSeq).destroy(gl); - } - - volatile boolean resetGL = true; - - @Override - public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { - final GLMediaPlayer mPlayer = (GLMediaPlayer)texSeq; - if( resetGL ) { - resetGL = false; - try { - mPlayer.initGL(gl); - if( null != region ) { - region.markShapeDirty(); // reset texture data - } - } catch (final Exception e) { - e.printStackTrace(); - } - } - super.draw(gl, renderer, sampleCount); - markStateDirty(); // keep on going - }; - -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Rectangle.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Rectangle.java deleted file mode 100644 index 4a8c29f2a..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Rectangle.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * 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.gl.shapes; - -import com.jogamp.graph.curve.OutlineShape; -import com.jogamp.graph.ui.gl.GraphShape; - -/** - * A GraphUI Rectangle {@link GraphShape} - *

- * GraphUI is GPU based and resolution independent. - *

- */ -public class Rectangle extends GraphShape { - private float width, height, lineWidth; - - public Rectangle(final int renderModes, final float width, final float height, final float linewidth) { - super(renderModes); - this.width = width; - this.height = height; - this.lineWidth = linewidth; - } - - public final float getWidth() { return width; } - public final float getHeight() { return height; } - public final float getLineWidth() { return lineWidth; } - - public void setDimension(final float width, final float height, final float lineWidth) { - this.width = width; - this.height = height; - this.lineWidth = lineWidth; - markShapeDirty(); - } - - @Override - protected void addShapeToRegion() { - final OutlineShape shape = new OutlineShape(vertexFactory); - - final float lwh = lineWidth/2f; - - final float tw = getWidth(); - final float th = getHeight(); - - final float twh = tw/2f; - final float twh_o = twh+lwh; - final float twh_i = twh-lwh; - final float thh = th/2f; - final float thh_o = thh+lwh; - final float thh_i = thh-lwh; - - final float ctrX = 0f, ctrY = 0f; - final float ctrZ = 0f; - - // outer (CCW!) - shape.moveTo(ctrX-twh_o, ctrY-thh_o, ctrZ); - shape.lineTo(ctrX+twh_o, ctrY-thh_o, ctrZ); - shape.lineTo(ctrX+twh_o, ctrY+thh_o, ctrZ); - shape.lineTo(ctrX-twh_o, ctrY+thh_o, ctrZ); - shape.closePath(); - - // inner (CCW!) - shape.moveTo(ctrX-twh_i, ctrY-thh_i, ctrZ); - shape.lineTo(ctrX+twh_i, ctrY-thh_i, ctrZ); - shape.lineTo(ctrX+twh_i, ctrY+thh_i, ctrZ); - shape.lineTo(ctrX-twh_i, ctrY+thh_i, ctrZ); - shape.closePath(); - - shape.setIsQuadraticNurbs(); - shape.setSharpness(oshapeSharpness); - region.addOutlineShape(shape, null, rgbaColor); - - box.resize(shape.getBounds()); - } - - @Override - public String getSubString() { - return super.getSubString()+", dim "+getWidth() + " x " + getHeight(); - } -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/RoundButton.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/RoundButton.java deleted file mode 100644 index 93ba4f0dc..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/RoundButton.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * 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.gl.shapes; - -import com.jogamp.graph.curve.OutlineShape; -import com.jogamp.graph.ui.gl.GraphShape; - -/** - * An abstract GraphUI round Button {@link GraphShape} - *

- * GraphUI is GPU based and resolution independent. - *

- *

- * This button is rendered with a round oval shape. - * To render it rectangular, {@link #setCorner(float)} to zero. - *

- */ -public abstract class RoundButton extends GraphShape { - - /** {@value} */ - public static final float DEFAULT_CORNER = 1f; - protected float width; - protected float height; - protected float corner = DEFAULT_CORNER; - - protected RoundButton(final int renderModes, final float width, final float height) { - super(renderModes); - this.width = width; - this.height = height; - } - - public final float getWidth() { return width; } - - public final float getHeight() { return height; } - - public final float getCorner() { return corner; } - - public void setSize(final float width, final float height) { - this.width = width; - this.height = height; - markShapeDirty(); - } - - protected void createSharpOutline(final OutlineShape shape, final float zOffset) { - final float tw = getWidth(); - final float th = getHeight(); - - final float minX = 0; - final float minY = 0; - final float minZ = zOffset; - - shape.addVertex(minX, minY, minZ, true); - shape.addVertex(minX+tw, minY, minZ, true); - shape.addVertex(minX+tw, minY + th, minZ, true); - shape.addVertex(minX, minY + th, minZ, true); - shape.closeLastOutline(true); - } - - protected void createCurvedOutline(final OutlineShape shape, final float zOffset) { - final float tw = getWidth(); - final float th = getHeight(); - final float dC = 0.5f*corner*Math.min(tw, th); - - final float minX = 0; - final float minY = 0; - final float minZ = zOffset; - - shape.addVertex(minX, minY + dC, minZ, true); - shape.addVertex(minX, minY, minZ, false); - - shape.addVertex(minX + dC, minY, minZ, true); - - shape.addVertex(minX + tw - dC, minY, minZ, true); - shape.addVertex(minX + tw, minY, minZ, false); - shape.addVertex(minX + tw, minY + dC, minZ, true); - shape.addVertex(minX + tw, minY + th- dC, minZ, true); - shape.addVertex(minX + tw, minY + th, minZ, false); - shape.addVertex(minX + tw - dC, minY + th, minZ, true); - shape.addVertex(minX + dC, minY + th, minZ, true); - shape.addVertex(minX, minY + th, minZ, false); - shape.addVertex(minX, minY + th - dC, minZ, true); - - shape.closeLastOutline(true); - } - - /** Set corner size, default is {@link #DEFAULT_CORNER} */ - public void setCorner(final float corner) { - if(corner > 1.0f){ - this.corner = 1.0f; - } - else if(corner < 0.01f){ - this.corner = 0.0f; - } - else{ - this.corner = corner; - } - markShapeDirty(); - } - - @Override - public String getSubString() { - return super.getSubString()+", dim "+getWidth() + " x " + getHeight() + ", corner " + corner; - } -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/TexSeqButton.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/TexSeqButton.java deleted file mode 100644 index 5ee164580..000000000 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/TexSeqButton.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright 2014-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.gl.shapes; - -import com.jogamp.opengl.GLProfile; -import com.jogamp.graph.curve.OutlineShape; -import com.jogamp.graph.curve.Region; -import com.jogamp.graph.curve.opengl.GLRegion; -import com.jogamp.graph.ui.gl.GraphShape; -import com.jogamp.opengl.util.texture.TextureSequence; - -/** - * An abstract GraphUI {@link TextureSequence} {@link RoundButton} {@link GraphShape}. - *

- * GraphUI is GPU based and resolution independent. - *

- *

- * This button is rendered with a round oval shape. - * To render it rectangular, {@link #setCorner(float)} to zero. - *

- */ -public abstract class TexSeqButton extends RoundButton { - protected final TextureSequence texSeq; - - public TexSeqButton(final int renderModes, final float width, - final float height, final TextureSequence texSeq) { - super(renderModes | Region.COLORTEXTURE_RENDERING_BIT, width, height); - this.texSeq = texSeq; - } - - @Override - protected GLRegion createGLRegion(final GLProfile glp) { - return GLRegion.create(glp, getRenderModes(), texSeq); - } - - public final TextureSequence getTextureSequence() { return this.texSeq; } - - @Override - protected void addShapeToRegion() { - final OutlineShape shape = new OutlineShape(vertexFactory); - if(corner == 0.0f) { - createSharpOutline(shape, 0f); - } else { - createCurvedOutline(shape, 0f); - } - shape.setIsQuadraticNurbs(); - shape.setSharpness(oshapeSharpness); - region.addOutlineShape(shape, null, rgbaColor); - box.resize(shape.getBounds()); - - setRotationPivot( box.getCenter() ); - - if( DEBUG_DRAW ) { - System.err.println("XXX.UIShape.TextureSeqButton: Added Shape: "+shape+", "+box); - } - } -} diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/Button.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/Button.java new file mode 100644 index 000000000..ca726e30f --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/Button.java @@ -0,0 +1,208 @@ +/** + * 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.shapes; + +import com.jogamp.opengl.GL2ES2; + +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.font.Font; +import com.jogamp.graph.geom.plane.AffineTransform; +import com.jogamp.graph.ui.GraphShape; +import com.jogamp.opengl.math.geom.AABBox; + +import jogamp.graph.ui.shapes.Label0; + +/** + * A GraphUI text labeled {@link RoundButton} {@link GraphShape} + *

+ * GraphUI is GPU based and resolution independent. + *

+ *

+ * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + *

+ */ +public class Button extends RoundButton { + /** {@value} */ + public static final float DEFAULT_SPACING_X = 0.12f; + /** {@value} */ + public static final float DEFAULT_SPACING_Y = 0.42f; + + private static final float DEFAULT_2PASS_LABEL_ZOFFSET = -0.005f; // -0.05f; + private float twoPassLabelZOffset = DEFAULT_2PASS_LABEL_ZOFFSET; + + private final Label0 label; + private float spacingX = DEFAULT_SPACING_X; + private float spacingY = DEFAULT_SPACING_Y; + + private final AffineTransform tempT1 = new AffineTransform(); + private final AffineTransform tempT2 = new AffineTransform(); + private final AffineTransform tempT3 = new AffineTransform(); + + public Button(final int renderModes, final Font labelFont, + final String labelText, final float width, + final float height) { + super(renderModes | Region.COLORCHANNEL_RENDERING_BIT, width, height); + this.label = new Label0(labelFont, labelText, new float[] { 1.33f, 1.33f, 1.33f, 1.0f }); // 0.75 * 1.33 = 1.0 + setColor(0.75f, 0.75f, 0.75f, 1.0f); + setPressedColorMod(0.9f, 0.9f, 0.9f, 0.7f); + setToggleOffColorMod(0.65f, 0.65f, 0.65f, 1.0f); + setToggleOnColorMod(0.85f, 0.85f, 0.85f, 1.0f); + } + + public Font getFont() { return label.getFont(); } + public String getLaben() { return label.getText(); } + + @Override + public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { + // Setup poly offset for z-fighting + // gl.glEnable(GL.GL_POLYGON_OFFSET_FILL); + // gl.glPolygonOffset(0f, 1f); + super.draw(gl, renderer, sampleCount); + // gl.glDisable(GL.GL_POLYGON_OFFSET_FILL); + } + + @Override + protected void addShapeToRegion() { + final OutlineShape shape = new OutlineShape(vertexFactory); + if(corner == 0.0f) { + createSharpOutline(shape, twoPassLabelZOffset); + } else { + createCurvedOutline(shape, twoPassLabelZOffset); + } + shape.setIsQuadraticNurbs(); + shape.setSharpness(oshapeSharpness); + region.addOutlineShape(shape, null, rgbaColor); + box.resize(shape.getBounds()); + + // Precompute text-box size .. guessing pixelSize + final float lw = box.getWidth() * ( 1f - spacingX ) ; + final float lh = box.getHeight() * ( 1f - spacingY ) ; + final AABBox lbox0_em = label.getFont().getGlyphBounds(label.getText(), tempT1, tempT2); + final float lsx = lw / lbox0_em.getWidth(); + final float lsy = lh / lbox0_em.getHeight(); + final float lScale = lsx < lsy ? lsx : lsy; + + // Setting left-corner transform using text-box in font em-size [0..1] + final AABBox lbox1_s = new AABBox(lbox0_em).scale2(lScale, new float[3]); + // Center text .. (share same center w/ button) + final float[] lctr = lbox1_s.getCenter(); + final float[] ctr = box.getCenter(); + final float[] ltxy = new float[] { ctr[0] - lctr[0], ctr[1] - lctr[1] }; + + if( DEBUG_DRAW ) { + System.err.println("Button: dim "+width+" x "+height+", spacing "+spacingX+", "+spacingY); + System.err.println("Button: net-text "+lw+" x "+lh); + System.err.println("Button: shape "+box); + System.err.println("Button: text_em "+lbox0_em+" em, "+label.getText()); + System.err.println("Button: lscale "+lsx+" x "+lsy+" -> "+lScale); + System.err.printf ("Button: text_s %s%n", lbox1_s); + System.err.printf ("Button: ltxy %f / %f, %f / %f%n", ltxy[0], ltxy[1], ltxy[0] * lScale, ltxy[1] * lScale); + } + + final AABBox lbox2 = label.addShapeToRegion(lScale, region, ltxy, tempT1, tempT2, tempT3); + if( DEBUG_DRAW ) { + System.err.printf("Button.X: lbox2 %s%n", lbox2); + } + + setRotationPivot( ctr ); + + if( DEBUG_DRAW ) { + System.err.println("XXX.Button: Added Shape: "+shape+", "+box); + } + } + + public float get2PassLabelZOffset() { return twoPassLabelZOffset; } + + public void set2PassLabelZOffset(final float v) { + twoPassLabelZOffset = v; + markShapeDirty(); + } + + public final float getSpacingX() { return spacingX; } + public final float getSpacingY() { return spacingY; } + + /** + * In percent of text label + * @param spacingX spacing in percent on X, default is {@link #DEFAULT_SPACING_X} + * @param spacingY spacing in percent on Y, default is {@link #DEFAULT_SPACING_Y} + */ + public final void setSpacing(final float spacingX, final float spacingY) { + if ( spacingX < 0.0f ) { + this.spacingX = 0.0f; + } else if ( spacingX > 1.0f ) { + this.spacingX = 1.0f; + } else { + this.spacingX = spacingX; + } + if ( spacingY < 0.0f ) { + this.spacingY = 0.0f; + } else if ( spacingY > 1.0f ) { + this.spacingY = 1.0f; + } else { + this.spacingY = spacingY; + } + markShapeDirty(); + } + + public final float[] getLabelColor() { + return label.getColor(); + } + + public final void setLabelColor(final float r, final float g, final float b) { + label.setColor(r, g, b, 1.0f); + markShapeDirty(); + } + + public final void setFont(final Font labelFont) { + if( !label.getFont().equals(labelFont) ) { + label.setFont(labelFont); + markShapeDirty(); + } + } + public final void setLabel(final String labelText) { + if( !label.getText().equals(labelText) ) { + label.setText(labelText); + markShapeDirty(); + } + } + public final void setLabel(final Font labelFont, final String labelText) { + if( !label.getText().equals(labelText) || !label.getFont().equals(labelFont) ) { + label.setFont(labelFont); + label.setText(labelText); + markShapeDirty(); + } + } + + @Override + public String getSubString() { + return super.getSubString()+", "+ label + ", " + "spacing["+spacingX+", "+spacingY+"]"; + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/CrossHair.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/CrossHair.java new file mode 100644 index 000000000..3e36422a4 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/CrossHair.java @@ -0,0 +1,99 @@ +/** + * 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.shapes; + +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.ui.GraphShape; + +/** + * A GraphUI Crosshair {@link GraphShape} + *

+ * GraphUI is GPU based and resolution independent. + *

+ */ +public class CrossHair extends GraphShape { + private float width, height, lineWidth; + + public CrossHair(final int renderModes, final float width, final float height, final float linewidth) { + super(renderModes); + this.width = width; + this.height = height; + this.lineWidth = linewidth; + } + + public final float getWidth() { return width; } + public final float getHeight() { return height; } + public final float getLineWidth() { return lineWidth; } + + public void setDimension(final float width, final float height, final float lineWidth) { + this.width = width; + this.height = height; + this.lineWidth = lineWidth; + markShapeDirty(); + } + + @Override + protected void addShapeToRegion() { + final OutlineShape shape = new OutlineShape(vertexFactory); + + final float lwh = lineWidth/2f; + + final float tw = getWidth(); + final float th = getHeight(); + final float twh = tw/2f; + final float thh = th/2f; + + final float ctrX = 0f, ctrY = 0f; + final float ctrZ = 0f; + + // middle vertical (CCW!) + shape.moveTo(ctrX-lwh, ctrY-thh, ctrZ); + shape.lineTo(ctrX+lwh, ctrY-thh, ctrZ); + shape.lineTo(ctrX+lwh, ctrY+thh, ctrZ); + shape.lineTo(ctrX-lwh, ctrY+thh, ctrZ); + shape.closePath(); + + // middle horizontal (CCW!) + shape.moveTo(ctrX-twh, ctrY-lwh, ctrZ); + shape.lineTo(ctrX+twh, ctrY-lwh, ctrZ); + shape.lineTo(ctrX+twh, ctrY+lwh, ctrZ); + shape.lineTo(ctrX-twh, ctrY+lwh, ctrZ); + shape.closePath(); + + shape.setIsQuadraticNurbs(); + shape.setSharpness(oshapeSharpness); + region.addOutlineShape(shape, null, rgbaColor); + + box.resize(shape.getBounds()); + } + + @Override + public String getSubString() { + return super.getSubString()+", dim "+getWidth() + " x " + getHeight(); + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/GLButton.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/GLButton.java new file mode 100644 index 000000000..f11475b84 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/GLButton.java @@ -0,0 +1,170 @@ +/** + * Copyright 2014-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.shapes; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLCapabilities; +import com.jogamp.opengl.GLCapabilitiesImmutable; +import com.jogamp.opengl.GLContext; +import com.jogamp.opengl.GLDrawable; +import com.jogamp.opengl.GLDrawableFactory; +import com.jogamp.opengl.GLEventListener; +import com.jogamp.opengl.GLOffscreenAutoDrawable; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.ui.GraphShape; +import com.jogamp.opengl.FBObject; +import com.jogamp.opengl.util.texture.ImageSequence; +import com.jogamp.opengl.util.texture.Texture; + +/** + * A GraphUI {@link GLEventListener} based {@link TexSeqButton} {@link GraphShape}. + *

+ * GraphUI is GPU based and resolution independent. + *

+ *

+ * The {@link GLEventListener} is rendered via an {@link GLOffscreenAutoDrawable.FBO} into an {@link ImageSequence}. + *

+ *

+ * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + *

+ */ +public class GLButton extends TexSeqButton { + private final GLEventListener glel; + private final boolean useAlpha; + private volatile int fboWidth = 200; + private volatile int fboHeight = 200; + private volatile GLOffscreenAutoDrawable.FBO fboGLAD = null; + private boolean animateGLEL = false; + + public GLButton(final int renderModes, final float width, final float height, + final int textureUnit, final GLEventListener glel, final boolean useAlpha) { + super(renderModes, width, height, new ImageSequence(textureUnit, true)); + this.glel = glel; + this.useAlpha = useAlpha; + + setColor(0.95f, 0.95f, 0.95f, 1.0f); + setPressedColorMod(1f, 1f, 1f, 0.9f); + setToggleOffColorMod(0.8f, 0.8f, 0.8f, 1.0f); + setToggleOnColorMod(1.0f, 1.0f, 1.0f, 1.0f); + + // fake surface-size, will be overriden in initial FBO setup @ display + this.fboWidth = 320; + this.fboHeight = Math.round( 640 * height / width ); + } + + public final void setAnimate(final boolean v) { animateGLEL = v; } + public final boolean getAnimate() { return animateGLEL; } + + public final void setFBOSize(final int fboWidth, final int fboHeight) { + this.fboWidth = fboWidth; + this.fboHeight = fboHeight; + } + + public final GLOffscreenAutoDrawable.FBO getFBOAutoDrawable() { return fboGLAD; } + + @Override + protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { + ((ImageSequence)texSeq).destroy(gl); + fboGLAD.destroy(); + } + + @Override + public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { + final int[/*2*/] surfaceSize = getSurfaceSize(renderer.getMatrix(), renderer.getViewport(), new int[2]); + final boolean got_sz = null != surfaceSize && 0 < surfaceSize[0] && 0 < surfaceSize[1]; + + if( null == fboGLAD ) { + final ImageSequence imgSeq = (ImageSequence)texSeq; + + final GLContext ctx = gl.getContext(); + final GLDrawable drawable = ctx.getGLDrawable(); + final GLCapabilitiesImmutable reqCaps = drawable.getRequestedGLCapabilities(); + final GLCapabilities caps = (GLCapabilities) reqCaps.cloneMutable(); + caps.setFBO(true); + caps.setDoubleBuffered(false); + if( !useAlpha ) { + caps.setAlphaBits(0); + } + final GLDrawableFactory factory = GLDrawableFactory.getFactory(caps.getGLProfile()); + + // System.err.println("XXX FBO initSurfaceSize got_sz "+got_sz+", "+fboWidth+" x "+fboHeight+" -> "+surfaceSize[0]+" x "+surfaceSize[1]); + if( got_sz ) { + // override with real surface-size + fboWidth = surfaceSize[0]; + fboHeight = surfaceSize[1]; + } + fboGLAD = (GLOffscreenAutoDrawable.FBO) factory.createOffscreenAutoDrawable( + drawable.getNativeSurface().getGraphicsConfiguration().getScreen().getDevice(), + caps, null, fboWidth, fboHeight); + fboWidth = 0; + fboHeight = 0; + fboGLAD.setSharedContext(ctx); + fboGLAD.setTextureUnit(imgSeq.getTextureUnit()); + fboGLAD.addGLEventListener(glel); + fboGLAD.display(); // 1st init! + + final FBObject.TextureAttachment texA01 = fboGLAD.getColorbuffer(GL.GL_FRONT).getTextureAttachment(); + final Texture tex = new Texture(texA01.getName(), imgSeq.getTextureTarget(), + fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), + false /* mustFlipVertically */); + imgSeq.addFrame(gl, tex); + markStateDirty(); + } else if( 0 != fboWidth*fboHeight ) { + fboGLAD.setSurfaceSize(fboWidth, fboHeight); + fboWidth = 0; + fboHeight = 0; + markStateDirty(); + } else if( got_sz && ( fboGLAD.getSurfaceWidth() != surfaceSize[0] || fboGLAD.getSurfaceHeight() != surfaceSize[1] ) ) { + // System.err.println("XXX FBO setSurfaceSize "+fboGLAD.getSurfaceWidth()+" x "+fboGLAD.getSurfaceHeight()+" -> "+surfaceSize[0]+" x "+surfaceSize[1]); + final ImageSequence imgSeq = (ImageSequence)texSeq; + + fboGLAD.setSurfaceSize(surfaceSize[0], surfaceSize[1]); + fboGLAD.display(); // re-init! + + imgSeq.destroy(gl); + final FBObject.TextureAttachment texA01 = fboGLAD.getColorbuffer(GL.GL_FRONT).getTextureAttachment(); + final Texture tex = new Texture(texA01.getName(), imgSeq.getTextureTarget(), + fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), + false /* mustFlipVertically */); + imgSeq.addFrame(gl, tex); + fboWidth = 0; + fboHeight = 0; + markStateDirty(); + } else if( animateGLEL ) { + fboGLAD.display(); + } + + super.draw(gl, renderer, sampleCount); + + if( animateGLEL ) { + markStateDirty(); // keep on going + } + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/GlyphShape.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/GlyphShape.java new file mode 100644 index 000000000..568270073 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/GlyphShape.java @@ -0,0 +1,204 @@ +/** + * 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.shapes; + +import java.util.List; + +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.GLRegion; +import com.jogamp.graph.font.Font; +import com.jogamp.graph.font.Font.Glyph; +import com.jogamp.graph.geom.plane.AffineTransform; +import com.jogamp.graph.ui.GraphShape; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.math.Vec3f; +import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.opengl.util.texture.TextureSequence; + +/** + * Representing a single {@link Font.Glyph} as a {@link GraphShape} + * + * A GlyphShape is represented in font em-size [0..1] unscaled w/ bottom-left origin at 0/0 + * while preserving an intended position, see {@link #getOrigPos()} and {@link #getOrigPos(float)}. + * + * Scaling, if any, should be applied via {@link #setScale(float, float, float)} etc. + */ +public class GlyphShape extends GraphShape { + private final char symbol; + private final Glyph glyph; + private final Vec3f origPos; + + /** + * Creates a new GlyphShape + * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. + * @param symbol the represented character + * @param glyph the {@link Font.Glyph} + * @param x the intended unscaled X position of this Glyph, e.g. if part of a string - otherwise use zero. + * @param y the intended unscaled Y position of this Glyph, e.g. if part of a string - otherwise use zero. + * @see #processString(List, int, Font, String) + */ + public GlyphShape(final int renderModes, final char symbol, final Glyph glyph, final float x, final float y) { + super(renderModes); + this.symbol = symbol; + this.glyph = glyph; + this.origPos = new Vec3f(x, y, 0f); + if( glyph.isWhiteSpace() || null == glyph.getShape() ) { + setEnabled(false); + } + } + + /** + * Creates a new GlyphShape + * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. + * @param font the {@link Font} to lookup the symbol's {@link Font.Glyph} + * @param symbol the represented character + * @param x the intended unscaled X position of this Glyph, e.g. if part of a string - otherwise use zero. + * @param y the intended unscaled Y position of this Glyph, e.g. if part of a string - otherwise use zero. + */ + public GlyphShape(final int renderModes, final Font font, final char symbol, final float x, final float y) { + super(renderModes); + this.symbol = symbol; + this.glyph = font.getGlyph( font.getGlyphID(symbol) ); + this.origPos = new Vec3f(x, y, 0f); + if( glyph.isWhiteSpace() || null == glyph.getShape() ) { + setEnabled(false); + } + } + + /** Returns the char symbol to be rendered. */ + public char getSymbol() { + return symbol; + } + + /** + * Returns the {@link Font.Glyph} to be rendered. + */ + public Glyph getGlyph() { + return glyph; + } + + /** + * Returns the {@link Font} used to render the text + */ + public Font getFont() { + return glyph.getFont(); + } + + /** + * Returns the unscaled original position of this glyph, e.g. if part of a string, otherwise zero. + * + * Method borrows and returns the internal instance. + * + * @see #processString(List, int, Font, String) + */ + public Vec3f getOrigPos() { return origPos; } + + /** + * Returns the unscaled original position of this glyph, e.g. if part of a string, otherwise zero. + * + * @param s {@link Vec3f} storage to be returned + * @return storage containing the unscaled original position + * @see #processString(List, int, Font, String) + */ + public Vec3f getOrigPos(final Vec3f s) { return s.set(origPos); } + + /** + * Returns a copy of the scaled original position of this glyph, see {@link #getOrigPos(Vec3f)} + * @see #processString(List, int, Font, String) + */ + public Vec3f getOrigPos(final float scale) { return origPos.mul(scale); } + + /** + * Returns the scaled original position of this glyph, see {@link #getOrigPos(float)} + * @param s {@link Vec3f} storage to be returned + * @return storage containing the scaled original position + * @see #processString(List, int, Font, String) + */ + public Vec3f getOrigPos(final Vec3f s, final float scale) { return s.set(origPos).scale(scale); } + + /** Resets this Shape's position to the scaled original position, see {@link #getOrigPos(float)}. */ + public void resetPos(final float scale) { + moveTo(origPos.x() * scale, origPos.y() * scale, 0f); + } + + /** Resets this Shape's position to the scaled original position and {@link #setScale(float, float, float) set scale}, see {@link #resetPos(float)}. */ + public void resetPosAndScale(final float scale) { + moveTo(origPos.x() * scale, origPos.y() * scale, 0f); + setScale(scale, scale, 1f); + } + + /** Returns {@link Font#getLineHeight()}. */ + public float getLineHeight() { + return glyph.getFont().getLineHeight(); + } + + /** + * Process the given text resulting in a list of {@link GlyphShape}s with stored original position {@link #getOrigX()} and {@link #getOrigY()} each at font em-size [0..1]. + * @param res storage for resulting {@link GlyphShape}s. + * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. + * @param font {@link Font} used + * @param text text to be represented + * @return the bounding box of the given string by taking each glyph's font em-sized [0..1] OutlineShape into account. + * @see #getOrigX() + * @see #getOrigY() + */ + public static final AABBox processString(final List res, final int renderModes, final Font font, final String text) { + final Font.GlyphVisitor fgv = new Font.GlyphVisitor() { + @Override + public void visit(final char symbol, final Glyph glyph, final AffineTransform t) { + if( !glyph.isWhiteSpace() && null != glyph.getShape() ) { + res.add( new GlyphShape(renderModes, symbol, glyph, t.getTranslateX(), t.getTranslateY()) ); + } + } + }; + return font.processString(fgv, null, text, new AffineTransform(), new AffineTransform()); + } + + @Override + protected void addShapeToRegion() { + final OutlineShape shape = glyph.getShape(); + box.reset(); + if( null != shape ) { + final AABBox sbox = shape.getBounds(); + final AffineTransform tmp = new AffineTransform(); + // Enforce bottom-left origin @ 0/0 for good drag-zoom experience, + // but keep the underline (decline) intact! + tmp.setToTranslation(-sbox.getMinX(), -sbox.getMinY() + glyph.getBounds().getMinY()); + shape.setSharpness(oshapeSharpness); + region.addOutlineShape(shape, tmp, rgbaColor); + setRotationPivot( sbox.getCenter() ); + box.resize(tmp.transform(sbox, new AABBox())); + } + } + + @Override + public String getSubString() { + return super.getSubString()+", origPos " + origPos.x() + " / " + origPos.y() + ", '" + symbol + "'"; + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/ImageButton.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/ImageButton.java new file mode 100644 index 000000000..63b0b3eaf --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/ImageButton.java @@ -0,0 +1,68 @@ +/** + * Copyright 2014-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.shapes; + +import com.jogamp.opengl.GL2ES2; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.ui.GraphShape; +import com.jogamp.opengl.util.texture.ImageSequence; + +/** + * A GraphUI {@link ImageSequence} based {@link TexSeqButton} {@link GraphShape}. + *

+ * GraphUI is GPU based and resolution independent. + *

+ *

+ * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + *

+ */ +public class ImageButton extends TexSeqButton { + + public ImageButton(final int renderModes, final float width, + final float height, final ImageSequence texSeq) { + super(renderModes, width, height, texSeq); + setColor(0.95f, 0.95f, 0.95f, 1.0f); + setPressedColorMod(1f, 1f, 1f, 0.9f); + setToggleOffColorMod(0.8f, 0.8f, 0.8f, 1.0f); + setToggleOnColorMod(1.0f, 1.0f, 1.0f, 1.0f); + } + + public final void setCurrentIdx(final int idx) { + ((ImageSequence)texSeq).setCurrentIdx(idx); + markStateDirty(); + } + + @Override + public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { + super.draw(gl, renderer, sampleCount); + if( !((ImageSequence)texSeq).getManualStepping() ) { + markStateDirty(); // keep on going + } + }; +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/Label.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/Label.java new file mode 100644 index 000000000..e7d89ade4 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/Label.java @@ -0,0 +1,225 @@ +/** + * 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.shapes; + +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.opengl.GLRegion; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.font.Font; +import com.jogamp.graph.font.Font.Glyph; +import com.jogamp.graph.geom.plane.AffineTransform; +import com.jogamp.graph.ui.GraphShape; + +/** + * A GraphUI text label {@link GraphShape} + *

+ * GraphUI is GPU based and resolution independent. + *

+ */ +public class Label extends GraphShape { + private Font font; + private float fontScale; + private String text; + + private final AffineTransform tempT1 = new AffineTransform(); + private final AffineTransform tempT2 = new AffineTransform(); + private final AffineTransform tempT3 = new AffineTransform(); + + /** + * Label ctor using a separate {@code fontScale} to scale the em-sized type glyphs + * @param renderModes region renderModes + * @param font the font + * @param fontScale font-scale factor, by which the em-sized type glyphs shall be scaled + * @param text the text to render + */ + public Label(final int renderModes, final Font font, final float fontScale, final String text) { + super(renderModes); + this.font = font; + this.fontScale = fontScale; + this.text = text; + } + + /** + * Label ctor using em-size type glyphs + * @param renderModes region renderModes + * @param font the font + * @param text the text to render + */ + public Label(final int renderModes, final Font font, final String text) { + super(renderModes); + this.font = font; + this.fontScale = 1f; + this.text = text; + } + + /** Return the text to be rendered. */ + public String getText() { + return text; + } + + /** + * Set the text to be rendered. Shape update is pending until next {@link #draw(GL2ES2, RegionRenderer, int[])} or {@link #validate(GL2ES2)}. + * @param text the text to be set. + * @return true if text has been updated, false if unchanged. + */ + public boolean setText(final String text) { + if( !this.text.equals(text) ) { + this.text = text; + markShapeDirty(); + return true; + } else { + return false; + } + } + + /** + * Set the text to be rendered and immediately updates the shape if necessary. + * @param gl {@link GL2ES2} to issue {@link #validate(GL2ES2)} in case text changed to immediately update shape and {@link #getBounds()} + * @param text the text to be set. + * @return true if text has been updated, false if unchanged. + */ + public boolean setText(final GL2ES2 gl, final String text) { + if( setText(text) ) { + validate(gl); + return true; + } else { + return false; + } + } + + /** + * Set the text to be rendered and immediately updates the shape if necessary. + * @param glp {@link GLProfile} to issue {@link #validate(GLProfile)} in case text changed to immediately update shape and {@link #getBounds()} + * @param text the text to be set. + * @return true if text has been updated, false if unchanged. + */ + public boolean setText(final GLProfile glp, final String text) { + if( setText(text) ) { + validate(glp); + return true; + } else { + return false; + } + } + + /** + * Return the {@link Font} used to render the text + */ + public Font getFont() { + return font; + } + + /** + * Set the {@link Font} used to render the text + * @param font the font to be set. + * @return true if font has been updated, false if unchanged. + */ + public boolean setFont(final Font font) { + if( !this.font.equals(font) ) { + this.font = font; + markShapeDirty(); + return true; + } else { + return false; + } + } + + /** + * Gets the font-scale factor, by which the em-sized type glyphs shall be scaled. + */ + public float getFontScale() { + return fontScale; + } + + /** Returns {@link Font#getLineHeight()} * {@link #getFontScale()}. */ + public float getLineHeight() { + return fontScale * font.getLineHeight(); + } + + /** Returns {@link Font#getLineHeight()} * {@link #getFontScale()} * {@link #getScaleY()}. */ + public float getScaledLineHeight() { + return getScaleY() * fontScale * font.getLineHeight(); + } + + /** + * Sets the font-scale factor, by which the em-sized type glyphs shall be scaled. + *

+ * This will lead to a recreate the shape's region in case fontScale differs. + *

+ *

+ * Use {@link #scale(float, float, float)} for non-expensive shape scaling. + *

+ * @param fontScale font-scale factor, by which the em-sized type glyphs shall be scaled + * @return true if font-scale has been updated, false if unchanged. + */ + public boolean setFontScale(final float fontScale) { + if( this.fontScale != fontScale ) { + this.fontScale = fontScale; + markShapeDirty(); + return true; + } else { + return false; + } + } + + @Override + protected GLRegion createGLRegion(final GLProfile glp) { + return GLRegion.create(glp, getRenderModes(), null, font, text); + } + + private final Font.GlyphVisitor glyphVisitor = new Font.GlyphVisitor() { + @Override + public void visit(final char symbol, final Glyph glyph, final AffineTransform t) { + if( glyph.isWhiteSpace() ) { + return; + } + final OutlineShape shape = glyph.getShape(); + shape.setSharpness(oshapeSharpness); + region.addOutlineShape(shape, t, rgbaColor); + } + }; + + @Override + protected void addShapeToRegion() { + AABBox fbox = font.getGlyphBounds(text, tempT2, tempT3); + tempT1.setToScale(fontScale, fontScale); + tempT1.translate(-fbox.getMinX(), -fbox.getMinY(), tempT2); // enforce bottom-left origin @ 0/0 for good drag-zoom experience + fbox = font.processString(glyphVisitor, tempT1, text, tempT2, tempT3); + setRotationPivot( fbox.getCenter() ); + box.copy(fbox); + } + + @Override + public String getSubString() { + final int m = Math.min(text.length(), 8); + return super.getSubString()+", fscale " + fontScale + ", '" + text.substring(0, m)+"'"; + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/MediaButton.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/MediaButton.java new file mode 100644 index 000000000..55a00aae4 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/MediaButton.java @@ -0,0 +1,139 @@ +/** + * Copyright 2014-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.shapes; + +import com.jogamp.opengl.GL2ES2; +import com.jogamp.common.util.InterruptSource; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.ui.GraphShape; +import com.jogamp.opengl.util.av.GLMediaPlayer; +import com.jogamp.opengl.util.av.GLMediaPlayer.GLMediaEventListener; +import com.jogamp.opengl.util.av.GLMediaPlayer.StreamException; +import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame; + +/** + * A GraphUI {@link GLMediaPlayer} based {@link TexSeqButton} {@link GraphShape}. + *

+ * GraphUI is GPU based and resolution independent. + *

+ *

+ * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + *

+ */ +public class MediaButton extends TexSeqButton { + private boolean verbose = false; + + /** + * @param renderModes + * @param width + * @param height + * @param mPlayer + * @param mPlayerListener + */ + public MediaButton(final int renderModes, final float width, + final float height, final GLMediaPlayer mPlayer) { + super(renderModes, width, height, mPlayer); + setColor(0.8f, 0.8f, 0.8f, 1.0f); + setPressedColorMod(1.1f, 1.1f, 1.1f, 0.7f); + setToggleOffColorMod(0.8f, 0.8f, 0.8f, 1.0f); + setToggleOnColorMod(1.0f, 1.0f, 1.0f, 1.0f); + } + + public void setVerbose(final boolean v) { verbose = v; } + + /** + * Add the default {@link GLMediaEventListener} to {@link #getGLMediaPlayer() this class's GLMediaPlayer}. + */ + public void addDefaultEventListener() { + getGLMediaPlayer().addEventListener(defGLMediaEventListener); + } + + public final GLMediaPlayer getGLMediaPlayer() { return (GLMediaPlayer)texSeq; } + + private final GLMediaEventListener defGLMediaEventListener = new GLMediaEventListener() { + @Override + public void newFrameAvailable(final GLMediaPlayer ts, final TextureFrame newFrame, final long when) { + // texButton.markStateDirty(); + } + + @Override + public void attributesChanged(final GLMediaPlayer mp, final int event_mask, final long when) { + if( verbose ) { + System.err.println("MediaButton AttributesChanges: events_mask 0x"+Integer.toHexString(event_mask)+", when "+when); + System.err.println("MediaButton State: "+mp); + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_INIT & event_mask ) ) { + resetGL = true; + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_SIZE & event_mask ) ) { + // FIXME: mPlayer.resetGLState(); + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_EOS & event_mask ) ) { + new InterruptSource.Thread() { + @Override + public void run() { + // loop for-ever .. + mp.seek(0); + mp.resume(); + } }.start(); + } else if( 0 != ( GLMediaEventListener.EVENT_CHANGE_ERR & event_mask ) ) { + final StreamException se = mp.getStreamException(); + if( null != se ) { + se.printStackTrace(); + } + } + } }; + + + @Override + protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { + ((GLMediaPlayer)texSeq).destroy(gl); + } + + volatile boolean resetGL = true; + + @Override + public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { + final GLMediaPlayer mPlayer = (GLMediaPlayer)texSeq; + if( resetGL ) { + resetGL = false; + try { + mPlayer.initGL(gl); + if( null != region ) { + region.markShapeDirty(); // reset texture data + } + } catch (final Exception e) { + e.printStackTrace(); + } + } + super.draw(gl, renderer, sampleCount); + markStateDirty(); // keep on going + }; + +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/Rectangle.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/Rectangle.java new file mode 100644 index 000000000..a75bd0bc3 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/Rectangle.java @@ -0,0 +1,104 @@ +/** + * 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.shapes; + +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.ui.GraphShape; + +/** + * A GraphUI Rectangle {@link GraphShape} + *

+ * GraphUI is GPU based and resolution independent. + *

+ */ +public class Rectangle extends GraphShape { + private float width, height, lineWidth; + + public Rectangle(final int renderModes, final float width, final float height, final float linewidth) { + super(renderModes); + this.width = width; + this.height = height; + this.lineWidth = linewidth; + } + + public final float getWidth() { return width; } + public final float getHeight() { return height; } + public final float getLineWidth() { return lineWidth; } + + public void setDimension(final float width, final float height, final float lineWidth) { + this.width = width; + this.height = height; + this.lineWidth = lineWidth; + markShapeDirty(); + } + + @Override + protected void addShapeToRegion() { + final OutlineShape shape = new OutlineShape(vertexFactory); + + final float lwh = lineWidth/2f; + + final float tw = getWidth(); + final float th = getHeight(); + + final float twh = tw/2f; + final float twh_o = twh+lwh; + final float twh_i = twh-lwh; + final float thh = th/2f; + final float thh_o = thh+lwh; + final float thh_i = thh-lwh; + + final float ctrX = 0f, ctrY = 0f; + final float ctrZ = 0f; + + // outer (CCW!) + shape.moveTo(ctrX-twh_o, ctrY-thh_o, ctrZ); + shape.lineTo(ctrX+twh_o, ctrY-thh_o, ctrZ); + shape.lineTo(ctrX+twh_o, ctrY+thh_o, ctrZ); + shape.lineTo(ctrX-twh_o, ctrY+thh_o, ctrZ); + shape.closePath(); + + // inner (CCW!) + shape.moveTo(ctrX-twh_i, ctrY-thh_i, ctrZ); + shape.lineTo(ctrX+twh_i, ctrY-thh_i, ctrZ); + shape.lineTo(ctrX+twh_i, ctrY+thh_i, ctrZ); + shape.lineTo(ctrX-twh_i, ctrY+thh_i, ctrZ); + shape.closePath(); + + shape.setIsQuadraticNurbs(); + shape.setSharpness(oshapeSharpness); + region.addOutlineShape(shape, null, rgbaColor); + + box.resize(shape.getBounds()); + } + + @Override + public String getSubString() { + return super.getSubString()+", dim "+getWidth() + " x " + getHeight(); + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/RoundButton.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/RoundButton.java new file mode 100644 index 000000000..a2ab43ae1 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/RoundButton.java @@ -0,0 +1,129 @@ +/** + * 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.shapes; + +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.ui.GraphShape; + +/** + * An abstract GraphUI round Button {@link GraphShape} + *

+ * GraphUI is GPU based and resolution independent. + *

+ *

+ * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + *

+ */ +public abstract class RoundButton extends GraphShape { + + /** {@value} */ + public static final float DEFAULT_CORNER = 1f; + protected float width; + protected float height; + protected float corner = DEFAULT_CORNER; + + protected RoundButton(final int renderModes, final float width, final float height) { + super(renderModes); + this.width = width; + this.height = height; + } + + public final float getWidth() { return width; } + + public final float getHeight() { return height; } + + public final float getCorner() { return corner; } + + public void setSize(final float width, final float height) { + this.width = width; + this.height = height; + markShapeDirty(); + } + + protected void createSharpOutline(final OutlineShape shape, final float zOffset) { + final float tw = getWidth(); + final float th = getHeight(); + + final float minX = 0; + final float minY = 0; + final float minZ = zOffset; + + shape.addVertex(minX, minY, minZ, true); + shape.addVertex(minX+tw, minY, minZ, true); + shape.addVertex(minX+tw, minY + th, minZ, true); + shape.addVertex(minX, minY + th, minZ, true); + shape.closeLastOutline(true); + } + + protected void createCurvedOutline(final OutlineShape shape, final float zOffset) { + final float tw = getWidth(); + final float th = getHeight(); + final float dC = 0.5f*corner*Math.min(tw, th); + + final float minX = 0; + final float minY = 0; + final float minZ = zOffset; + + shape.addVertex(minX, minY + dC, minZ, true); + shape.addVertex(minX, minY, minZ, false); + + shape.addVertex(minX + dC, minY, minZ, true); + + shape.addVertex(minX + tw - dC, minY, minZ, true); + shape.addVertex(minX + tw, minY, minZ, false); + shape.addVertex(minX + tw, minY + dC, minZ, true); + shape.addVertex(minX + tw, minY + th- dC, minZ, true); + shape.addVertex(minX + tw, minY + th, minZ, false); + shape.addVertex(minX + tw - dC, minY + th, minZ, true); + shape.addVertex(minX + dC, minY + th, minZ, true); + shape.addVertex(minX, minY + th, minZ, false); + shape.addVertex(minX, minY + th - dC, minZ, true); + + shape.closeLastOutline(true); + } + + /** Set corner size, default is {@link #DEFAULT_CORNER} */ + public void setCorner(final float corner) { + if(corner > 1.0f){ + this.corner = 1.0f; + } + else if(corner < 0.01f){ + this.corner = 0.0f; + } + else{ + this.corner = corner; + } + markShapeDirty(); + } + + @Override + public String getSubString() { + return super.getSubString()+", dim "+getWidth() + " x " + getHeight() + ", corner " + corner; + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/TexSeqButton.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/TexSeqButton.java new file mode 100644 index 000000000..2f7d85198 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/TexSeqButton.java @@ -0,0 +1,82 @@ +/** + * Copyright 2014-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.shapes; + +import com.jogamp.opengl.GLProfile; +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.GLRegion; +import com.jogamp.graph.ui.GraphShape; +import com.jogamp.opengl.util.texture.TextureSequence; + +/** + * An abstract GraphUI {@link TextureSequence} {@link RoundButton} {@link GraphShape}. + *

+ * GraphUI is GPU based and resolution independent. + *

+ *

+ * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + *

+ */ +public abstract class TexSeqButton extends RoundButton { + protected final TextureSequence texSeq; + + public TexSeqButton(final int renderModes, final float width, + final float height, final TextureSequence texSeq) { + super(renderModes | Region.COLORTEXTURE_RENDERING_BIT, width, height); + this.texSeq = texSeq; + } + + @Override + protected GLRegion createGLRegion(final GLProfile glp) { + return GLRegion.create(glp, getRenderModes(), texSeq); + } + + public final TextureSequence getTextureSequence() { return this.texSeq; } + + @Override + protected void addShapeToRegion() { + final OutlineShape shape = new OutlineShape(vertexFactory); + if(corner == 0.0f) { + createSharpOutline(shape, 0f); + } else { + createCurvedOutline(shape, 0f); + } + shape.setIsQuadraticNurbs(); + shape.setSharpness(oshapeSharpness); + region.addOutlineShape(shape, null, rgbaColor); + box.resize(shape.getBounds()); + + setRotationPivot( box.getCenter() ); + + if( DEBUG_DRAW ) { + System.err.println("XXX.UIShape.TextureSeqButton: Added Shape: "+shape+", "+box); + } + } +} -- cgit v1.2.3