aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--make/scripts/tests.sh5
-rw-r--r--src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java691
-rw-r--r--src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03b.java563
-rw-r--r--src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo20.java16
-rw-r--r--src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemoU01a.java1
-rw-r--r--src/demos/com/jogamp/opengl/demos/util/CommandlineOptions.java15
-rw-r--r--src/demos/com/jogamp/opengl/demos/util/MiscUtils.java19
-rw-r--r--src/graphui/classes/com/jogamp/graph/ui/AnimGroup.java812
8 files changed, 1338 insertions, 784 deletions
diff --git a/make/scripts/tests.sh b/make/scripts/tests.sh
index 7a9481b55..3888edc1c 100644
--- a/make/scripts/tests.sh
+++ b/make/scripts/tests.sh
@@ -201,7 +201,7 @@ function jrun() {
#D_ARGS="-Dnativewindow.debug.JAWT -Djogl.debug.GLCanvas -Djogl.debug.GLJPanel -Dnewt.debug.Window"
#D_ARGS="-Dnativewindow.debug.JAWT -Djogamp.debug.TaskBase.TraceSource"
#D_ARGS="-Dnativewindow.debug.JAWT"
- D_ARGS="-Djogl.debug.GLJPanel"
+ #D_ARGS="-Djogl.debug.GLJPanel"
#D_ARGS="-Djogl.debug.GLContext.TraceSwitch"
#D_ARGS="-Djogl.debug.GLContext -Djogl.debug.GLContext.TraceSwitch"
#D_ARGS="-Djogl.debug.DebugGL -Djogl.debug.TraceGL -Djogl.debug.FixedFuncPipeline -Djogl.debug.GLSLState -Djogl.debug.GLSLCode"
@@ -999,8 +999,7 @@ 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.UISceneDemo03b $*
+testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo03 $*
#testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo10 $*
#testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo11 $*
#testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo20 $*
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 e1febc560..e4bde4c3e 100644
--- a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java
+++ b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java
@@ -28,31 +28,42 @@
package com.jogamp.opengl.demos.graph.ui;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
import java.util.Random;
import com.jogamp.common.os.Clock;
import com.jogamp.common.util.IOUtil;
-import com.jogamp.common.util.InterruptSource;
import com.jogamp.graph.curve.Region;
import com.jogamp.graph.font.Font;
import com.jogamp.graph.font.FontFactory;
+import com.jogamp.graph.ui.GraphShape;
import com.jogamp.graph.ui.Group;
import com.jogamp.graph.ui.Scene;
+import com.jogamp.graph.ui.Shape;
+import com.jogamp.graph.ui.AnimGroup;
import com.jogamp.graph.ui.Scene.PMVMatrixSetup;
-import com.jogamp.graph.ui.shapes.GlyphShape;
+import com.jogamp.graph.ui.layout.Alignment;
+import com.jogamp.graph.ui.layout.Gap;
+import com.jogamp.graph.ui.layout.GridLayout;
+import com.jogamp.graph.ui.shapes.Button;
import com.jogamp.graph.ui.shapes.Label;
+import com.jogamp.graph.ui.shapes.Rectangle;
import com.jogamp.newt.MonitorDevice;
import com.jogamp.newt.event.KeyAdapter;
import com.jogamp.newt.event.KeyEvent;
+import com.jogamp.newt.event.MouseAdapter;
+import com.jogamp.newt.event.MouseEvent;
import com.jogamp.newt.event.WindowAdapter;
import com.jogamp.newt.event.WindowEvent;
import com.jogamp.newt.opengl.GLWindow;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2ES2;
+import com.jogamp.opengl.GLAnimatorControl;
+import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
+import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLProfile;
+import com.jogamp.opengl.JoglVersion;
import com.jogamp.opengl.demos.graph.FontSetDemos;
import com.jogamp.opengl.demos.util.CommandlineOptions;
import com.jogamp.opengl.demos.util.MiscUtils;
@@ -61,19 +72,28 @@ import com.jogamp.opengl.math.FloatUtil;
import com.jogamp.opengl.math.Quaternion;
import com.jogamp.opengl.math.Recti;
import com.jogamp.opengl.math.Vec3f;
+import com.jogamp.opengl.math.Vec4f;
import com.jogamp.opengl.math.geom.AABBox;
import com.jogamp.opengl.util.Animator;
import com.jogamp.opengl.util.PMVMatrix;
/**
- * Res independent Shape, Scene attached to GLWindow showing simple linear Shape movement.
+ * Res independent Shape, Scene attached to GLWindow showing multiple animated shape movements.
* <p>
- * This variation of {@link UISceneDemo00} shows a text animation assembling one line of text,
- * each glyph coming from from a random 3D point moving to its destination all at once.
+ * This variation of {@link UISceneDemo00} shows
+ * <ul>
+ * <li>Two repetitive scrolling text lines. One text shorter than the line-width and one longer.</li>
+ * <li>One line of animated rectangles, rotating around their z-axis</li>
+ * <li>A text animation assembling one line of text,
+ * each glyph coming from from a random 3D point moving to its destination all at once including rotation.</li>
+ * <li>One line of text with sine wave animation.</li>
+ * </ul>
* </p>
* <p>
* - Pass '-keep' to main-function to keep running.
* - Pass '-aspeed' to vary velocity
+ * - Pass '-rspeed <float>' angular velocity in radians/s
+ * - Pass '-no_anim_box' to not show a visible and shrunken box around the AnimGroup
* </p>
*/
public class UISceneDemo03 {
@@ -86,16 +106,24 @@ public class UISceneDemo03 {
};
static CommandlineOptions options = new CommandlineOptions(1280, 720, Region.VBAA_RENDERING_BIT);
+ static float frame_velocity = 5f / 1e3f; // [m]/[s]
static float velocity = 30 / 1e3f; // [m]/[s]
- static float rot_step = velocity * 1;
+ static float ang_velo = velocity * 60f; // [radians]/[s]
+ static int autoSpeed = -1;
+
+ static final int[] manualScreenShorCount = { 0 };
static void setVelocity(final float v) {
velocity = v; // Math.max(1/1e3f, v);
- rot_step = velocity * 1;
+ ang_velo = velocity * 60f;
+ autoSpeed = 0;
}
public static void main(final String[] args) throws IOException {
- int autoSpeed = 0;
+ setVelocity(80/1000f);
+ autoSpeed = -1;
+ options.keepRunning = true;
+ boolean showAnimBox = true;
if (0 != args.length) {
final int[] idx = { 0 };
@@ -106,12 +134,18 @@ public class UISceneDemo03 {
++idx[0];
setVelocity(MiscUtils.atoi(args[idx[0]], (int) velocity * 1000) / 1000f);
} else if(args[idx[0]].equals("-aspeed")) {
- autoSpeed = -1;
setVelocity(80/1000f);
+ autoSpeed = -1;
options.keepRunning = true;
+ } else if(args[idx[0]].equals("-rspeed")) {
+ ++idx[0];
+ ang_velo = MiscUtils.atof(args[idx[0]], ang_velo);
+ } else if(args[idx[0]].equals("-no_anim_box")) {
+ showAnimBox = false;
}
}
}
+ System.err.println(JoglVersion.getInstance().toString());
// renderModes |= Region.COLORCHANNEL_RENDERING_BIT;
System.err.println(options);
@@ -128,14 +162,14 @@ public class UISceneDemo03 {
final Scene scene = new Scene();
scene.setClearParams(new float[] { 1f, 1f, 1f, 1f }, GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
- scene.setPMVMatrixSetup(new MyPMVMatrixSetup());
+ scene.setPMVMatrixSetup( new MyPMVMatrixSetup(Scene.DEFAULT_SCENE_DIST) );
scene.setDebugBorderBox(options.debugBoxThickness);
- final Group glyphGroup = new Group();
- scene.addShape(glyphGroup);
+ final AnimGroup animGroup = new AnimGroup(null);
+ scene.addShape(animGroup);
- // scene.setFrustumCullingEnabled(true);
- glyphGroup.setFrustumCullingEnabled(true);
+ scene.setFrustumCullingEnabled(true);
+ animGroup.setFrustumCullingEnabled(true);
final Animator animator = new Animator(0 /* w/o AWT */);
animator.setUpdateFPSFrames(1 * 60, null); // System.err);
@@ -153,46 +187,18 @@ public class UISceneDemo03 {
window.setTitle(UISceneDemo03.class.getSimpleName() + ": " + window.getSurfaceWidth() + " x " + window.getSurfaceHeight());
window.setVisible(true);
window.addGLEventListener(scene);
- window.addWindowListener(new WindowAdapter() {
- @Override
- public void windowResized(final WindowEvent e) {
- window.setTitle(UISceneDemo03.class.getSimpleName() + ": " + window.getSurfaceWidth() + " x " + window.getSurfaceHeight());
- }
-
- @Override
- public void windowDestroyNotify(final WindowEvent e) {
- animator.stop();
- }
- });
- window.addKeyListener(new KeyAdapter() {
- @Override
- public void keyReleased(final KeyEvent e) {
- final short keySym = e.getKeySymbol();
- if (keySym == KeyEvent.VK_PLUS ||
- keySym == KeyEvent.VK_ADD)
- {
- if (e.isShiftDown()) {
- setVelocity(velocity + 10 / 1000f);
- } else {
- setVelocity(velocity + 1 / 1000f);
- }
- } else if (keySym == KeyEvent.VK_MINUS ||
- keySym == KeyEvent.VK_SUBTRACT)
- {
- if (e.isShiftDown()) {
- setVelocity(velocity - 10 / 1000f);
- } else {
- setVelocity(velocity - 1 / 1000f);
- }
- } else if( keySym == KeyEvent.VK_F4 || keySym == KeyEvent.VK_ESCAPE || keySym == KeyEvent.VK_Q ) {
- new InterruptSource.Thread( () -> { window.destroy(); } ).start();
- }
- }
- });
-
scene.attachInputListenerTo(window);
+ final float pixPerMM, dpiV;
+ {
+ final float[] tmp = window.getPixelsPerMM(new float[2]);
+ pixPerMM = tmp[0]; // [px]/[mm]
+ final float[] sDPI = MonitorDevice.perMMToPerInch( tmp );
+ dpiV = sDPI[1];
+ }
+
animator.add(window);
+ animator.setExclusiveContext(options.exclusiveContext);
animator.start();
//
@@ -202,18 +208,44 @@ public class UISceneDemo03 {
// - Compute the animation values with DPI
scene.waitUntilDisplayed();
- scene.setFrustumCullingEnabled(true);
window.invoke(true, (drawable) -> {
final GL gl = drawable.getGL();
gl.glEnable(GL.GL_DEPTH_TEST);
+ // gl.glDepthFunc(GL.GL_LEQUAL);
+ // gl.glEnable(GL.GL_BLEND);
return true;
});
final GLProfile hasGLP = window.getChosenGLCapabilities().getGLProfile();
final AABBox sceneBox = scene.getBounds();
+ final float sceneBoxFrameWidth;
+ {
+ sceneBoxFrameWidth = sceneBox.getWidth() * 0.0025f;
+ final GraphShape r = new Rectangle(options.renderModes, sceneBox, sceneBoxFrameWidth);
+ if( showAnimBox ) {
+ r.setColor(0.45f, 0.45f, 0.45f, 0.9f);
+ } else {
+ r.setColor(0f, 0f, 0f, 0f);
+ }
+ r.setInteractive(false);
+ animGroup.addShape( r );
+ }
+ animGroup.setRotationPivot(0, 0, 0);
+ if( showAnimBox ) {
+ animGroup.scale(0.85f, 0.85f, 1f);
+ animGroup.move(-sceneBox.getWidth()/2f*0.075f, 0f, 0f);
+ animGroup.getRotation().rotateByAngleY(0.1325f);
+ } else {
+ animGroup.scale(1.0f, 1.0f, 1f);
+ }
+ animGroup.validate(hasGLP);
+ animGroup.setInteractive(false);
+ animGroup.setToggleable(true);
+ animGroup.setResizable(false);
+ animGroup.setToggle( false );
System.err.println("SceneBox " + sceneBox);
System.err.println("Frustum " + scene.getMatrix().getFrustum());
- System.err.println("GlyphGroup.0: "+glyphGroup);
+ System.err.println("AnimGroup.0: "+animGroup);
final Label statusLabel;
{
@@ -227,6 +259,7 @@ public class UISceneDemo03 {
statusLabel.moveTo(sceneBox.getMinX(), sceneBox.getMinY() + statusLabelScale * (fontStatus.getMetrics().getLineGap() - fontStatus.getMetrics().getDescent()), 0f);
scene.addShape(statusLabel);
}
+ sub01SetupWindowListener(window, scene, animGroup, statusLabel, dpiV);
{
final StringBuilder sb = new StringBuilder();
@@ -248,194 +281,452 @@ public class UISceneDemo03 {
window.invoke(true, (drawable) -> {
final GL2ES2 gl = drawable.getGL().getGL2ES2();
- scene.screenshot(drawable.getGL(), scene.nextScreenshotFile(null, UISceneDemo03.class.getSimpleName(), options.renderModes, drawable.getChosenGLCapabilities(), null));
+ scene.screenshot(gl, scene.nextScreenshotFile(null, UISceneDemo03.class.getSimpleName(), options.renderModes, window.getChosenGLCapabilities(), null));
scene.removeShape(gl, l);
return true;
});
}
//
+ // HUD UI
+ //
+ sub02AddUItoScene(scene, animGroup, 2, window);
+
+ //
// Setup the moving glyphs
//
+ final boolean[] z_only = { true };
+ int txt_idx = 0;
- final List<GlyphShape> glyphShapes = new ArrayList<GlyphShape>();
+ final AABBox animBox = new AABBox( animGroup.getBounds() );
+ final float g_w = animBox.getWidth();
+ System.err.println("AnimBox " + animBox);
+ System.err.println("AnimGroup.1 " + animGroup);
- final float pixPerMM, dpiV;
- {
- final float[] tmp = window.getPixelsPerMM(new float[2]);
- pixPerMM = tmp[0]; // [px]/[mm]
- final float[] sDPI = MonitorDevice.perMMToPerInch( tmp );
- dpiV = sDPI[1];
- }
-
- boolean z_only = true;
- int txt_idx = 0;
+ final float[] y_pos = { 0 };
+ window.invoke(true, (drawable) -> {
+ final float fontScale2;
+ {
+ final String vs = "Welcome to Göthel Software *** Jausoft *** https://jausoft.com *** We do software ... Bremerhaven 19°C, Munich";
+ final AABBox fbox = font.getGlyphBounds(vs);
+ fontScale2 = g_w / fbox.getWidth();
+ System.err.println("FontScale2: " + fontScale2 + " = " + g_w + " / " + fbox.getWidth());
+ }
+ final AABBox clippedBox = new AABBox(animBox).resizeWidth(sceneBoxFrameWidth, -sceneBoxFrameWidth);
+ y_pos[0] = clippedBox.getMaxY();
+ // AnimGroup.Set 1:
+ // Circular short scrolling text (right to left) without rotation, no acceleration
+ {
+ final String vs = "Welcome to Göthel Software *** Jausoft *** https://jausoft.com *** We do software ... ";
+ y_pos[0] -= fontScale2 * 1.5f;
+ animGroup.addGlyphSetHorizScroll01(pixPerMM, hasGLP, scene.getMatrix(), scene.getViewport(), options.renderModes,
+ font, vs, fontScale2, new Vec4f(0.1f, 0.1f, 0.1f, 0.9f),
+ 50 / 1e3f /* velocity */, clippedBox, y_pos[0]);
+ }
+ // AnimGroup.Set 2:
+ // Circular long scrolling text (right to left) without rotation, no acceleration
+ {
+ final String vs = "Berlin 23°C, London 20°C, Paris 22°C, Madrid 26°C, Lisbon 28°C, Moscow 22°C, Prag 22°C, Bremerhaven 19°C, Munich 25°C, Fukushima 40°C, Bejing 30°C, Rome 29°C, Beirut 28°C, Damaskus 29°C *** ";
+ y_pos[0] -= fontScale2 * 1.2f;
+ animGroup.addGlyphSetHorizScroll01(pixPerMM, hasGLP, scene.getMatrix(), scene.getViewport(), options.renderModes,
+ font, vs, fontScale2, new Vec4f(0.1f, 0.1f, 0.1f, 0.9f),
+ 30 / 1e3f /* velocity */, clippedBox, y_pos[0]);
+ }
+ return true;
+ });
do {
+ //
+ // Setup new animation sequence
+ // - Flush all AnimGroup.Set entries
+ // - Add newly created AnimGroup.Set entries
+ //
+ final String curText = originalTexts[txt_idx];
final float fontScale;
+ final AnimGroup.Set[] dynAnimSet = { null, null, null };
{
- final AABBox fbox = font.getGlyphBounds(originalTexts[txt_idx]);
- fontScale = sceneBox.getWidth() / fbox.getWidth();
- System.err.println("FontScale: " + fontScale + " = " + sceneBox.getWidth() + " / " + fbox.getWidth());
+ final AABBox fbox = font.getGlyphBounds(curText);
+ fontScale = g_w / fbox.getWidth();
+ System.err.println("FontScale: " + fontScale + " = " + g_w + " / " + fbox.getWidth());
}
- z_only = !z_only;
+ z_only[0] = !z_only[0];
window.invoke(true, (drawable) -> {
- glyphGroup.removeAllShapes(drawable.getGL().getGL2ES2(), scene.getRenderer());
- return true;
- });
+ // AnimGroup.Set 3: This `mainAnimSet[0]` is controlling overall animation duration
+ // Rotating animated text moving to target (right to left) + slight acceleration on rotation
+ dynAnimSet[0] = animGroup.addGlyphSetRandom01(pixPerMM, hasGLP, scene.getMatrix(), scene.getViewport(),
+ options.renderModes, font, curText, fontScale, new Vec4f(0.1f, 0.1f, 0.1f, 1f),
+ 0f /* accel */, velocity, FloatUtil.PI/20f /* ang_accel */, ang_velo,
+ animBox, z_only[0], new Random(), new AnimGroup.TargetLerp(Vec3f.UNIT_Y));
+
+ // AnimGroup.Set 4:
+ // Sine animated text moving to target (right to left) with sine amplitude alternating on Z- and Y-axis + acceleration
+ {
+ final GL gl = drawable.getGL();
- final float[] movingGlyphPixPerShapeUnit;
- {
- final Random random = new Random();
-
- 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 = 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 ? target.y() :
- sceneBox.getMinY() + random.nextFloat() * sceneBox.getHeight();
- final float start_pos_z = 0f + random.nextFloat() * sceneBox.getHeight() * 1f;
- gs.moveTo(start_pos_x, start_pos_y, start_pos_z);
+ final String vs = "JogAmp Version "+JoglVersion.getInstance().getImplementationVersion()+", "+gl.glGetString(GL.GL_VERSION)+", "+gl.glGetString(GL.GL_VENDOR);
+ final float fontScale2;
+ {
+ final AABBox fbox = font.getGlyphBounds(vs);
+ fontScale2 = g_w / fbox.getWidth() * 0.6f;
+ }
+ // Translation : We use velocity as acceleration (good match) and pass only velocity/10 as starting velocity
+ dynAnimSet[1] = animGroup.addGlyphSet(pixPerMM, hasGLP, scene.getMatrix(), scene.getViewport(),
+ options.renderModes, font, 'X', vs, fontScale2,
+ velocity /* accel */, velocity/10f, 0f /* ang_accel */, 2*FloatUtil.PI /* 1-rotation/s */,
+ new AnimGroup.SineLerp(z_only[0] ? Vec3f.UNIT_Z : Vec3f.UNIT_Y, 1.618f, 1.618f),
+ (final AnimGroup.Set as, final int idx, final AnimGroup.ShapeData sd) -> {
+ sd.shape.setColor(0.1f, 0.1f, 0.1f, 0.9f);
+
+ sd.targetPos.add(
+ animBox.getMinX() + as.refShape.getScaledWidth() * 1.0f,
+ animBox.getMinY() + as.refShape.getScaledHeight() * 2.0f, 0f);
+
+ sd.startPos.set( sd.targetPos.x() + animBox.getWidth(),
+ sd.targetPos.y(), sd.targetPos.z());
+ sd.shape.moveTo( sd.startPos );
+ } );
}
- // just add testGlyph to scene to be cleaned up, invisible
- testGlyph.setEnabled(false);
- glyphGroup.addShape(testGlyph);
- }
- glyphGroup.addShapes(glyphShapes);
+ // AnimGroup.Set 5:
+ // 3 animated Shapes moving to target (right to left) while rotating around z-axis + acceleration on translation
+ {
+ final float size2 = fontScale/2;
+ final float yscale = 1.1f;
+ final GraphShape refShape = new Rectangle(options.renderModes, size2, size2*yscale, sceneBox.getWidth() * 0.0025f );
+ dynAnimSet[2] = animGroup.addAnimSet(
+ pixPerMM, hasGLP, scene.getMatrix(), scene.getViewport(),
+ velocity /* accel */, velocity/10f, 0f /* ang_accel */, 2*FloatUtil.PI /* 1-rotation/s */,
+ new AnimGroup.TargetLerp(Vec3f.UNIT_Z), refShape);
+ final AnimGroup.ShapeSetup shapeSetup = (final AnimGroup.Set as, final int idx, final AnimGroup.ShapeData sd) -> {
+ sd.targetPos.add(animBox.getMinX() + as.refShape.getScaledWidth() * 1.0f,
+ y_pos[0] - as.refShape.getScaledHeight() * 1.5f, 0f);
+
+ sd.startPos.set( sd.targetPos.x() + animBox.getWidth(),
+ sd.targetPos.y(), sd.targetPos.z());
+ sd.shape.moveTo( sd.startPos );
+ };
+ refShape.setColor(1.0f, 0.0f, 0.0f, 0.9f);
+ refShape.getRotation().rotateByAngleZ(FloatUtil.QUARTER_PI);
+ dynAnimSet[2].addShape(animGroup, refShape, shapeSetup);
+ {
+ final Shape s = new Rectangle(options.renderModes, size2, size2*yscale, sceneBox.getWidth() * 0.0025f ).validate(hasGLP);
+ s.setColor(0.0f, 1.0f, 0.0f, 0.9f);
+ s.move(refShape.getScaledWidth() * 1.5f * 1, 0, 0);
+ dynAnimSet[2].addShape(animGroup, s, shapeSetup);
+ }
+ {
+ final Shape s = new Rectangle(options.renderModes, size2, size2*yscale, sceneBox.getWidth() * 0.0025f ).validate(hasGLP);
+ s.setColor(0.0f, 0.0f, 1.0f, 0.9f);
+ s.move(refShape.getScaledWidth() * 1.5f * 2, 0, 0);
+ s.getRotation().rotateByAngleZ(FloatUtil.QUARTER_PI);
+ dynAnimSet[2].addShape(animGroup, s, shapeSetup);
+ }
+ }
+ return true;
+ });
- final float pos_eps = FloatUtil.EPSILON * 5000; // ~= 0.0005960
- final float rot_eps = FloatUtil.adegToRad(0.5f); // 1 adeg ~= 0.01745 rad
+ // animGroup.setTickOnDraw(false);
final long t0_us = Clock.currentNanos() / 1000; // [us]
- final long[] t2_us = { t0_us };
- while ( !glyphShapes.isEmpty() && window.isNativeValid() ) {
- window.invoke(true, (drawable) -> {
- final long t3_us = Clock.currentNanos() / 1000;
- final float dt_s = (t3_us - t2_us[0]) / 1e6f;
- t2_us[0] = t3_us;
-
- final float velocity_px = velocity * 1e3f * pixPerMM; // [px]/[s]
- final float velocity_obj = velocity_px / movingGlyphPixPerShapeUnit[0]; // [shapeUnit]/[s]
- final float dxy = velocity_obj * dt_s; // [shapeUnit]
-
- 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);
- final float p_t_diff = p_t.length();
- final Quaternion q = glyph.getRotation();
- final Vec3f euler = q.toEuler(new Vec3f());
- final float radY = euler.y();
- final float radYdiff = Math.min(Math.abs(radY), FloatUtil.TWO_PI - Math.abs(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
- if( DEBUG ) {
- if( 0 == idx ) {
- System.err.println("F: rot: "+radY+" ("+FloatUtil.radToADeg(radY)+"), diff "+radYdiff+" ("+FloatUtil.radToADeg(radYdiff)+"), step "+rot_step+" ("+FloatUtil.radToADeg(rot_step)+")");
- }
- }
- glyph.moveTo(target.x(), target.y(), target.z());
- glyph.setInteractive(false);
- q.setIdentity();
- glyphShapes.remove(idx);
- continue;
- }
- 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("P: 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 );
- }
- }
+ while ( ( null == dynAnimSet[0] || dynAnimSet[0].isAnimationActive() ) && window.isNativeValid() ) {
+ try { Thread.sleep(100); } catch (final InterruptedException e1) { }
+ }
+ if( window.isNativeValid() ) {
+ final float has_dur_s = ((Clock.currentNanos() / 1000) - t0_us) / 1e6f; // [us]
+ System.err.printf("Text travel-duration %.3f s, %d chars%n", has_dur_s, curText.length());
+ if( scene.getScreenshotCount() - manualScreenShorCount[0] < 1 + originalTexts.length ) {
+ scene.screenshot(true, scene.nextScreenshotFile(null, UISceneDemo03.class.getSimpleName(), options.renderModes, window.getChosenGLCapabilities(), null));
+ }
+ try { Thread.sleep(1500); } catch (final InterruptedException e1) { }
+ if( autoSpeed > 0 ) {
+ if( velocity < 60/1000f ) {
+ setVelocity(velocity + 9/1000f);
+ } else {
+ setVelocity(velocity - 9/1000f);
+ autoSpeed = -1;
+ }
+ } else if( autoSpeed < 0 ) {
+ if( velocity > 11/1000f ) {
+ setVelocity(velocity - 9/1000f);
+ } else {
+ setVelocity(velocity + 9/1000f);
+ autoSpeed = 1;
}
- final String text = String.format("%s, v %.1f mm/s, r %.3f",
- scene.getStatusText(drawable, options.renderModes, 0, dpiV), velocity * 1e3f, rot_step);
- statusLabel.setText(text);
+ }
+ txt_idx = ( txt_idx + 1 ) % originalTexts.length;
+ window.invoke(true, (drawable) -> {
+ animGroup.removeAnimSets(drawable.getGL().getGL2ES2(), scene.getRenderer(), Arrays.asList(dynAnimSet));
return true;
- });
+ } );
}
- final float has_dur_s = ((Clock.currentNanos() / 1000) - t0_us) / 1e6f; // [us]
- System.err.printf("Text travel-duration %.3f s, %d chars%n", has_dur_s, originalTexts[txt_idx].length());
- if( scene.getScreenshotCount() < 1 + originalTexts.length ) {
- scene.screenshot(true, scene.nextScreenshotFile(null, UISceneDemo03.class.getSimpleName(), options.renderModes, window.getChosenGLCapabilities(), null));
+ } while (options.keepRunning && window.isNativeValid());
+ if (!options.stayOpen) {
+ MiscUtils.destroyWindow(window);
+ }
+ }
+
+ /**
+ * Setup Window listener for I/O
+ * @param window
+ * @param animGroup
+ */
+ static void sub01SetupWindowListener(final GLWindow window, final Scene scene, final AnimGroup animGroup, final Label statusLabel, final float dpiV) {
+ window.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowResized(final WindowEvent e) {
+ window.setTitle(UISceneDemo03.class.getSimpleName() + ": " + window.getSurfaceWidth() + " x " + window.getSurfaceHeight());
}
- try { Thread.sleep(2000); } catch (final InterruptedException e1) { }
- if( autoSpeed > 0 ) {
- if( velocity < 60/1000f ) {
- setVelocity(velocity + 9/1000f);
- } else {
- setVelocity(velocity - 9/1000f);
- autoSpeed = -1;
+
+ @Override
+ public void windowDestroyNotify(final WindowEvent e) {
+ final GLAnimatorControl animator = window.getAnimator();
+ if( null != animator ) {
+ animator.stop();
+ }
+ }
+ });
+ window.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyReleased(final KeyEvent e) {
+ final short keySym = e.getKeySymbol();
+ if (keySym == KeyEvent.VK_PLUS ||
+ keySym == KeyEvent.VK_ADD)
+ {
+ if (e.isShiftDown()) {
+ setVelocity(velocity + 10 / 1000f);
+ } else {
+ setVelocity(velocity + 1 / 1000f);
+ }
+ } else if (keySym == KeyEvent.VK_MINUS ||
+ keySym == KeyEvent.VK_SUBTRACT)
+ {
+ if (e.isShiftDown()) {
+ setVelocity(velocity - 10 / 1000f);
+ } else {
+ setVelocity(velocity - 1 / 1000f);
+ }
+ } else if( keySym == KeyEvent.VK_F4 || keySym == KeyEvent.VK_ESCAPE || keySym == KeyEvent.VK_Q ) {
+ MiscUtils.destroyWindow(window);
+ } else if( keySym == KeyEvent.VK_SPACE ) {
+ animGroup.setTickPaused ( !animGroup.getTickPaused() );
+ } else if( keySym == KeyEvent.VK_ENTER ) {
+ animGroup.stopAnimation();
}
- } else if( autoSpeed < 0 ) {
- if( velocity > 11/1000f ) {
- setVelocity(velocity - 9/1000f);
- } else {
- setVelocity(velocity + 9/1000f);
- autoSpeed = 1;
+ }
+ });
+ window.addMouseListener( new MouseAdapter() {
+ @Override
+ public void mouseWheelMoved(final MouseEvent e) {
+ int axis = 1;
+ if( e.isControlDown() ) {
+ axis = 0;
+ } else if( e.isAltDown() ) {
+ axis = 2;
}
+ final float angle = e.getRotation()[1] < 0f ? FloatUtil.adegToRad(-1f) : FloatUtil.adegToRad(1f);
+ rotateShape(animGroup, angle, axis);
}
- txt_idx = ( txt_idx + 1 ) % originalTexts.length;
- } while (options.keepRunning && window.isNativeValid());
- if (!options.stayOpen) {
- window.destroy();
+ });
+ window.addGLEventListener(new GLEventListener() {
+ float dir = 1f;
+ @Override
+ public void init(final GLAutoDrawable drawable) {
+ System.err.println(JoglVersion.getGLInfo(drawable.getGL(), null));
+ }
+ @Override
+ public void dispose(final GLAutoDrawable drawable) {}
+ @Override
+ public void display(final GLAutoDrawable drawable) {
+ if( animGroup.isToggleOn() ) {
+ final Quaternion rot = animGroup.getRotation();
+ final Vec3f euler = rot.toEuler(new Vec3f());
+ if( FloatUtil.HALF_PI <= euler.y() ) {
+ dir = -1f;
+ } else if( euler.y() <= -FloatUtil.HALF_PI ) {
+ dir = 1f;
+ }
+ animGroup.getRotation().rotateByAngleY( frame_velocity * dir );
+ }
+ final String text = String.format("%s, v %.1f mm/s, r %.3f rad/s",
+ scene.getStatusText(drawable, options.renderModes, 0, dpiV), velocity * 1e3f, ang_velo);
+ statusLabel.setText(text);
+ }
+ @Override
+ public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) {}
+ });
+ }
+
+ /**
+ * Add a HUD UI to the scene
+ * @param scene
+ * @param animGroup
+ * @param window
+ * @throws IOException
+ */
+ static void sub02AddUItoScene(final Scene scene, final AnimGroup animGroup, final int mainAnimSetIdx, final GLWindow window) throws IOException {
+ final AABBox sceneBox = scene.getBounds();
+ final Group buttonsRight = new Group();
+
+ final Font fontButtons = FontFactory.get(FontFactory.UBUNTU).getDefault();
+ final float buttonWidth = sceneBox.getWidth() * 0.09f;
+ final float buttonHeight = buttonWidth / 3.0f;
+
+ buttonsRight.setLayout(new GridLayout(buttonWidth, buttonHeight, Alignment.Fill, new Gap(buttonHeight*0.50f, buttonWidth*0.10f), 7));
+ {
+ final Button button = new Button(options.renderModes, fontButtons, " Pause ", buttonWidth, buttonHeight);
+ button.setToggleable(true);
+ button.addMouseListener(new Shape.MouseGestureAdapter() {
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ animGroup.setTickPaused ( !animGroup.getTickPaused() );
+ } } );
+ buttonsRight.addShape(button);
+ }
+ {
+ final Button button = new Button(options.renderModes, fontButtons, " Next ", buttonWidth, buttonHeight);
+ button.addMouseListener(new Shape.MouseGestureAdapter() {
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ final AnimGroup.Set as = animGroup.getAnimSet(mainAnimSetIdx);
+ if( null != as ) {
+ as.setAnimationActive(false);
+ }
+ } } );
+ buttonsRight.addShape(button);
+ }
+ {
+ final Button button = new Button(options.renderModes, fontButtons, " Rotate ", buttonWidth, buttonHeight);
+ button.setToggleable(true);
+ button.addMouseListener(new Shape.MouseGestureAdapter() {
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ animGroup.toggle();
+ } } );
+ buttonsRight.addShape(button);
+ }
+ {
+ final Button button = new Button(options.renderModes, fontButtons, " < Rot > ", buttonWidth, buttonHeight);
+ button.addMouseListener(new Shape.MouseGestureAdapter() {
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ final Shape.EventInfo shapeEvent = (Shape.EventInfo) e.getAttachment();
+ int axis = 1;
+ if( e.isControlDown() ) {
+ axis = 0;
+ } else if( e.isAltDown() ) {
+ axis = 2;
+ }
+ if( shapeEvent.objPos.x() < shapeEvent.shape.getBounds().getCenter().x() ) {
+ rotateShape(animGroup, FloatUtil.adegToRad(1f), axis);
+ } else {
+ rotateShape(animGroup, FloatUtil.adegToRad(-1f), axis);
+ }
+ } } );
+ buttonsRight.addShape(button);
+ }
+ {
+ final Button button = new Button(options.renderModes, fontButtons, " < Velo > ", buttonWidth, buttonHeight);
+ button.addMouseListener(new Shape.MouseGestureAdapter() {
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ final Shape.EventInfo shapeEvent = (Shape.EventInfo) e.getAttachment();
+ final float scale = e.isShiftDown() ? 1f : 10f;
+ if( shapeEvent.objPos.x() < shapeEvent.shape.getBounds().getCenter().x() ) {
+ setVelocity(velocity - scale / 1000f);
+ } else {
+ setVelocity(velocity + scale / 1000f);
+ }
+ final AnimGroup.Set as = animGroup.getAnimSet(mainAnimSetIdx);
+ if( null != as ) {
+ as.setAnimationActive(false);
+ }
+ } } );
+ buttonsRight.addShape(button);
+ }
+ {
+ final Button button = new Button(options.renderModes, fontButtons, " Snap ", buttonWidth, buttonHeight);
+ button.addMouseListener(new Shape.MouseGestureAdapter() {
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ scene.screenshot(false, scene.nextScreenshotFile(null, UISceneDemo03.class.getSimpleName(), options.renderModes, window.getChosenGLCapabilities(), null));
+ manualScreenShorCount[0]++;
+ } } );
+ buttonsRight.addShape(button);
+ }
+ {
+ final Button button = new Button(options.renderModes, fontButtons, " Quit ", buttonWidth, buttonHeight);
+ button.setColor(0.7f, 0.3f, 0.3f, 1.0f);
+ button.addMouseListener(new Shape.MouseGestureAdapter() {
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ MiscUtils.destroyWindow(window);
+ } } );
+ buttonsRight.addShape(button);
+ }
+ buttonsRight.forAll((final Shape s) -> { s.setDragAndResizeable(false); return false; });
+ buttonsRight.validate(window.getChosenGLCapabilities().getGLProfile());
+ buttonsRight.moveTo(sceneBox.getWidth()/2f - buttonsRight.getScaledWidth()*1.02f,
+ sceneBox.getHeight()/2f - buttonsRight.getScaledHeight()*1.02f, 0f);
+ scene.addShape(buttonsRight);
+ if( DEBUG ) {
+ System.err.println("Buttons-Right: Button-1 "+buttonsRight.getShapes().get(0));
+ System.err.println("Buttons-Right: SceneBox "+sceneBox);
+ System.err.println("Buttons-Right: scaled "+buttonsRight.getScaledWidth()+" x "+buttonsRight.getScaledHeight());
+ System.err.println("Buttons-Right: Box "+buttonsRight.getBounds());
+ System.err.println("Buttons-Right: "+buttonsRight);
}
}
/**
+ * Rotate the shape while avoiding 90 degree position
+ * @param shape the shape to rotate
+ * @param angle the angle in radians
+ * @param axis 0 for X-, 1 for Y- and 2 for Z-axis
+ */
+ public static void rotateShape(final Shape shape, float angle, final int axis) {
+ final Quaternion rot = shape.getRotation();
+ final Vec3f euler = rot.toEuler(new Vec3f());
+ final Vec3f eulerOld = euler.copy();
+
+ final float eps = FloatUtil.adegToRad(5f);
+ final float sign = angle >= 0f ? 1f : -1f;
+ final float v;
+ switch( axis ) {
+ case 0: v = euler.x(); break;
+ case 1: v = euler.y(); break;
+ case 2: v = euler.z(); break;
+ default: return;
+ }
+ final float av = Math.abs(v);
+ if( 1f*FloatUtil.HALF_PI - eps <= av && av <= 1f*FloatUtil.HALF_PI + eps ||
+ 3f*FloatUtil.HALF_PI - eps <= av && av <= 3f*FloatUtil.HALF_PI + eps) {
+ angle = 2f * eps * sign;
+ }
+ switch( axis ) {
+ case 0: euler.add(angle, 0, 0); break;
+ case 1: euler.add(0, angle, 0); break;
+ case 2: euler.add(0, 0, angle); break;
+ }
+ System.err.println("Rot: angleDelta "+angle+" (eps "+eps+"): "+eulerOld+" -> "+euler);
+ rot.setFromEuler(euler);
+ }
+
+ /**
* Our PMVMatrixSetup:
* - gluPerspective like Scene's default
* - no normal scale to 1, keep distance to near plane for rotation effects.
*/
public static class MyPMVMatrixSetup implements PMVMatrixSetup {
+ private final float scene_dist;
+ public MyPMVMatrixSetup(final float scene_dist) {
+ this.scene_dist = scene_dist;
+ }
@Override
public void set(final PMVMatrix pmv, final Recti viewport) {
final float ratio = (float) viewport.width() / (float) viewport.height();
pmv.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
pmv.glLoadIdentity();
pmv.gluPerspective(Scene.DEFAULT_ANGLE, ratio, Scene.DEFAULT_ZNEAR, Scene.DEFAULT_ZFAR);
- pmv.glTranslatef(0f, 0f, Scene.DEFAULT_SCENE_DIST);
+ pmv.glTranslatef(0f, 0f, scene_dist);
pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
pmv.glLoadIdentity();
diff --git a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03b.java b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03b.java
deleted file mode 100644
index 4d3e603ab..000000000
--- a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03b.java
+++ /dev/null
@@ -1,563 +0,0 @@
-/**
- * Copyright 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 java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-import com.jogamp.common.os.Clock;
-import com.jogamp.common.util.IOUtil;
-import com.jogamp.graph.curve.Region;
-import com.jogamp.graph.font.Font;
-import com.jogamp.graph.font.FontFactory;
-import com.jogamp.graph.ui.GraphShape;
-import com.jogamp.graph.ui.Group;
-import com.jogamp.graph.ui.Scene;
-import com.jogamp.graph.ui.Shape;
-import com.jogamp.graph.ui.Scene.PMVMatrixSetup;
-import com.jogamp.graph.ui.shapes.GlyphShape;
-import com.jogamp.graph.ui.shapes.Label;
-import com.jogamp.graph.ui.shapes.Rectangle;
-import com.jogamp.newt.MonitorDevice;
-import com.jogamp.newt.event.KeyAdapter;
-import com.jogamp.newt.event.KeyEvent;
-import com.jogamp.newt.event.MouseEvent;
-import com.jogamp.newt.event.WindowAdapter;
-import com.jogamp.newt.event.WindowEvent;
-import com.jogamp.newt.opengl.GLWindow;
-import com.jogamp.opengl.GL;
-import com.jogamp.opengl.GL2ES2;
-import com.jogamp.opengl.GLAutoDrawable;
-import com.jogamp.opengl.GLCapabilities;
-import com.jogamp.opengl.GLEventListener;
-import com.jogamp.opengl.GLProfile;
-import com.jogamp.opengl.demos.graph.FontSetDemos;
-import com.jogamp.opengl.demos.util.CommandlineOptions;
-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.Recti;
-import com.jogamp.opengl.math.Vec3f;
-import com.jogamp.opengl.math.geom.AABBox;
-import com.jogamp.opengl.util.Animator;
-import com.jogamp.opengl.util.PMVMatrix;
-
-/**
- * Res independent Shape, Scene attached to GLWindow showing simple linear Shape movement.
- * <p>
- * This variation of {@link UISceneDemo00} shows a text animation assembling one line of text,
- * 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 '-aspeed' to vary velocity
- * </p>
- */
-public class UISceneDemo03b {
- 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 CommandlineOptions options = new CommandlineOptions(1280, 720, Region.VBAA_RENDERING_BIT);
- static float frame_velocity = 5f / 1e3f; // [m]/[s]
- static float velocity = 30 / 1e3f; // [m]/[s]
- static float rot_step = velocity * 1;
-
- static void setVelocity(final float v) {
- velocity = v; // Math.max(1/1e3f, v);
- rot_step = velocity * 1;
- }
-
- public static void main(final String[] args) throws IOException {
- int autoSpeed = -1;
- setVelocity(80/1000f);
- options.keepRunning = true;
- boolean groupRotate = true;
- boolean groupFrame = true;
-
- if (0 != args.length) {
- 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);
- autoSpeed = 0;
- } else if(args[idx[0]].equals("-aspeed")) {
- autoSpeed = -1;
- setVelocity(80/1000f);
- options.keepRunning = true;
- } else if(args[idx[0]].equals("-no_group_rotate")) {
- groupRotate = false;
- } else if(args[idx[0]].equals("-no_group_frame")) {
- groupFrame = false;
- }
- }
- }
- // renderModes |= Region.COLORCHANNEL_RENDERING_BIT;
- System.err.println(options);
-
- final GLProfile reqGLP = GLProfile.get(options.glProfileName);
- System.err.println("GLProfile: "+reqGLP);
-
- //
- // Resolution independent, no screen size
- //
- final Font font = FontFactory.get(IOUtil.getResource("fonts/freefont/FreeSerif.ttf",FontSetDemos.class.getClassLoader(), FontSetDemos.class).getInputStream(), true);
- // final Font font = FontFactory.get(IOUtil.getResource("jogamp/graph/font/fonts/ubuntu/Ubuntu-R.ttf",FontSetDemos.class.getClassLoader(), FontSetDemos.class).getInputStream(), true);
- System.err.println("Font: " + font.getFullFamilyName());
- final Font fontStatus = FontFactory.get(IOUtil.getResource("fonts/freefont/FreeMono.ttf", FontSetDemos.class.getClassLoader(), FontSetDemos.class).getInputStream(), true);
-
- final Scene scene = new Scene();
- scene.setClearParams(new float[] { 1f, 1f, 1f, 1f }, GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
- scene.setPMVMatrixSetup(new MyPMVMatrixSetup(groupRotate ? Scene.DEFAULT_SCENE_DIST : -0.16f));
- scene.setDebugBorderBox(options.debugBoxThickness);
-
- final Group glyphGroup = new Group();
- scene.addShape(glyphGroup);
-
- scene.setFrustumCullingEnabled(true);
- glyphGroup.setFrustumCullingEnabled(true);
-
- final Animator animator = new Animator(0 /* w/o AWT */);
- animator.setUpdateFPSFrames(1 * 60, null); // System.err);
-
- final GLCapabilities caps = new GLCapabilities(reqGLP);
- caps.setAlphaBits(4);
- if( options.sceneMSAASamples > 0 ) {
- caps.setSampleBuffers(true);
- caps.setNumSamples(options.sceneMSAASamples);
- }
- System.out.println("Requested: " + caps);
-
- final GLWindow window = GLWindow.create(caps);
- window.setSize(options.surface_width, options.surface_height);
- window.setTitle(UISceneDemo03b.class.getSimpleName() + ": " + window.getSurfaceWidth() + " x " + window.getSurfaceHeight());
- window.setVisible(true);
- window.addGLEventListener(scene);
- window.addWindowListener(new WindowAdapter() {
- @Override
- public void windowResized(final WindowEvent e) {
- window.setTitle(UISceneDemo03b.class.getSimpleName() + ": " + window.getSurfaceWidth() + " x " + window.getSurfaceHeight());
- }
-
- @Override
- public void windowDestroyNotify(final WindowEvent e) {
- animator.stop();
- }
- });
- window.addKeyListener(new KeyAdapter() {
- @Override
- public void keyReleased(final KeyEvent e) {
- if (e.getKeySymbol() == KeyEvent.VK_PLUS ||
- e.getKeySymbol() == KeyEvent.VK_ADD)
- {
- if (e.isShiftDown()) {
- setVelocity(velocity + 10 / 1000f);
- } else {
- setVelocity(velocity + 1 / 1000f);
- }
- } else if (e.getKeySymbol() == KeyEvent.VK_MINUS ||
- e.getKeySymbol() == KeyEvent.VK_SUBTRACT)
- {
- if (e.isShiftDown()) {
- setVelocity(velocity - 10 / 1000f);
- } else {
- setVelocity(velocity - 1 / 1000f);
- }
- }
- }
- });
-
- scene.attachInputListenerTo(window);
-
- animator.add(window);
- animator.start();
-
- //
- // After initial display we can use screen resolution post initial
- // Scene.reshape(..)
- // However, in this example we merely use the resolution to
- // - Compute the animation values with DPI
- scene.waitUntilDisplayed();
-
- window.invoke(true, (drawable) -> {
- final GL gl = drawable.getGL();
- gl.glEnable(GL.GL_DEPTH_TEST);
- return true;
- });
-
- final GLProfile hasGLP = window.getChosenGLCapabilities().getGLProfile();
- final AABBox sceneBox = scene.getBounds();
- {
- final GraphShape r = new Rectangle(options.renderModes, sceneBox, sceneBox.getWidth()*0.01f);
- // final GraphShape r = new Rectangle(options.renderModes, sceneBox.getMinX(), sceneBox.getMinY(), sceneBox.getWidth(), sceneBox.getHeight(), sceneBox.getWidth()*0.01f);
- // final GraphShape r = new Rectangle(options.renderModes, sceneBox.getMinX()*0.5f, sceneBox.getMinY()*0.5f, sceneBox.getWidth()*0.5f, sceneBox.getHeight()*0.5f, sceneBox.getWidth()*0.5f*0.01f, sceneBox.getMinZ());
- if( !groupFrame ) {
- r.setColor(0f, 0f, 0f, 0f);
- }
- glyphGroup.addShape( r );
- }
- glyphGroup.scale(0.8f, 0.8f, 1f);
- // glyphGroup.scale(0.5f, 0.5f, 1f);
- glyphGroup.setRotationPivot(0, 0, 0);
- glyphGroup.validate(hasGLP);
- System.err.println("SceneBox " + sceneBox);
- System.err.println("Frustum " + scene.getMatrix().getFrustum());
- System.err.println("GlyphGroup.0: "+glyphGroup);
-
- 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(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);
- scene.addShape(statusLabel);
- }
-
- {
- final StringBuilder sb = new StringBuilder();
- for(final String s : originalTexts) {
- sb.append(s).append("\n");
- }
- final Label l = new Label(options.renderModes, font, sb.toString()); // originalTexts[0]);
- l.validate(hasGLP);
- final float scale = sceneBox.getWidth() / l.getBounds().getWidth();
- l.setScale(scale, scale, 1f);
- l.setColor(0.1f, 0.1f, 0.1f, 1.0f);
- l.moveTo(sceneBox.getMinX(), 0f, 0f);
- scene.addShape(l);
-
- if( options.wait_to_start ) {
- statusLabel.setText("Press enter to continue");
- MiscUtils.waitForKey("Start");
- }
-
- window.invoke(true, (drawable) -> {
- final GL2ES2 gl = drawable.getGL().getGL2ES2();
- scene.screenshot(gl, scene.nextScreenshotFile(null, UISceneDemo03b.class.getSimpleName(), options.renderModes, drawable.getChosenGLCapabilities(), null));
- scene.removeShape(gl, l);
- return true;
- });
- }
-
- //
- // Setup the moving glyphs
- //
-
- final List<GlyphShape> glyphShapesAnim = new ArrayList<GlyphShape>();
- final List<GlyphShape> glyphShapesAll = new ArrayList<GlyphShape>();
-
- final float pixPerMM, dpiV;
- {
- final float[] tmp = window.getPixelsPerMM(new float[2]);
- pixPerMM = tmp[0]; // [px]/[mm]
- final float[] sDPI = MonitorDevice.perMMToPerInch( tmp );
- dpiV = sDPI[1];
- }
- boolean z_only = true;
- int txt_idx = 0;
-
- final AABBox glyphBox = glyphGroup.getBounds();
- final float g_w = glyphBox.getWidth();
- final float g_h = glyphBox.getHeight();
-
- // glyphGroup.scale(0.8f, 0.8f, 1f);
- // glyphGroup.validate(hasGLP);
- System.err.println("GlyphBox " + glyphBox);
- System.err.println("GlyphGroup " + glyphGroup);
-
- glyphGroup.addMouseListener( new Shape.MouseGestureAdapter() {
- @Override
- public void mouseWheelMoved(final MouseEvent e) {
- final Shape.EventInfo shapeEvent = (Shape.EventInfo) e.getAttachment();
- final Shape shape = shapeEvent.shape;
- final Quaternion rot = shape.getRotation();
- final Vec3f euler = rot.toEuler(new Vec3f());
- final Vec3f eulerOld = euler.copy();
- if( !e.isShiftDown() ) {
- final float eps = FloatUtil.adegToRad(5f);
- float diff = e.getRotation()[1] < 0f ? FloatUtil.adegToRad(-1f) : FloatUtil.adegToRad(1f);
- final float sign = diff >= 0f ? 1f : -1f;
- final float v;
- if( e.isAltDown() ) {
- shape.scale(1f+sign/10f, 1f+sign/10f, 1f);
- System.err.println("Scaled: "+shape);
- return;
- } else if( e.isControlDown() ) {
- v = euler.x();
- } else {
- v = euler.y();
- }
- final float av = Math.abs(v);
- if( 1f*FloatUtil.HALF_PI - eps <= av && av <= 1f*FloatUtil.HALF_PI + eps ||
- 3f*FloatUtil.HALF_PI - eps <= av && av <= 3f*FloatUtil.HALF_PI + eps) {
- diff = 2f * eps * sign;
- }
- if( e.isAltDown() ) {
- euler.add(0, 0, diff);
- } else if( e.isControlDown() ) {
- euler.add(diff, 0, 0);
- } else {
- euler.add(0, diff, 0);
- }
- System.err.println("Rot: diff "+diff+" (eps "+eps+"): "+eulerOld+" -> "+euler);
- rot.setFromEuler(euler);
- }
- }
- });
- glyphGroup.onToggle((final Shape shape) -> {
- System.err.println("Toggle: "+shape);
- });
- glyphGroup.setInteractive(true);
- glyphGroup.setDraggable(false);
- glyphGroup.setResizable(false);
- glyphGroup.setToggleable(true);
- glyphGroup.setToggle( groupRotate );
- System.err.println("GlyphGroup.1: "+glyphGroup);
-
- window.addGLEventListener(new GLEventListener() {
- float dir = 1f;
- @Override
- public void init(final GLAutoDrawable drawable) {}
- @Override
- public void dispose(final GLAutoDrawable drawable) {}
- @Override
- public void display(final GLAutoDrawable drawable) {
- if( glyphGroup.isToggleOn() ) {
- final Quaternion rot = glyphGroup.getRotation();
- final Vec3f euler = rot.toEuler(new Vec3f());
- if( FloatUtil.HALF_PI <= euler.y() ) {
- dir = -1f;
- } else if( euler.y() <= -FloatUtil.HALF_PI ) {
- dir = 1f;
- }
- glyphGroup.getRotation().rotateByAngleY( frame_velocity * dir );
- }
- }
- @Override
- public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) {}
- });
-
- do {
- final float fontScale;
- {
- final AABBox fbox = font.getGlyphBounds(originalTexts[txt_idx]);
- // fontScale = s_w / fbox.getWidth();
- // System.err.println("FontScale: " + fontScale + " = " + s_w + " / " + fbox.getWidth());
- fontScale = g_w / fbox.getWidth();
- System.err.println("FontScale: " + fontScale + " = " + g_w + " / " + fbox.getWidth());
- }
- z_only = !z_only;
- window.invoke(true, (drawable) -> {
- glyphGroup.removeShapes(drawable.getGL().getGL2ES2(), scene.getRenderer(), glyphShapesAll);
- return true;
- });
- glyphShapesAll.clear();
- glyphShapesAnim.clear();
-
- final float[] movingGlyphPixPerShapeUnit;
- {
- final Random random = new Random();
-
- 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 = testGlyph.getSurfaceSize(scene, pmv, new int[2]); // [px]
- movingGlyphPixPerShapeUnit = testGlyph.getPixelPerShapeUnit(movingGlyphSizePx, new float[2]); // [px]/[shapeUnit]
-
- final AABBox box = GlyphShape.processString(glyphShapesAll, options.renderModes, font, originalTexts[txt_idx]);
- System.err.println("Shapes: "+box);
- for(final GlyphShape gs : glyphShapesAll) {
- gs.setScale(fontScale, fontScale, 1f);
- gs.setColor(0.1f, 0.1f, 0.1f, 1);
- final Vec3f target = gs.getOrigPos(fontScale).add(glyphBox.getMinX(), 0f, 0f);
-
- final float start_pos_x = z_only ? target.x() :
- glyphBox.getMinX() + random.nextFloat() * glyphBox.getWidth();
- final float start_pos_y = z_only ? target.y() :
- glyphBox.getMinY() + random.nextFloat() * glyphBox.getHeight();
- final float start_pos_z = 0f + random.nextFloat() * glyphBox.getHeight() * 1f;
- gs.moveTo(start_pos_x, start_pos_y, start_pos_z);
- }
- // just add destText to scene to be cleaned up, invisible
- testGlyph.setEnabled(false);
- glyphGroup.addShape(testGlyph);
- }
- glyphGroup.addShapes(glyphShapesAll);
- glyphShapesAnim.addAll(glyphShapesAll);
-
- final float pos_eps = FloatUtil.EPSILON * 5000; // ~= 0.0005960
- final float rot_eps = FloatUtil.adegToRad(0.5f); // 1 adeg ~= 0.01745 rad
-
- final long t0_us = Clock.currentNanos() / 1000; // [us]
- final long[] t2_us = { t0_us };
- while (!glyphShapesAnim.isEmpty()) {
- window.invoke(true, (drawable) -> {
- final long t3_us = Clock.currentNanos() / 1000;
- final float dt_s = (t3_us - t2_us[0]) / 1e6f;
- t2_us[0] = t3_us;
-
- final float velocity_px = velocity * 1e3f * pixPerMM; // [px]/[s]
- final float velocity_obj = velocity_px / movingGlyphPixPerShapeUnit[0]; // [shapeUnit]/[s]
- final float dxy = velocity_obj * dt_s; // [shapeUnit]
-
- for (int idx = glyphShapesAnim.size() - 1; 0 <= idx; --idx) {
- final GlyphShape glyph = glyphShapesAnim.get(idx);
- final Vec3f pos = new Vec3f(glyph.getPosition());
- final Vec3f target = glyph.getOrigPos(fontScale).add(glyphBox.getMinX(), 0f, 0f);
- final Vec3f p_t = target.minus(pos);
- final float p_t_diff = p_t.length();
- final Quaternion q = glyph.getRotation();
- final Vec3f euler = q.toEuler(new Vec3f());
- final float radY = euler.y();
- final float radYdiff = Math.min(Math.abs(radY), FloatUtil.TWO_PI - Math.abs(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
- if( DEBUG ) {
- if( 0 == idx ) {
- System.err.println("F: rot: "+radY+" ("+FloatUtil.radToADeg(radY)+"), diff "+radYdiff+" ("+FloatUtil.radToADeg(radYdiff)+"), step "+rot_step+" ("+FloatUtil.radToADeg(rot_step)+")");
- }
- }
- glyph.moveTo(target.x(), target.y(), target.z());
- q.setIdentity();
- glyphShapesAnim.remove(idx);
- continue;
- }
- 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("P: 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, 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%n", has_dur_s, originalTexts[txt_idx].length());
- if( scene.getScreenshotCount() < 1 + originalTexts.length ) {
- scene.screenshot(true, scene.nextScreenshotFile(null, UISceneDemo03b.class.getSimpleName(), options.renderModes, window.getChosenGLCapabilities(), null));
- }
- try { Thread.sleep(2000); } catch (final InterruptedException e1) { }
- if( autoSpeed > 0 ) {
- if( velocity < 60/1000f ) {
- setVelocity(velocity + 9/1000f);
- } else {
- setVelocity(velocity - 9/1000f);
- autoSpeed = -1;
- }
- } else if( autoSpeed < 0 ) {
- if( velocity > 11/1000f ) {
- setVelocity(velocity - 9/1000f);
- } else {
- setVelocity(velocity + 9/1000f);
- autoSpeed = 1;
- }
- }
- txt_idx = ( txt_idx + 1 ) % originalTexts.length;
- } while (options.keepRunning && window.isNativeValid());
- if (!options.stayOpen) {
- window.destroy();
- }
- }
-
- /**
- * Our PMVMatrixSetup:
- * - gluPerspective like Scene's default
- * - no normal scale to 1, keep distance to near plane for rotation effects.
- */
- public static class MyPMVMatrixSetup implements PMVMatrixSetup {
- private final float scene_dist;
- public MyPMVMatrixSetup(final float scene_dist) {
- this.scene_dist = scene_dist;
- }
- @Override
- public void set(final PMVMatrix pmv, final Recti viewport) {
- final float ratio = (float) viewport.width() / (float) viewport.height();
- pmv.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
- pmv.glLoadIdentity();
- pmv.gluPerspective(Scene.DEFAULT_ANGLE, ratio, Scene.DEFAULT_ZNEAR, Scene.DEFAULT_ZFAR);
- pmv.glTranslatef(0f, 0f, scene_dist);
-
- pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
- pmv.glLoadIdentity();
- }
-
- @Override
- public void setPlaneBox(final AABBox planeBox, final PMVMatrix pmv, final Recti viewport) {
- Scene.getDefaultPMVMatrixSetup().setPlaneBox(planeBox, pmv, viewport);
- }
- };
-}
diff --git a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo20.java b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo20.java
index 64904bdb4..799933ba0 100644
--- a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo20.java
+++ b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo20.java
@@ -38,7 +38,6 @@ import java.util.Locale;
import com.jogamp.common.av.AudioSink;
import com.jogamp.common.net.Uri;
import com.jogamp.common.util.IOUtil;
-import com.jogamp.common.util.InterruptSource;
import com.jogamp.common.util.VersionUtil;
import com.jogamp.graph.curve.Region;
import com.jogamp.graph.curve.opengl.RenderState;
@@ -65,7 +64,6 @@ import com.jogamp.newt.MonitorDevice;
import com.jogamp.newt.NewtFactory;
import com.jogamp.newt.Screen;
import com.jogamp.newt.Window;
-import com.jogamp.newt.event.KeyEvent;
import com.jogamp.newt.event.MouseEvent;
import com.jogamp.newt.event.WindowAdapter;
import com.jogamp.newt.event.WindowEvent;
@@ -179,6 +177,7 @@ public class UISceneDemo20 implements GLEventListener {
final Animator animator = new Animator(0 /* w/o AWT */);
animator.setUpdateFPSFrames(5*60, null);
animator.add(window);
+ animator.setExclusiveContext(options.exclusiveContext);
window.addWindowListener(new WindowAdapter() {
@Override
@@ -600,20 +599,13 @@ public class UISceneDemo20 implements GLEventListener {
button = new Button(renderModes, fontButtons, "Quit", buttonLWidth, buttonLHeight);
button.setName(BUTTON_QUIT);
- button.setColor(0.7f, 0.0f, 0.0f, 1.0f);
+ button.setColor(0.7f, 0.3f, 0.3f, 1.0f);
((Button)button).setLabelColor(1.2f, 1.2f, 1.2f);
button.setPressedColorMod(1.1f, 0.0f, 0.0f, 1.0f);
button.addMouseListener(new Shape.MouseGestureAdapter() {
@Override
public void mouseClicked(final MouseEvent e) {
- new InterruptSource.Thread( () -> {
- if( null != cDrawable ) {
- final GLAnimatorControl actrl = cDrawable.getAnimator();
- if( null != actrl ) {
- actrl.stop();
- }
- cDrawable.destroy();
- } } ).start();
+ MiscUtils.destroyWindow(cDrawable);
} } );
button.addMouseListener(dragZoomRotateListener);
buttonsLeft.addShape(button);
@@ -689,7 +681,7 @@ public class UISceneDemo20 implements GLEventListener {
button.addMouseListener(new Shape.MouseGestureAdapter() {
@Override
public void mouseClicked(final MouseEvent e) {
- scene.screenshot(true, scene.nextScreenshotFile(null, UISceneDemo20.class.getSimpleName(), options.renderModes, gl.getContext().getGLDrawable().getChosenGLCapabilities(), null));
+ scene.screenshot(false, scene.nextScreenshotFile(null, UISceneDemo20.class.getSimpleName(), options.renderModes, gl.getContext().getGLDrawable().getChosenGLCapabilities(), null));
} } );
button.addMouseListener(dragZoomRotateListener);
buttonsLeft.addShape(button);
diff --git a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemoU01a.java b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemoU01a.java
index 2269a79aa..c5ba2e918 100644
--- a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemoU01a.java
+++ b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemoU01a.java
@@ -209,6 +209,7 @@ public class UISceneDemoU01a {
@Override
public void init(final GLAutoDrawable drawable) {
final GL2ES2 gl = drawable.getGL().getGL2ES2();
+ System.err.println(JoglVersion.getGLInfo(gl, null));
if( !textOnly ) {
shape = new CrossHair(options.renderModes, normWidgetSize, normWidgetSize, normWidgetSize/100f); // normalized: 1 is 100% surface size (width and/or height)
diff --git a/src/demos/com/jogamp/opengl/demos/util/CommandlineOptions.java b/src/demos/com/jogamp/opengl/demos/util/CommandlineOptions.java
index 81fbbac65..d3b4ae1af 100644
--- a/src/demos/com/jogamp/opengl/demos/util/CommandlineOptions.java
+++ b/src/demos/com/jogamp/opengl/demos/util/CommandlineOptions.java
@@ -36,9 +36,10 @@ public class CommandlineOptions {
public boolean wait_to_start = false;
public boolean keepRunning = false;
public boolean stayOpen = false;
- public int renderModes;
+ public int renderModes = Region.NORM_RENDERING_BIT;
public int sceneMSAASamples = 0;
public float debugBoxThickness = 0f;
+ public boolean exclusiveContext = false;
static {
GLProfile.initSingleton(); // ensure JOGL is completely initialized
@@ -76,12 +77,20 @@ public class CommandlineOptions {
glProfileName = GLProfile.GLES2;
} else if(args[idx[0]].equals("-es3")) {
glProfileName = GLProfile.GLES3;
+ } else if(args[idx[0]].equals("-gl2")) {
+ glProfileName = GLProfile.GL2;
+ } else if(args[idx[0]].equals("-gl3bc")) {
+ glProfileName = GLProfile.GL3bc;
} else if(args[idx[0]].equals("-gl3")) {
glProfileName = GLProfile.GL3;
} else if(args[idx[0]].equals("-gl4")) {
glProfileName = GLProfile.GL4;
+ } else if(args[idx[0]].equals("-gl4bc")) {
+ glProfileName = GLProfile.GL4bc;
} else if(args[idx[0]].equals("-gldef")) {
glProfileName = null;
+ } else if(args[idx[0]].equals("-exclusiveContext")) {
+ exclusiveContext = true;
} else if(args[idx[0]].equals("-wait")) {
wait_to_start = true;
} else if (args[idx[0]].equals("-keep")) {
@@ -91,7 +100,7 @@ public class CommandlineOptions {
stayOpen = true;
} else if(args[idx[0]].equals("-gnone")) {
sceneMSAASamples = 0;
- renderModes = 0;
+ renderModes = Region.NORM_RENDERING_BIT;
} else if(args[idx[0]].equals("-color")) {
renderModes |= Region.COLORCHANNEL_RENDERING_BIT;
} else if(args[idx[0]].equals("-no-color")) {
@@ -119,7 +128,7 @@ public class CommandlineOptions {
@Override
public String toString() {
return "Options{surface[width "+surface_width+" x "+surface_height+"], glp "+glProfileName+
- ", wait "+wait_to_start+", keep "+keepRunning+", stay "+stayOpen+
+ ", exclusiveContext "+exclusiveContext+", wait "+wait_to_start+", keep "+keepRunning+", stay "+stayOpen+
", renderModes "+Region.getRenderModeString(renderModes)+
", smsaa "+sceneMSAASamples+", dbgbox "+debugBoxThickness+"}";
}
diff --git a/src/demos/com/jogamp/opengl/demos/util/MiscUtils.java b/src/demos/com/jogamp/opengl/demos/util/MiscUtils.java
index f1c367e95..9284dfc84 100644
--- a/src/demos/com/jogamp/opengl/demos/util/MiscUtils.java
+++ b/src/demos/com/jogamp/opengl/demos/util/MiscUtils.java
@@ -1,5 +1,5 @@
/**
- * Copyright 2010 JogAmp Community. All rights reserved.
+ * 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:
@@ -25,8 +25,6 @@
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
-
-
package com.jogamp.opengl.demos.util;
import java.io.BufferedReader;
@@ -39,6 +37,8 @@ import java.nio.FloatBuffer;
import java.util.Iterator;
import java.util.List;
+import com.jogamp.opengl.GLAnimatorControl;
+import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLContext;
import com.jogamp.common.os.Platform;
@@ -253,6 +253,19 @@ public class MiscUtils {
System.err.println("\t Total created "+i+" + destroyed "+j+" = "+(i+j));
System.err.println();
}
+
+ public static void destroyWindow(final GLAutoDrawable glad) {
+ if( glad.isRealized() ) {
+ glad.setExclusiveContextThread(null);
+ final GLAnimatorControl actrl = glad.getAnimator();
+ if( null != actrl ) {
+ actrl.stop();
+ }
+ System.err.println("Destroying window from thread "+Thread.currentThread());
+ // Thread.dumpStack();
+ new InterruptSource.Thread( () -> { glad.destroy(); } ).start();
+ }
+ }
}
diff --git a/src/graphui/classes/com/jogamp/graph/ui/AnimGroup.java b/src/graphui/classes/com/jogamp/graph/ui/AnimGroup.java
new file mode 100644
index 000000000..2f3d2bf07
--- /dev/null
+++ b/src/graphui/classes/com/jogamp/graph/ui/AnimGroup.java
@@ -0,0 +1,812 @@
+/**
+ * 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 java.util.Random;
+
+import com.jogamp.common.os.Clock;
+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.Group.Layout;
+import com.jogamp.graph.ui.shapes.GlyphShape;
+import com.jogamp.opengl.GL2ES2;
+import com.jogamp.opengl.GLProfile;
+import com.jogamp.opengl.fixedfunc.GLMatrixFunc;
+import com.jogamp.opengl.math.FloatUtil;
+import com.jogamp.opengl.math.Quaternion;
+import com.jogamp.opengl.math.Recti;
+import com.jogamp.opengl.math.Vec2f;
+import com.jogamp.opengl.math.Vec3f;
+import com.jogamp.opengl.math.Vec4f;
+import com.jogamp.opengl.math.geom.AABBox;
+import com.jogamp.opengl.util.PMVMatrix;
+
+/**
+ * Group of animated {@link Shape}s including other static {@link Shape}s, optionally utilizing a {@link Group.Layout}.
+ * @see Scene
+ * @see Shape
+ * @see Group.Layout
+ */
+public class AnimGroup extends Group {
+ private static final boolean DEBUG = false;
+ /** Epsilon of position, 5000 x {@link FloatUtil#EPSILON} */
+ public static final float POS_EPS = FloatUtil.EPSILON * 5000; // ~= 0.0005960
+ /** Epsilon of rotation [radian], 0.5 degrees or 0.008726646 radians */
+ public static final float ROT_EPS = FloatUtil.adegToRad(0.5f); // 1 adeg ~= 0.01745 rad
+
+ private volatile long tstart_us = 0;
+ private volatile long tlast_us = 0;
+ private volatile long tpause_us = 0;
+ private volatile boolean tickOnDraw = true;
+ private volatile boolean tickPaused = false;
+ private long frame_count = 0;
+
+ /** Animation {@link Shapes} data covering one {@link Shape} of {@link Set}. */
+ public static final class ShapeData {
+ /** Indicator whether the {@link Shapes} is animating or not. */
+ public boolean active;
+ /** {@link Shapes} scaled start position */
+ public final Vec3f startPos;
+ /** {@link Shapes} scaled target position */
+ public final Vec3f targetPos;
+ /** The {@link Shapes} */
+ public final Shape shape;
+ /** Optional user attachment per {@link Shape} to be used within {@link LerpFunc}. */
+ public Object user;
+
+ /** New instance with set {@link Shape} using its scaled {@link Shape#getPosition()} for {@link #startPos} and {@link #targetPos}. */
+ public ShapeData(final Shape s) {
+ active = true;
+ startPos = new Vec3f( s.getPosition() );
+ targetPos = startPos.copy();
+ shape = s;
+ user = null;
+ }
+ }
+
+ /** Animation-Set covering its {@link ShapeData} elements, {@link LerpFunc} and animation parameter. */
+ public static final class Set {
+ /** Pixel per millimeter */
+ public final float pixPerMM;
+ /** Pixel per shape unit */
+ public final Vec2f pixPerShapeUnit;
+ /** Reference {@link Shape} giving reference size */
+ public final Shape refShape;
+
+ /** Translation acceleration in [m]/[s*s] */
+ public final float accel;
+ /** Translation acceleration in [shapeUnit]/[s*s] */
+ public final float accel_obj;
+ /** Start translation velocity in [m]/[s] */
+ public final float start_velocity;
+ /** Start translation velocity in [shapeUnit]/[s] */
+ public final float start_velocity_obj;
+ /** Current translation velocity in [m]/[s] */
+ public float velocity;
+ /** Current translation velocity in [shapeUnit]/[s] */
+ public float velocity_obj;
+
+ /** Angular acceleration in [radians]/[s*s] */
+ public final float ang_accel;
+ /** Start angular velocity in [radians]/[s] */
+ public final float start_ang_velo;
+ /** Current angular velocity in [radians]/[s] */
+ public float ang_velo;
+
+ /** {@link LerpFunc} function */
+ public final LerpFunc lerp;
+
+ /** All {@link Shape}s wrapped within {@link ShapeData}. */
+ public final List<ShapeData> allShapes;
+
+ /** Unscaled bounds of {@link #allShapes} at their original position, size and rotation. */
+ public final AABBox sourceBounds;
+
+ private Set(final float pixPerMM, final float[/*2*/] pixPerShapeUnit, final Shape refShape,
+ final float accel, final float velocity,
+ final float ang_accel, final float ang_velo,
+ final List<ShapeData> allShapes, final AABBox sourceBounds,
+ final LerpFunc lerp) {
+ this.pixPerMM = pixPerMM;
+ this.pixPerShapeUnit = new Vec2f( pixPerShapeUnit );
+ this.refShape = refShape;
+ this.accel = accel;
+ this.start_velocity = velocity;
+ this.velocity = velocity;
+ {
+ final float accel_px = accel * 1e3f * pixPerMM; // [px]/[s*s]
+ this.accel_obj = accel_px / this.pixPerShapeUnit.x(); // [shapeUnit]/[s*s]
+
+ final float velocity_px = velocity * 1e3f * pixPerMM; // [px]/[s]
+ this.start_velocity_obj = velocity_px / this.pixPerShapeUnit.x(); // [shapeUnit]/[s]
+ this.velocity_obj = this.start_velocity_obj;
+ }
+ this.ang_accel = ang_accel;
+ this.start_ang_velo = ang_velo;
+ this.ang_velo = ang_velo;
+ this.lerp = lerp;
+ this.allShapes = allShapes;
+ this.sourceBounds = sourceBounds;
+ }
+
+ /**
+ * Adds given {@link Shape} to this {@link Set} and its {@link AnimGroup} wrapping it in {@link ShapeData}.
+ * <p>
+ * Also issues {@link ShapeSetup#setup(Set, int, ShapeData)}.
+ * </p>
+ * @return newly created {@link ShapeData}
+ */
+ public ShapeData addShape(final AnimGroup g, final Shape s, final ShapeSetup op) {
+ final ShapeData sd = new ShapeData(s);
+ final int idx = this.allShapes.size();
+ this.allShapes.add( sd );
+ this.sourceBounds.resize(sd.shape.getBounds());
+ op.setup(this, idx, sd);
+ g.addShape(sd.shape);
+ return sd;
+ }
+
+ /**
+ * Removes given {@link ShapeData} from this {@link Set} and its {@link AnimGroup}.
+ * <p>
+ * Also destroys the {@link ShapeData}, including its {@link ShapeData} and their {@link Shape}.
+ * </p>
+ */
+ public void removeShape(final AnimGroup g, final GL2ES2 gl, final RegionRenderer renderer, final ShapeData sd) {
+ g.removeShape(gl, renderer, sd.shape);
+ sd.active = false;
+ allShapes.remove(sd);
+ }
+
+ /**
+ * Removes all {@link ShapeData} from this {@link Set} and its {@link AnimGroup}.
+ * <p>
+ * Also destroys the {@link ShapeData}, including its {@link ShapeData} and their {@link Shape}.
+ * </p>
+ */
+ public void removeShapes(final AnimGroup g, final GL2ES2 gl, final RegionRenderer renderer) {
+ for(final ShapeData sd : allShapes) {
+ g.removeShape(gl, renderer, sd.shape);
+ sd.active = false;
+ }
+ allShapes.clear();
+ }
+
+ /** Removes this {@link Set} from its {@link AnimGroup} and destroys it, including its {@link ShapeData} and their {@link Shape}. */
+ private void remove(final AnimGroup g, final GL2ES2 gl, final RegionRenderer renderer) {
+ removeShapes(g, gl, renderer);
+ refShape.destroy(gl, renderer);
+ }
+
+ public void setAnimationActive(final boolean v) {
+ for(final ShapeData sd : allShapes) {
+ sd.active = v;
+ }
+ }
+ public boolean isAnimationActive() {
+ for(final ShapeData sd : allShapes) {
+ if( sd.active ) { return true; }
+ }
+ return false;
+ }
+ }
+ private final List<Set> animSets = new ArrayList<Set>();
+
+ /**
+ * Create a group of animated {@link Shape}s including other static {@link Shape}s w/ given {@link Group.Layout}.
+ * <p>
+ * Default is non-interactive, see {@link #setInteractive(boolean)}.
+ * </p>
+ * @param l optional {@link Layout}, maybe {@code null}
+ */
+ public AnimGroup(final Layout l) {
+ super(l);
+ }
+
+ /** Return the {@link Set} at given index or {@code null} if n/a. */
+ public Set getAnimSet(final int idx) {
+ if( idx < animSets.size() ) {
+ return animSets.get(idx);
+ }
+ return null;
+ }
+
+ /** Removes all {@link Set}s and destroys them, including all {@link ShapeData} and their {@link Shape}s. */
+ public final void removeAllAnimSets(final GL2ES2 gl, final RegionRenderer renderer) {
+ for(final Set as : animSets) {
+ as.remove(this, gl, renderer);
+ }
+ animSets.clear();
+ }
+
+ /** Removes the given {@link Set} and destroys it, including its {@link ShapeData} and {@link Shape}. */
+ public final void removeAnimSet(final GL2ES2 gl, final RegionRenderer renderer, final Set as) {
+ if( null != as ) {
+ as.remove(this, gl, renderer);
+ animSets.remove(as);
+ }
+ }
+
+ /** Removes the given {@link Set}s and destroys them, including their {@link ShapeData} and {@link Shape}. */
+ public final void removeAnimSets(final GL2ES2 gl, final RegionRenderer renderer, final List<Set> asList) {
+ for(final Set as : asList) {
+ if( null != as ) {
+ as.remove(this, gl, renderer);
+ animSets.remove(as);
+ }
+ }
+ }
+
+ /**
+ * {@link ShapeData} setup function for animation using its enclosing {@link Set} and other data points
+ * <p>
+ * At minimum, {@link ShapeData}'s {@link ShapeData#startPos} and {@link ShapeData#targetPos} shall be adjusted.
+ * </p>
+ */
+ public static interface ShapeSetup {
+ /**
+ * Setting up the {@link ShapeData} for animation using its enclosing {@link Set} and other data points
+ * @param as {@link Set} of the animation
+ * @param idx {@link ShapeData} index within the {@link Set#allShapes}
+ * @param sd the {@link ShapeData} matching {@code idx} containing the {@link Shape} to apply this operation
+ */
+ public void setup(final Set as, final int idx, final ShapeData sd);
+ }
+
+ /**
+ * Linear interpolation (LERP) function to evaluate the next animated frame for each {@link ShapeData} of a {@link Set}.
+ * @see AnimGroup.TargetLerp
+ */
+ public static interface LerpFunc {
+ /**
+ * Evaluate next LERP step for the given {@link ShapeData} within the animation {@link Set}.
+ * @param frame_cnt frame count for the given {@link ShapeData}
+ * @param as {@link Set} of the animation
+ * @param idx {@link ShapeData} index within the {@link Set#allShapes}
+ * @param sd the {@link ShapeData} matching {@code idx} containing the {@link Shape} to apply this operation
+ * @param at_s time delta to animation start, i.e. animation duration [s]
+ * @param dt_s time delta to last call [s]
+ * @return true if target animation shall continue, false otherwise
+ */
+ public boolean eval(long frame_cnt, Set as, final int idx, ShapeData sd, float at_s, float dt_s);
+ }
+
+ /**
+ * Add a new {@link Set} with an empty {@link ShapeData} container.
+ * <p>
+ * 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
+ * only, without a shape's {@link #setTransform(PMVMatrix)}. See {@link Scene.PMVMatrixSetup#set(PMVMatrix, Recti)}.
+ * </p>
+ * @param pixPerMM monitor pixel per millimeter for accurate animation
+ * @param glp used {@link GLProfile}
+ * @param pmv well formed {@link PMVMatrix}, e.g. could have been setup via {@link Scene.PMVMatrixSetup#set(PMVMatrix, Recti)}.
+ * @param viewport the int[4] viewport
+ * @param accel translation acceleration in [m]/[s*s]
+ * @param velocity translation velocity in [m]/[s]
+ * @param ang_accel angular acceleration in [radians]/[s*s], usable for rotation etc
+ * @param ang_velo angular velocity in [radians]/[s], usable for rotation etc
+ * @param lerp {@link LerpFunc} function, see {@link AnimGroup.TargetLerp}
+ * @param refShape reference {@link Shape} giving reference size, see {@link #refShape}
+ * @param op {@link ShapeData} setup function for {@link ShapeData#startPos} and {@link ShapeData#targetPos}
+ * @return a new {@link Set} instance
+ */
+ public Set addAnimSet(final float pixPerMM,
+ final GLProfile glp, final PMVMatrix pmv, final Recti viewport,
+ final float accel, final float velocity,
+ final float ang_accel, final float ang_velo,
+ final LerpFunc lerp, final Shape refShape)
+ {
+ final Set as;
+ refShape.validate(glp);
+ pmv.glPushMatrix();
+ {
+ refShape.setTransform(pmv);
+ as = new Set(pixPerMM, refShape.getPixelPerShapeUnit(pmv, viewport, new float[2]), refShape,
+ accel, velocity, ang_accel, ang_velo,
+ new ArrayList<ShapeData>(), new AABBox(), lerp);
+ }
+ pmv.glPopMatrix();
+ animSets.add(as);
+ return as;
+ }
+
+ /**
+ * Add a new {@link Set} with {@link ShapeData} for each {@link GlyphShape}, moving towards its target position
+ * using a generic displacement via {@link ShapeSetup} to determine each {@link ShapeData}'s starting position.
+ * <p>
+ * 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
+ * only, without a shape's {@link #setTransform(PMVMatrix)}. See {@link Scene.PMVMatrixSetup#set(PMVMatrix, Recti)}.
+ * </p>
+ * @param pixPerMM monitor pixel per millimeter for accurate animation
+ * @param glp used {@link GLProfile}
+ * @param pmv well formed {@link PMVMatrix}, e.g. could have been setup via {@link Scene.PMVMatrixSetup#set(PMVMatrix, Recti)}.
+ * @param viewport the int[4] viewport
+ * @param renderModes used {@link GLRegion#create(GLProfile, int, com.jogamp.opengl.util.texture.TextureSequence) region render-modes}
+ * @param font {@link Font} to be used for resulting {@link GlyphShape}s
+ * @param refChar reference character to calculate the reference {@link GlyphShape}
+ * @param text the text for resulting {@link GlyphShape}s
+ * @param fontScale font scale factor for resulting {@link GlyphShape}s
+ * @param accel translation acceleration in [m]/[s*s]
+ * @param velocity translation velocity in [m]/[s]
+ * @param ang_accel angular acceleration in [radians]/[s*s], usable for rotation etc
+ * @param ang_velo angular velocity in [radians]/[s], usable for rotation etc
+ * @param lerp {@link LerpFunc} function, see {@link AnimGroup.TargetLerp}
+ * @param op {@link ShapeData} setup function for {@link ShapeData#startPos} and {@link ShapeData#targetPos}
+ * @return newly created and added {@link Set}
+ */
+ public final Set addGlyphSet(final float pixPerMM,
+ final GLProfile glp, final PMVMatrix pmv, final Recti viewport, final int renderModes,
+ final Font font, final char refChar, final CharSequence text, final float fontScale,
+ final float accel, final float velocity, final float ang_accel, final float ang_velo,
+ final LerpFunc lerp, final ShapeSetup op)
+ {
+ final Set as;
+ {
+ final List<ShapeData> allShapes = new ArrayList<ShapeData>();
+ final AABBox sourceBounds = processString(allShapes, renderModes, font, fontScale, text);
+ final GlyphShape refShape = new GlyphShape(renderModes, font, refChar, 0, 0);
+ refShape.setScale(fontScale, fontScale, 1f);
+ refShape.validate(glp);
+ pmv.glPushMatrix();
+ {
+ refShape.setTransform(pmv);
+ as = new Set(pixPerMM, refShape.getPixelPerShapeUnit(pmv, viewport, new float[2]), refShape,
+ accel, velocity, ang_accel, ang_velo, allShapes, sourceBounds, lerp);
+ }
+ pmv.glPopMatrix();
+ }
+ animSets.add(as);
+
+ for (int idx = 0; idx < as.allShapes.size(); ++idx) {
+ final ShapeData sd = as.allShapes.get(idx);
+ op.setup(as, idx, sd);
+ super.addShape(sd.shape);
+ }
+ if( DEBUG ) {
+ System.err.println("addAnimShapes: AnimSet.sourceBounds = "+as.sourceBounds);
+ }
+ resetAnimation();
+ return as;
+ }
+ private static final AABBox processString(final List<ShapeData> res, final int renderModes,
+ final Font font, final float fontScale, final CharSequence 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() ) {
+ final GlyphShape gs = new GlyphShape(renderModes, symbol, glyph, t.getTranslateX(), t.getTranslateY());
+ gs.setScale(fontScale, fontScale, 1f);
+ gs.moveTo(gs.getOrigPos().x()*fontScale, gs.getOrigPos().y()*fontScale, gs.getOrigPos().z());
+ res.add( new ShapeData( gs ) );
+ }
+ }
+ };
+ return font.processString(fgv, null, text, new AffineTransform(), new AffineTransform());
+ }
+
+ /**
+ * Add a new {@link Set} with {@link ShapeData} for each {@link GlyphShape}, moving towards its target position
+ * using a fixed displacement function, defining each {@link ShapeData}'s starting position.
+ * <p>
+ * The start-position is randomly chosen within given {@link AABBox} glyphBox.
+ * </p>
+ * <p>
+ * 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
+ * only, without a shape's {@link #setTransform(PMVMatrix)}. See {@link Scene.PMVMatrixSetup#set(PMVMatrix, Recti)}.
+ * </p>
+ * @param pixPerMM monitor pixel per millimeter for accurate animation
+ * @param glp used {@link GLProfile}
+ * @param pmv well formed {@link PMVMatrix}, e.g. could have been setup via {@link Scene.PMVMatrixSetup#set(PMVMatrix, Recti)}.
+ * @param viewport the int[4] viewport
+ * @param renderModes used {@link GLRegion#create(GLProfile, int, com.jogamp.opengl.util.texture.TextureSequence) region render-modes}
+ * @param font {@link Font} to be used for resulting {@link GlyphShape}s
+ * @param text the text for resulting {@link GlyphShape}s
+ * @param fontScale font scale factor for resulting {@link GlyphShape}s
+ * @param fgCol foreground color for resulting {@link GlyphShape}s
+ * @param accel translation acceleration in [m]/[s*s]
+ * @param velocity translation velocity in [m]/[s]
+ * @param ang_accel angular acceleration in [radians]/[s*s], usable for rotation etc
+ * @param ang_velo angular velocity in [radians]/[s], usable for rotation etc
+ * @param animBox {@link AABBox} denoting the maximum extend of {@link ShapeData}s start-position, also used for their x-offset
+ * @param z_only Pass true for z-only distance
+ * @param random the random float generator
+ * @param lerp {@link LerpFunc} function, see {@link AnimGroup.TargetLerp}
+ * @return newly created and added {@link Set}
+ */
+ public final Set addGlyphSetRandom01(final float pixPerMM,
+ final GLProfile glp, final PMVMatrix pmv, final Recti viewport, final int renderModes,
+ final Font font, final CharSequence text, final float fontScale, final Vec4f fgCol,
+ final float accel, final float velocity, final float ang_accel, final float ang_velo,
+ final AABBox animBox, final boolean z_only, final Random random, final LerpFunc lerp)
+ {
+ return addGlyphSet(pixPerMM, glp, pmv, viewport, renderModes, font, 'X', text, fontScale,
+ accel, velocity, ang_accel, ang_velo, lerp, (final Set as, final int idx, final ShapeData sd) -> {
+ sd.shape.setColor(fgCol);
+
+ // shift targetPost to glyphBox.getMinX()
+ sd.targetPos.add(animBox.getMinX(), 0f, 0f);
+
+ final Vec3f target = sd.targetPos;
+
+ sd.startPos.set( z_only ? target.x() : animBox.getMinX() + random.nextFloat() * animBox.getWidth(),
+ z_only ? target.y() : animBox.getMinY() + random.nextFloat() * animBox.getHeight(),
+ 0f + random.nextFloat() * animBox.getHeight() * 1f);
+ sd.shape.moveTo(sd.startPos);
+ } );
+ }
+
+ /**
+ * Add a new {@link Set} with {@link ShapeData} for each {@link GlyphShape}, implementing<br/>
+ * horizontal continuous scrolling while repeating the given {@code text}.
+ * <p>
+ * 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
+ * only, without a shape's {@link #setTransform(PMVMatrix)}. See {@link Scene.PMVMatrixSetup#set(PMVMatrix, Recti)}.
+ * </p>
+ * @param pixPerMM monitor pixel per millimeter for accurate animation
+ * @param glp used {@link GLProfile}
+ * @param pmv well formed {@link PMVMatrix}, e.g. could have been setup via {@link Scene.PMVMatrixSetup#set(PMVMatrix, Recti)}.
+ * @param viewport the int[4] viewport
+ * @param renderModes used {@link GLRegion#create(GLProfile, int, com.jogamp.opengl.util.texture.TextureSequence) region render-modes}
+ * @param font {@link Font} to be used for resulting {@link GlyphShape}s
+ * @param text the text for resulting {@link GlyphShape}s
+ * @param fontScale font scale factor for resulting {@link GlyphShape}s
+ * @param fgCol foreground color for resulting {@link GlyphShape}s
+ * @param velocity translation velocity in [m]/[s]
+ * @param animBox {@link AABBox} denoting the maximum extend of {@link ShapeData}s start-position, also used for their x-offset
+ * @return newly created and added {@link Set}
+ */
+ public final Set addGlyphSetHorizScroll01(final float pixPerMM,
+ final GLProfile glp, final PMVMatrix pmv, final Recti viewport, final int renderModes,
+ final Font font, final CharSequence text, final float fontScale, final Vec4f fgCol,
+ final float velocity, final AABBox animBox, final float y_offset)
+ {
+ return addGlyphSet(pixPerMM, glp, pmv, viewport,
+ renderModes, font, 'X', text, fontScale,
+ 0f /* accel */, velocity, 0f /* ang_accel */, 0f /* 1-rotation/s */,
+ new AnimGroup.ScrollLerp(animBox),
+ (final AnimGroup.Set as, final int idx, final AnimGroup.ShapeData sd) -> {
+ sd.shape.setColor(fgCol);
+
+ sd.targetPos.set(animBox.getMinX(), y_offset, 0);
+
+ sd.startPos.set( sd.startPos.x() + animBox.getMaxX(), sd.targetPos.y(), sd.targetPos.z());
+
+ sd.shape.moveTo( sd.startPos );
+ } );
+ }
+
+ /** Sets whether {@link #tick()} shall be automatic issued on {@link #draw(GL2ES2, RegionRenderer, int[])}, default is {@code true}. */
+ public final void setTickOnDraw(final boolean v) { tickOnDraw = v; }
+ public final boolean getTickOnDraw() { return tickOnDraw; }
+
+ /**
+ * Sets whether {@link #tick()} shall be paused, default is {@code false}.
+ * <p>
+ * Unpausing {@link #tick()} will also forward animation start-time about paused duration,
+ * as well as set last-tick timestamp to now. This prevents animation artifacts and resumes where left off.
+ * </p>
+ */
+ public final void setTickPaused(final boolean v) {
+ if( tickPaused == v ) {
+ return;
+ }
+ if( v ) {
+ tickPaused = true;
+ tpause_us = Clock.currentNanos() / 1000; // [us]
+ } else {
+ final long tnow_us = Clock.currentNanos() / 1000; // [us]
+ final long dtP_us = tnow_us - tpause_us;
+ tstart_us += dtP_us;
+ tlast_us += dtP_us;
+ tickPaused = false;
+ }
+ }
+ public final boolean getTickPaused() { return tickPaused; }
+
+ @Override
+ public void draw(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) {
+ if( tickOnDraw && !tickPaused) {
+ tickImpl();
+ }
+ super.draw(gl, renderer, sampleCount);
+ }
+
+ public final void resetAnimation() {
+ tstart_us = Clock.currentNanos() / 1000; // [us]
+ tlast_us = tstart_us;
+ frame_count = 0;
+ }
+
+ public final void restartAnimation() {
+ super.runSynced( () -> {
+ for(final Set as : animSets) {
+ as.setAnimationActive(true);
+ } } );
+ resetAnimation();
+ }
+
+ public void stopAnimation() {
+ super.runSynced( () -> {
+ for(final Set as : animSets) {
+ as.setAnimationActive(false);
+ } } );
+ }
+
+ public final boolean isAnimationActive() {
+ for(final Set as : animSets) {
+ if( as.isAnimationActive() ) { return true; }
+ }
+ return false;
+ }
+
+ /**
+ * Issues an animation tick, usually done at {@link #draw(GL2ES2, RegionRenderer, int[])}.
+ * @see #setTickOnDraw(boolean)
+ * @see #setTickPaused(boolean)
+ */
+ public final void tick() {
+ if( !tickPaused ) {
+ super.runSynced( () -> { tickImpl(); } );
+ }
+ }
+ private final void tickImpl() {
+ final long tnow_us = Clock.currentNanos() / 1000;
+ final float at_s = (tnow_us - tstart_us) / 1e6f;
+ final float dt_s = (tnow_us - tlast_us) / 1e6f;
+ tlast_us = tnow_us;
+ for(final Set as : animSets) {
+ if( as.isAnimationActive() ) {
+ if( !FloatUtil.isZero( as.accel ) ) {
+ as.velocity += as.accel * dt_s; // [shapeUnit]/[s]
+ as.velocity_obj += as.accel_obj * dt_s; // [shapeUnit]/[s]
+ }
+ if( !FloatUtil.isZero( as.ang_accel ) ) {
+ as.ang_velo += as.ang_accel * dt_s; // [radians]/[s]
+ }
+ for (int idx = 0; idx < as.allShapes.size(); ++idx) {
+ final ShapeData sd = as.allShapes.get(idx);
+ if( !as.lerp.eval(frame_count, as, idx, sd, at_s, dt_s) ) {
+ sd.active = false;
+ }
+ }
+ }
+ }
+ ++frame_count;
+ }
+
+ /**
+ * Default target {@link LerpFunc}, approaching {@link ShapeData}'s target position inclusive angular rotation around given normalized axis.
+ * <p>
+ * Implementation uses the current shape position and time delta since last call,
+ * hence allows rugged utilization even if shapes are dragged around.
+ * </p>
+ */
+ public static class TargetLerp implements LerpFunc {
+ final Vec3f rotAxis;
+ /**
+ * New target {@link LerpFunc} instance
+ * @param rotAxis normalized axis vector for {@link Quaternion#rotateByAngleNormalAxis(float, Vec3f)}
+ */
+ public TargetLerp(final Vec3f rotAxis) {
+ this.rotAxis = rotAxis;
+ }
+ @Override
+ public boolean eval(final long frame_cnt, final Set as, final int idx, final ShapeData sd, final float at_s, final float dt_s) {
+ final float dxy = as.velocity_obj * dt_s; // [shapeUnit]
+ final float rot_step = as.ang_velo * dt_s; // [radians]
+ final float shapeScale = sd.shape.getScale().y();
+ final Vec3f pos = sd.shape.getPosition().copy();
+ final Vec3f p_t = sd.targetPos.minus(pos);
+ final float p_t_diff = p_t.length();
+ final Quaternion q = sd.shape.getRotation();
+ final Vec3f euler = q.toEuler(new Vec3f());
+ final float rotAng = euler.length();
+ final float rotAngDiff = Math.min(Math.abs(rotAng), FloatUtil.TWO_PI - Math.abs(rotAng));
+ final boolean pos_ok = p_t_diff <= AnimGroup.POS_EPS;
+ final boolean pos_near = pos_ok || p_t_diff <= sd.shape.getBounds().getSize() * shapeScale * 2f;
+ final boolean rot_ok = pos_near && ( rot_step < AnimGroup.ROT_EPS || rotAngDiff <= AnimGroup.ROT_EPS || rotAngDiff <= rot_step * 2f );
+ if ( pos_ok && rot_ok ) {
+ // arrived
+ if( DEBUG ) {
+ if( 0 == idx ) {
+ System.err.println("F: dt "+(dt_s*1000f)+" ms, p_t[OK "+pos_ok+", near "+pos_near+", diff: "+p_t_diff+", dxy "+dxy+"], rot[OK "+rot_ok+", radY "+rotAng+" ("+FloatUtil.radToADeg(rotAng)+"), diff "+rotAngDiff+" ("+FloatUtil.radToADeg(rotAngDiff)+"), ang_velo "+as.ang_velo+", step "+rot_step+" ("+FloatUtil.radToADeg(rot_step)+")]");
+ }
+ }
+ sd.shape.moveTo(sd.targetPos);
+ q.setIdentity();
+ sd.shape.setInteractive(false);
+ return false;
+ }
+ if( !pos_ok ) {
+ if( DEBUG ) {
+ if( 0 == idx ) {
+ System.err.println("P: dt "+(dt_s*1000f)+" ms, p_t[OK "+pos_ok+", near "+pos_near+", diff: "+p_t_diff+", dxy "+dxy+"], rot[OK "+rot_ok+", radY "+rotAng+" ("+FloatUtil.radToADeg(rotAng)+"), diff "+rotAngDiff+" ("+FloatUtil.radToADeg(rotAngDiff)+"), ang_velo "+as.ang_velo+", step "+rot_step+" ("+FloatUtil.radToADeg(rot_step)+")]");
+ }
+ }
+ if( p_t_diff <= dxy || p_t_diff <= AnimGroup.POS_EPS ) {
+ sd.shape.moveTo(sd.targetPos);
+ } else {
+ pos.add( p_t.normalize().scale( dxy ) );
+ sd.shape.moveTo(pos);
+ }
+ if( !rot_ok ) {
+ if( pos_near ) {
+ q.rotateByAngleNormalAxis( rot_step * 2f, rotAxis );
+ } else {
+ q.rotateByAngleNormalAxis( rot_step, rotAxis );
+ }
+ }
+ } else {
+ if( DEBUG ) {
+ if( 0 == idx ) {
+ System.err.println("p: dt "+(dt_s*1000f)+" ms, p_t[OK "+pos_ok+", near "+pos_near+", diff: "+p_t_diff+", dxy "+dxy+"], rot[OK "+rot_ok+", radY "+rotAng+" ("+FloatUtil.radToADeg(rotAng)+"), diff "+rotAngDiff+" ("+FloatUtil.radToADeg(rotAngDiff)+"), ang_velo "+as.ang_velo+", step "+rot_step+" ("+FloatUtil.radToADeg(rot_step)+")]");
+ }
+ }
+ if( rot_ok || rotAngDiff <= rot_step * 3f ) {
+ q.setIdentity();
+ } else {
+ q.rotateByAngleNormalAxis( rot_step * 3f, rotAxis );
+ }
+ }
+ return true;
+ }
+ };
+
+ /**
+ * Scrolling {@link LerpFunc}, approaching {@link ShapeData}'s target position over and over.
+ * <p>
+ * Implementation uses the current shape position and time delta since last call,
+ * hence allows rugged utilization even if shapes are dragged around.
+ * </p>
+ */
+ public static class ScrollLerp implements LerpFunc {
+ final AABBox clip;
+ /**
+ * New scroller {@link LerpFunc} instance
+ * @param clip clipping box for each shape
+ */
+ public ScrollLerp(final AABBox clip) {
+ this.clip = clip;
+ }
+ @Override
+ public boolean eval(final long frame_cnt, final Set as, final int idx, final ShapeData sd, final float at_s, final float dt_s) {
+ final float dxy = as.velocity_obj * dt_s; // [shapeUnit]
+ final Vec3f pos = sd.shape.getPosition().copy();
+ final Vec3f p_t = sd.targetPos.minus(pos);
+ final float p_t_diff = p_t.length();
+ final boolean pos_ok = p_t_diff <= dxy || p_t_diff <= AnimGroup.POS_EPS;
+ if ( pos_ok ) {
+ // arrived -> restart
+ if( 0 == idx ) {
+ as.velocity = as.start_velocity;
+ as.velocity_obj = as.start_velocity_obj;
+ as.ang_velo = as.start_ang_velo;
+ final ShapeData sd_last = as.allShapes.get(as.allShapes.size()-1);
+ final Vec3f v_thisstart_lastpos = sd_last.shape.getPosition().minus( sd.startPos );
+ final float angle_thisstart_lastpos = Vec3f.UNIT_X.angle(v_thisstart_lastpos);
+ if( angle_thisstart_lastpos >= FloatUtil.HALF_PI ) {
+ // start position of this is 'right of' current position of last: short shape-string case
+ pos.set( sd.startPos );
+ } else {
+ // start position of this is 'left of' current position of last: long shape-string case
+ pos.set( sd_last.shape.getPosition() ).add( Vec3f.UNIT_X.mul( sd_last.shape.getScaledWidth() * 2f ) );
+ }
+ // System.err.println("Scroll-0: idx "+idx+", this "+sd.shape.getPosition()+", lst "+sd_last.shape.getPosition()+", angle "+angle_thisstart_lastpos+" rad ("+FloatUtil.radToADeg(angle_thisstart_lastpos)+" deg) -> "+pos);
+ } else {
+ final ShapeData sd_pre = as.allShapes.get(idx-1);
+ final Vec3f diff_start_pre_this = sd.startPos.minus( sd_pre.startPos );
+ pos.set( sd_pre.shape.getPosition() ).add( diff_start_pre_this );
+ // System.err.println("Scroll-n: idx "+idx+", this "+sd.shape.getPosition()+", pre "+sd_pre.shape.getPosition()+" -> "+pos);
+ }
+ } else {
+ pos.add( p_t.normalize().scale( dxy ) );
+ }
+ if( clip.intersects2DRegion(pos.x(), pos.y(), sd.shape.getScaledWidth(), sd.shape.getScaledHeight()) ) {
+ sd.shape.setEnabled(true);
+ } else {
+ sd.shape.setEnabled(false);
+ }
+ sd.shape.moveTo(pos);
+ return true;
+ }
+ };
+
+ /**
+ * Sine target {@link LerpFunc}, approaching {@link ShapeData}'s target position utilizing the angular value for sine amplitude
+ * towards the given normalized direction vector.
+ * <p>
+ * The sine amplitude is flattened towards target.
+ * </p>
+ * <p>
+ * Implementation uses the current shape position and relative time duration since last call to interpolate,
+ * hence allows rugged utilization even if shapes are dragged around.
+ * </p>
+ */
+ public static class SineLerp implements LerpFunc {
+ final Vec3f sineDir;
+ final float sineScale;
+ final float shapeStep;
+
+ /**
+ * New sine {@link LerpFunc} instance
+ * @param sineDir normalized vector for sine amplitude direction
+ * @param sineScale sine scale factor to amplify effect
+ * @param shapeStep shape index {@code idx} factor for {@code dt_s}, amplifying angular distance between each shape. Golden ratio {@code 1.618f} reveals dynamic characteristics.
+ */
+ public SineLerp(final Vec3f sineDir, final float sineScale, final float shapeStep) {
+ this.sineDir = sineDir;
+ this.sineScale = sineScale;
+ this.shapeStep = shapeStep;
+ }
+ @Override
+ public boolean eval(final long frame_cnt, final Set as, final int idx, final ShapeData sd, final float at_s, final float dt_s) {
+ final float dxy = as.velocity_obj * dt_s; // [shapeUnit]
+ final float angle = as.ang_velo * ( at_s + idx * shapeStep * dt_s ); // [radians]
+ final Vec3f pos = sd.shape.getPosition().copy();
+ if( 0 == frame_cnt ) {
+ sd.user = null;
+ } else if( null != sd.user ) {
+ final Vec3f lastSineVal = (Vec3f)sd.user;
+ pos.sub(lastSineVal);
+ }
+ final Vec3f p_t = sd.targetPos.minus(pos);
+ final float p_t_diff = p_t.length();
+ final boolean pos_ok = p_t_diff <= dxy || p_t_diff <= AnimGroup.POS_EPS;
+
+ if ( pos_ok ) {
+ // arrived
+ sd.shape.moveTo(sd.targetPos);
+ sd.shape.setInteractive(false);
+ return false;
+ } else {
+ final float shapeScale = sd.shape.getScale().y();
+ final float p_t_norm;
+ {
+ final Vec3f s_t = sd.targetPos.minus(sd.startPos);
+ p_t_norm = p_t_diff / s_t.length(); // [1 -> 0] from start to target
+ }
+ final float sineAmp = FloatUtil.sin(angle)*p_t_norm*shapeScale*sineScale;
+ final Vec3f sineVec = sineDir.copy().scale( sineAmp );
+ sd.user = sineVec;
+ sd.shape.moveTo( pos.add( p_t.normalize().scale( dxy ) ).add( sineVec ) );
+ }
+ return true;
+ }
+ static final boolean methodB = true;
+ };
+}