summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--make/scripts/tests.sh4
-rw-r--r--src/demos/com/jogamp/opengl/demos/graph/ui/GraphUIDemoArgs.java90
-rw-r--r--src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java219
-rw-r--r--src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java18
-rw-r--r--src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GlyphShape.java204
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 + "'";
+ }
+}