diff options
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; + }; +} |