diff options
5 files changed, 419 insertions, 116 deletions
diff --git a/make/scripts/tests.sh b/make/scripts/tests.sh index 69ff56d40..9724c7632 100644 --- a/make/scripts/tests.sh +++ b/make/scripts/tests.sh @@ -1014,9 +1014,9 @@ function testawtswt() { #testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo01 $* #testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo01b $* #testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo02 $* -#testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo03 $* +testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo03 $* #testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo10 $* -testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo20 $* +#testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo20 $* #testnoawt com.jogamp.opengl.demos.av.MovieCube $* #testnoawt com.jogamp.opengl.demos.av.MovieSimple $* diff --git a/src/demos/com/jogamp/opengl/demos/graph/ui/GraphUIDemoArgs.java b/src/demos/com/jogamp/opengl/demos/graph/ui/GraphUIDemoArgs.java new file mode 100644 index 000000000..dd8abceec --- /dev/null +++ b/src/demos/com/jogamp/opengl/demos/graph/ui/GraphUIDemoArgs.java @@ -0,0 +1,90 @@ +/** + * 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.opengl.demos.graph.ui; + +import com.jogamp.graph.curve.Region; +import com.jogamp.opengl.demos.util.MiscUtils; + +public class GraphUIDemoArgs { + public int surface_width, surface_height; + public boolean wait_to_start = false; + public boolean keepRunning = false; + public int renderModes; + public int sceneMSAASamples = 4; + + public GraphUIDemoArgs(final int width, final int height, final int renderModes) { + this.surface_width = width; + this.surface_height = height; + this.renderModes = renderModes; + } + public void parse(final String[] args) { + final int[] idx = { 0 }; + for (idx[0] = 0; idx[0] < args.length; ++idx[0]) { + parse(args, idx); + } + } + public boolean parse(final String[] args, final int[] idx) { + if( 0 > idx[0] || idx[0] >= args.length ) { + return false; + } + boolean res = true; + if (args[idx[0]].equals("-keep")) { + keepRunning = true; + } else if (args[idx[0]].equals("-hhd")) { + surface_width = 1280; + surface_height = 720; + } else if (args[idx[0]].equals("-fhd")) { + surface_width = 1920; + surface_height = 1080; + } else if (args[idx[0]].equals("-w")) { + ++idx[0]; + surface_width = MiscUtils.atoi(args[idx[0]], surface_width); + } else if (args[idx[0]].equals("-h")) { + ++idx[0]; + surface_height = MiscUtils.atoi(args[idx[0]], surface_height); + } else if(args[idx[0]].equals("-wait")) { + wait_to_start = true; + } else if(args[idx[0]].equals("-gnone")) { + sceneMSAASamples = 0; + renderModes = 0; + } else if(args[idx[0]].equals("-smsaa")) { + ++idx[0]; + sceneMSAASamples = MiscUtils.atoi(args[idx[0]], sceneMSAASamples); + renderModes = 0; + } else if(args[idx[0]].equals("-gmsaa")) { + sceneMSAASamples = 0; + renderModes = Region.MSAA_RENDERING_BIT; + } else if(args[idx[0]].equals("-gvbaa")) { + sceneMSAASamples = 0; + renderModes = Region.VBAA_RENDERING_BIT; + } else { + res = false; + } + return res; + } +} diff --git a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java index 825cc61d1..86c2122c5 100644 --- a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java +++ b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java @@ -39,6 +39,7 @@ import com.jogamp.graph.font.Font; import com.jogamp.graph.font.FontFactory; import com.jogamp.graph.ui.gl.Scene; import com.jogamp.graph.ui.gl.Scene.PMVMatrixSetup; +import com.jogamp.graph.ui.gl.shapes.GlyphShape; import com.jogamp.graph.ui.gl.shapes.Label; import com.jogamp.newt.MonitorDevice; import com.jogamp.newt.event.KeyAdapter; @@ -52,8 +53,10 @@ import com.jogamp.opengl.GLProfile; import com.jogamp.opengl.demos.graph.FontSetDemos; import com.jogamp.opengl.demos.util.MiscUtils; import com.jogamp.opengl.fixedfunc.GLMatrixFunc; +import com.jogamp.opengl.math.FloatUtil; import com.jogamp.opengl.math.Quaternion; import com.jogamp.opengl.math.Vec3f; +import com.jogamp.opengl.math.VectorUtil; import com.jogamp.opengl.math.geom.AABBox; import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.util.PMVMatrix; @@ -65,19 +68,20 @@ import com.jogamp.opengl.util.PMVMatrix; * each glyph coming from from a random 3D point moving to its destination all at once. * </p> * <p> - * Pass '-keep' to main-function to keep running. + * - Pass '-keep' to main-function to keep running. + * - Pass '-aspeed' to vary velocity * </p> */ public class UISceneDemo03 { - // final String originalText = "JOGL, Java™ Binding for the OpenGL® API"; + static final boolean DEBUG = false; + static final String[] originalTexts = { "JOGL, Java™ Binding for the OpenGL® API", "GraphUI, Resolution Independent Curves", "JogAmp, Java™ libraries for 3D & Media" }; - static int renderModes = Region.MSAA_RENDERING_BIT; // Region.VBAA_RENDERING_BIT; - static int sceneMSAASamples = 0; + static GraphUIDemoArgs options = new GraphUIDemoArgs(1280, 720, 0); static float velocity = 30 / 1e3f; // [m]/[s] static float rot_step = velocity * 1; @@ -87,42 +91,26 @@ public class UISceneDemo03 { } public static void main(final String[] args) throws IOException { - final int surface_width = 1280, surface_height = 720; final GLProfile reqGLP = GLProfile.getGL2ES2(); int autoSpeed = 0; - boolean wait_to_start = false; - boolean keepRunning = false; if (0 != args.length) { - for (int i = 0; i < args.length; i++) { - if (args[i].equals("-keep")) { - keepRunning = true; - } else if (args[i].equals("-v")) { - ++i; - setVelocity(MiscUtils.atoi(args[i], (int) velocity * 1000) / 1000f); - } else if(args[i].equals("-aspeed")) { + final int[] idx = { 0 }; + for (idx[0] = 0; idx[0] < args.length; ++idx[0]) { + if( options.parse(args, idx) ) { + continue; + } else if (args[idx[0]].equals("-v")) { + ++idx[0]; + setVelocity(MiscUtils.atoi(args[idx[0]], (int) velocity * 1000) / 1000f); + } else if(args[idx[0]].equals("-aspeed")) { autoSpeed = -1; setVelocity(80/1000f); - keepRunning = true; - } else if(args[i].equals("-wait")) { - wait_to_start = true; - } else if(args[i].equals("-gnone")) { - sceneMSAASamples = 0; - renderModes = 0; - } else if(args[i].equals("-smsaa")) { - i++; - sceneMSAASamples = MiscUtils.atoi(args[i], sceneMSAASamples); - renderModes = 0; - } else if(args[i].equals("-gmsaa")) { - sceneMSAASamples = 0; - renderModes = Region.MSAA_RENDERING_BIT; - } else if(args[i].equals("-gvbaa")) { - sceneMSAASamples = 0; - renderModes = Region.VBAA_RENDERING_BIT; + options.keepRunning = true; } } } // renderModes |= Region.COLORCHANNEL_RENDERING_BIT; + System.err.println("RenderModes: "+Region.getRenderModeString(options.renderModes)); // // Resolution independent, no screen size @@ -141,14 +129,14 @@ public class UISceneDemo03 { final GLCapabilities caps = new GLCapabilities(reqGLP); caps.setAlphaBits(4); - if( sceneMSAASamples > 0 ) { + if( options.sceneMSAASamples > 0 ) { caps.setSampleBuffers(true); - caps.setNumSamples(sceneMSAASamples); + caps.setNumSamples(options.sceneMSAASamples); } System.out.println("Requested: " + caps); final GLWindow window = GLWindow.create(caps); - window.setSize(surface_width, surface_height); + window.setSize(options.surface_width, options.surface_height); window.setTitle(UISceneDemo03.class.getSimpleName() + ": " + window.getSurfaceWidth() + " x " + window.getSurfaceHeight()); window.setVisible(true); window.addGLEventListener(scene); @@ -210,27 +198,20 @@ public class UISceneDemo03 { System.err.println("SceneBox " + sceneBox); System.err.println("Frustum " + scene.getMatrix().glGetFrustum()); - if( wait_to_start ) { + if( options.wait_to_start ) { MiscUtils.waitForKey("Start"); } // // // - final float fontScale; - { - final AABBox fbox = font.getGlyphBounds(originalTexts[0]); - fontScale = sceneBox.getWidth() / fbox.getWidth(); - // final float fontScale = sceneBox.getWidth() / 20; - System.err.println("FontScale: " + fontScale + " = " + sceneBox.getWidth() + " / " + fbox.getWidth()); - } final Label statusLabel; { final AABBox fbox = fontStatus.getGlyphBounds("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); final float statusLabelScale = sceneBox.getWidth() / fbox.getWidth(); System.err.println("StatusLabelScale: " + statusLabelScale + " = " + sceneBox.getWidth() + " / " + fbox.getWidth() + ", " + fbox); - statusLabel = new Label(renderModes, fontStatus, "Nothing there yet"); + statusLabel = new Label(options.renderModes, fontStatus, "Nothing there yet"); statusLabel.setScale(statusLabelScale, statusLabelScale, 1f); statusLabel.setColor(0.1f, 0.1f, 0.1f, 1.0f); statusLabel.moveTo(sceneBox.getMinX(), sceneBox.getMinY() + statusLabelScale * (fontStatus.getMetrics().getLineGap() - fontStatus.getMetrics().getDescent()), 0f); @@ -241,10 +222,8 @@ public class UISceneDemo03 { // Setup the moving glyphs // - final List<Label> glyphs = new ArrayList<Label>(); - final List<Label> addedGlyphs = new ArrayList<Label>(); - final List<Vec3f> glyphsTarget = new ArrayList<Vec3f>(); - final List<Vec3f> glyphsPos = new ArrayList<Vec3f>(); + final List<GlyphShape> glyphShapes = new ArrayList<GlyphShape>(); + final List<GlyphShape> addedGlyphShapes = new ArrayList<GlyphShape>(); final float pixPerMM, dpiV; { @@ -258,10 +237,16 @@ public class UISceneDemo03 { int txt_idx = 0; do { + final float fontScale; + { + final AABBox fbox = font.getGlyphBounds(originalTexts[txt_idx]); + fontScale = sceneBox.getWidth() / fbox.getWidth(); + System.err.println("FontScale: " + fontScale + " = " + sceneBox.getWidth() + " / " + fbox.getWidth()); + } z_only = !z_only; window.invoke(true, (drawable) -> { - scene.removeShapes(drawable.getGL().getGL2ES2(), addedGlyphs); - addedGlyphs.clear(); + scene.removeShapes(drawable.getGL().getGL2ES2(), addedGlyphShapes); + addedGlyphShapes.clear(); return true; }); @@ -269,58 +254,40 @@ public class UISceneDemo03 { { final Random random = new Random(); - final Label destText = new Label(renderModes, font, fontScale, ""); - // destText.setScale(fontScale, fontScale, 1f); - destText.setColor(0.1f, 0.1f, 0.1f, 1); - - destText.setText(hasGLP, "X"); + final GlyphShape testGlyph = new GlyphShape(options.renderModes, font, 'X', 0, 0); + testGlyph.setScale(fontScale, fontScale, 1f); + testGlyph.validate(hasGLP); final PMVMatrix pmv = new PMVMatrix(); - final int[] movingGlyphSizePx = destText.getSurfaceSize(scene, pmv, new int[2]); // [px] - movingGlyphPixPerShapeUnit = destText.getPixelPerShapeUnit(movingGlyphSizePx, new float[2]); // [px]/[shapeUnit] - destText.setText(""); - - Font.Glyph left_glyph = null; - for (int idx = 0; idx < originalTexts[txt_idx].length(); ++idx) { - final String movingChar = originalTexts[txt_idx].substring(idx, idx + 1); - destText.validate(hasGLP); - final Label movingGlyph = new Label(renderModes, font, fontScale, movingChar); - // movingGlyph.setScale(fontScale, fontScale, 1f); - movingGlyph.setColor(0.1f, 0.1f, 0.1f, 1); - movingGlyph.validate(hasGLP); - final Font.Glyph glyph = font.getGlyph( font.getGlyphID(movingChar.charAt(0)) ); - float end_pos_x = sceneBox.getMinX() + (destText.getText().isEmpty() ? 0 : destText.getScaledWidth()); - if( !glyph.isWhiteSpace() ) { - if( null != left_glyph ) { - end_pos_x += left_glyph.getKerning(glyph.getID()) * fontScale; - } - left_glyph = glyph; - } else { - left_glyph = null; - } - final float end_pos_y = glyph.getBounds().getMinY() * fontScale; - final float end_pos_z = 0f; - final float start_pos_x = z_only ? end_pos_x : + final int[] movingGlyphSizePx = testGlyph.getSurfaceSize(scene, pmv, new int[2]); // [px] + movingGlyphPixPerShapeUnit = testGlyph.getPixelPerShapeUnit(movingGlyphSizePx, new float[2]); // [px]/[shapeUnit] + + final AABBox box = GlyphShape.processString(glyphShapes, options.renderModes, font, originalTexts[txt_idx]); + System.err.println("Shapes: "+box); + for(final GlyphShape gs : glyphShapes) { + gs.setScale(fontScale, fontScale, 1f); + gs.setColor(0.1f, 0.1f, 0.1f, 1); + final Vec3f target = gs.getOrigPos(fontScale).add(sceneBox.getMinX(), 0f, 0f); + + final float start_pos_x = z_only ? target.x() : sceneBox.getMinX() + random.nextFloat() * sceneBox.getWidth(); - final float start_pos_y = z_only ? end_pos_y : + final float start_pos_y = z_only ? target.y() : sceneBox.getMinY() + random.nextFloat() * sceneBox.getHeight(); final float start_pos_z = 0f + random.nextFloat() * sceneBox.getHeight() * 1f; - glyphsTarget.add( new Vec3f(end_pos_x, end_pos_y, end_pos_z) ); - glyphsPos.add( new Vec3f(start_pos_x, start_pos_y, start_pos_z) ); - movingGlyph.moveTo(start_pos_x, start_pos_y, start_pos_z); - glyphs.add(movingGlyph); - - destText.setText( destText.getText() + movingGlyph.getText() ); + gs.moveTo(start_pos_x, start_pos_y, start_pos_z); } // just add destText to scene to be cleaned up, invisible - destText.setEnabled(false); - scene.addShape(destText); + testGlyph.setEnabled(false); + scene.addShape(testGlyph); } - scene.addShapes(glyphs); - addedGlyphs.addAll(glyphs); + scene.addShapes(glyphShapes); + addedGlyphShapes.addAll(glyphShapes); + + final float pos_eps = FloatUtil.EPSILON * 5000; // ~= 0.0005960 + final float rot_eps = FloatUtil.adegToRad(1f); // 1 adeg ~= 0.01745 rad final long t0_us = Clock.currentNanos() / 1000; // [us] final long[] t2_us = { t0_us }; - while (!glyphs.isEmpty()) { + while (!glyphShapes.isEmpty()) { window.invoke(true, (drawable) -> { final long t3_us = Clock.currentNanos() / 1000; final float dt_s = (t3_us - t2_us[0]) / 1e6f; @@ -330,36 +297,66 @@ public class UISceneDemo03 { final float velovity_obj = velocity_px / movingGlyphPixPerShapeUnit[0]; // [shapeUnit]/[s] final float dxy = velovity_obj * dt_s; // [shapeUnit] - for (int idx = glyphs.size() - 1; 0 <= idx; --idx) { - final Label glyph = glyphs.get(idx); - final Vec3f pos = glyphsPos.get(idx); - final Vec3f target = glyphsTarget.get(idx); + for (int idx = glyphShapes.size() - 1; 0 <= idx; --idx) { + final GlyphShape glyph = glyphShapes.get(idx); + final Vec3f pos = new Vec3f(glyph.getPosition()); + final Vec3f target = glyph.getOrigPos(fontScale).add(sceneBox.getMinX(), 0f, 0f); final Vec3f p_t = target.minus(pos); - if ( p_t.length() <= glyph.getBounds().getSize() / 5f && - Math.abs( glyph.getRotation().getY() ) <= 0.4f ) - { + final float p_t_diff = p_t.length(); + final Quaternion q = glyph.getRotation(); + final float radY = q.toAngleAxis(VectorUtil.VEC3_UNIT_Y); + final float radYdiff = Math.min(radY, FloatUtil.TWO_PI - radY); + final boolean pos_ok = p_t_diff <= pos_eps; + final boolean pos_near = p_t_diff <= glyph.getBounds().getSize() * fontScale * 2f; + final boolean rot_ok = pos_near && ( radYdiff <= rot_eps || radYdiff <= rot_step * 2f ); + if ( pos_ok && rot_ok ) { // arrived - glyph.moveTo(target.x(), target.y(), 0f); - glyph.getRotation().setIdentity(); - glyphs.remove(idx); - glyphsPos.remove(idx); - glyphsTarget.remove(idx); + glyph.moveTo(target.x(), target.y(), target.z()); + q.setIdentity(); + glyphShapes.remove(idx); continue; } - p_t.normalize(); - pos.add(p_t.scale(dxy)); - glyph.moveTo(pos.x(), pos.y(), pos.z()); - final Quaternion rot = glyph.getRotation(); - rot.rotateByAngleY(rot_step); + if( !pos_ok ) { + if( DEBUG ) { + if( 0 == idx ) { + System.err.println("p_t_diff: "+p_t_diff+", dxy "+dxy); + } + } + if( p_t_diff <= dxy || p_t_diff <= pos_eps ) { + glyph.moveTo(target.x(), target.y(), target.z()); + } else { + p_t.normalize(); + pos.add( p_t.scale( dxy ) ); + glyph.moveTo(pos.x(), pos.y(), pos.z()); + } + if( !rot_ok ) { + if( pos_near ) { + q.rotateByAngleY( rot_step * 2f ); + } else { + q.rotateByAngleY( rot_step ); + } + } + } else { + if( DEBUG ) { + if( 0 == idx ) { + System.err.println("rot: "+radY+" ("+FloatUtil.radToADeg(radY)+"), diff "+radYdiff+" ("+FloatUtil.radToADeg(radYdiff)+"), step "+rot_step+" ("+FloatUtil.radToADeg(rot_step)+")"); + } + } + if( radYdiff <= rot_step * 3f || radYdiff <= rot_eps ) { + q.setIdentity(); + } else { + q.rotateByAngleY( rot_step * 3f ); + } + } } final String text = String.format("%s, v %.1f mm/s, r %.3f", - scene.getStatusText(drawable, renderModes, 0, dpiV), velocity * 1e3f, rot_step); + scene.getStatusText(drawable, options.renderModes, 0, dpiV), velocity * 1e3f, rot_step); statusLabel.setText(text); return true; }); } final float has_dur_s = ((Clock.currentNanos() / 1000) - t0_us) / 1e6f; // [us] - System.err.printf("Text travel-duration %.3f s, %d chars, %.3f s/char%n", has_dur_s, originalTexts[txt_idx].length(), has_dur_s / originalTexts[txt_idx].length()); + System.err.printf("Text travel-duration %.3f s, %d chars%n", has_dur_s, originalTexts[txt_idx].length()); try { Thread.sleep(2000); } catch (final InterruptedException e1) { } if( autoSpeed > 0 ) { if( velocity < 60/1000f ) { @@ -377,8 +374,8 @@ public class UISceneDemo03 { } } txt_idx = ( txt_idx + 1 ) % originalTexts.length; - } while (keepRunning && window.isNativeValid()); - if (!keepRunning) { + } while (options.keepRunning && window.isNativeValid()); + if (!options.keepRunning) { window.destroy(); } } diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java b/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java index 509479cc9..43d174916 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java @@ -209,19 +209,31 @@ public abstract class Shape { System.arraycopy(pivot, 0, rotPivot, 0, 3); } - /** Set scale factor to given scale. */ + /** + * 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. */ + /** + * 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. */ + /** + * 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]; } 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 new file mode 100644 index 000000000..07c8ae0de --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/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.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<GlyphShape> 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 + "'"; + } +} |