diff options
author | Sven Gothel <[email protected]> | 2023-03-30 04:41:51 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-03-30 04:41:51 +0200 |
commit | d959e28119a5a973968d47a988d3dd4b6320db87 (patch) | |
tree | 200f7e776d39a1735a495768131575285bd74714 /src/demos/com | |
parent | 901df212f75db8cf51349f53abeaed6ef62b61d3 (diff) |
GraphUI: Add GlyphShape representing a single Font.Glyph as a GraphShape; Use w/ UISceneDemo03 Type Animation...
A list of GlyphShape can be created via GlyphShape.processString(..),
which preserves all details incl. intended original unscaled position and its kerning.
Whitespace or contourless Glyphs are dropped.
A GlyphShape is represented in font em-size [0..1] unscaled.
+++
UISceneDemo03 Type Animation
- Using GlyphShape and apply scaling via its Shape.setScale()
- Recalc fontScale per used text
- Refined 'arrival' criteria and smoothing out near target w/ speed-up rotation
- Using GraphUIDemoArgs to parse common commandline demo options
Diffstat (limited to 'src/demos/com')
-rw-r--r-- | src/demos/com/jogamp/opengl/demos/graph/ui/GraphUIDemoArgs.java | 90 | ||||
-rw-r--r-- | src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java | 219 |
2 files changed, 198 insertions, 111 deletions
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(); } } |