diff options
Diffstat (limited to 'src')
38 files changed, 3523 insertions, 1231 deletions
diff --git a/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java b/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java index be5a1d1bf..fb0ff6a7e 100644 --- a/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java +++ b/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.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: @@ -43,19 +43,32 @@ import com.jogamp.opengl.math.FloatUtil; import com.jogamp.opengl.math.VectorUtil; import com.jogamp.opengl.math.geom.AABBox; - /** * A Generic shape objects which is defined by a list of Outlines. * This Shape can be transformed to triangulations. * The list of triangles generated are render-able by a Region object. * The triangulation produced by this Shape will define the * closed region defined by the outlines. - * + * <p> * One or more OutlineShape Object can be associated to a region * this is left as a high-level representation of the Objects. For * optimizations, flexibility requirements for future features. - * - * <br><br> + * </p> + * <p> + * <a name="windingrules"> + * Outline shape general {@link Winding} rules + * <ul> + * <li>Outer boundary shapes are required as {@link Winding#CCW}, if unsure + * <ul> + * <li>You may check {@link Winding} via {@link #getWindingOfLastOutline()} or {@link Outline#getWinding()} (optional)</li> + * <li>Use {@link #setWindingOfLastOutline(Winding)} before {@link #closeLastOutline(boolean)} or {@link #closePath()} } to enforce {@link Winding#CCW}, or</li> + * <li>use {@link Outline#setWinding(Winding)} on a specific {@link Outline} to enforce {@link Winding#CCW}.</li> + * <li>If e.g. the {@link Winding} has changed for an {@link Outline} by above operations, its vertices have been reversed.</li> + * </ul></li> + * <li>Inner shapes or holes are adjusted to be {@link Winding#CW}, no user consideration is required here.</li> + * <li>Safe path: Simply create all shapes with {@link Winding#CCW} or apply {@link Outline#setWinding(Winding)}.</li> + * </ul> + * </p> * Example to creating an Outline Shape: * <pre> addVertex(...) @@ -67,33 +80,35 @@ import com.jogamp.opengl.math.geom.AABBox; addVertex(...) * </pre> * + * <p> * The above will create two outlines each with three vertices. By adding these two outlines to * the OutlineShape, we are stating that the combination of the two outlines represent the shape. - * <br> - * + * </p> + * <p> * To specify that the shape is curved at a region, the on-curve flag should be set to false * for the vertex that is in the middle of the curved region (if the curved region is defined by 3 * vertices (quadratic curve). - * <br> + * </p> + * <p> * In case the curved region is defined by 4 or more vertices the middle vertices should both have * the on-curve flag set to false. - * - * <br>Example: <br> + * </p> + * Example: * <pre> addVertex(0,0, true); addVertex(0,1, false); addVertex(1,1, false); addVertex(1,0, true); * </pre> - * + * <p> * The above snippet defines a cubic nurbs curve where (0,1 and 1,1) * do not belong to the final rendered shape. + * </p> * * <i>Implementation Notes:</i><br> * <ul> * <li> The first vertex of any outline belonging to the shape should be on-curve</li> * <li> Intersections between off-curved parts of the outline is not handled</li> - * <li> Outline shape winding shall be constructed counter clock wise ({@link Winding#CCW}).</li> * </ul> * * @see Outline @@ -113,6 +128,18 @@ public final class OutlineShape implements Comparable<OutlineShape> { } } + /** + * General purpose {@link OutlineShape} visitor. + */ + public static interface Visitor { + /** + * Visiting the given {@link OutlineShape} with it's corresponding {@link AffineTransform}. + * @param shape may be used as is, otherwise a copy shall be made if intended to be modified. + * @param t may be used immediately as is, otherwise a copy shall be made if stored. + */ + public void visit(final OutlineShape shape, final AffineTransform t); + } + /** Initial {@link #getSharpness()} value, which can be modified via {@link #setSharpness(float)}. */ public static final float DEFAULT_SHARPNESS = 0.5f; @@ -214,6 +241,21 @@ public final class OutlineShape implements Comparable<OutlineShape> { } /** + * Compute the {@link Winding} of the {@link #getLastOutline()} using the {@link #area(ArrayList)} function over all of its vertices. + * @return {@link Winding#CCW} or {@link Winding#CW} + */ + public final Winding getWindingOfLastOutline() { + return getLastOutline().getWinding(); + } + + /** + * Sets the enforced {@link Winding} of the {@link #getLastOutline()}. + */ + public final void setWindingOfLastOutline(final Winding enforced) { + getLastOutline().setWinding(enforced); + } + + /** * Add a new empty {@link Outline} * to the end of this shape's outline list. * <p>If the {@link #getLastOutline()} is empty already, no new one will be added.</p> @@ -346,9 +388,8 @@ public final class OutlineShape implements Comparable<OutlineShape> { /** * Adds a vertex to the last open outline to the shape's tail. * - * The constructed shape should be {@link Winding#CCW}. - * * @param v the vertex to be added to the OutlineShape + * @see <a href="#windingrules">see winding rules</a> */ public final void addVertex(final Vertex v) { final Outline lo = getLastOutline(); @@ -363,10 +404,9 @@ public final class OutlineShape implements Comparable<OutlineShape> { /** * Adds a vertex to the last open outline to the shape at {@code position} * - * The constructed shape should be {@link Winding#CCW}. - * * @param position index within the last open outline, at which the vertex will be added * @param v the vertex to be added to the OutlineShape + * @see <a href="#windingrules">see winding rules</a> */ public final void addVertex(final int position, final Vertex v) { final Outline lo = getLastOutline(); @@ -381,12 +421,10 @@ public final class OutlineShape implements Comparable<OutlineShape> { * Add a 2D {@link Vertex} to the last open outline to the shape's tail. * The 2D vertex will be represented as Z=0. * - * The constructed shape should be {@link Winding#CCW}. - * * @param x the x coordinate * @param y the y coordniate - * @param onCurve flag if this vertex is on the final curve or defines a curved region - * of the shape around this vertex. + * @param onCurve flag if this vertex is on the final curve or defines a curved region of the shape around this vertex. + * @see <a href="#windingrules">see winding rules</a> */ public final void addVertex(final float x, final float y, final boolean onCurve) { addVertex(vertexFactory.create(x, y, 0f, onCurve)); @@ -396,13 +434,11 @@ public final class OutlineShape implements Comparable<OutlineShape> { * Add a 2D {@link Vertex} to the last open outline to the shape at {@code position}. * The 2D vertex will be represented as Z=0. * - * The constructed shape should be {@link Winding#CCW}. - * * @param position index within the last open outline, at which the vertex will be added * @param x the x coordinate * @param y the y coordniate - * @param onCurve flag if this vertex is on the final curve or defines a curved region - * of the shape around this vertex. + * @param onCurve flag if this vertex is on the final curve or defines a curved region of the shape around this vertex. + * @see <a href="#windingrules">see winding rules</a> */ public final void addVertex(final int position, final float x, final float y, final boolean onCurve) { addVertex(position, vertexFactory.create(x, y, 0f, onCurve)); @@ -411,13 +447,11 @@ public final class OutlineShape implements Comparable<OutlineShape> { /** * Add a 3D {@link Vertex} to the last open outline to the shape's tail. * - * The constructed shape should be {@link Winding#CCW}. - * * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate - * @param onCurve flag if this vertex is on the final curve or defines a curved region - * of the shape around this vertex. + * @param onCurve flag if this vertex is on the final curve or defines a curved region of the shape around this vertex. + * @see <a href="#windingrules">see winding rules</a> */ public final void addVertex(final float x, final float y, final float z, final boolean onCurve) { addVertex(vertexFactory.create(x, y, z, onCurve)); @@ -426,14 +460,12 @@ public final class OutlineShape implements Comparable<OutlineShape> { /** * Add a 3D {@link Vertex} to the last open outline to the shape at {@code position}. * - * The constructed shape should be {@link Winding#CCW}. - * * @param position index within the last open outline, at which the vertex will be added * @param x the x coordinate * @param y the y coordniate * @param z the z coordinate - * @param onCurve flag if this vertex is on the final curve or defines a curved region - * of the shape around this vertex. + * @param onCurve flag if this vertex is on the final curve or defines a curved region of the shape around this vertex. + * @see <a href="#windingrules">see winding rules</a> */ public final void addVertex(final int position, final float x, final float y, final float z, final boolean onCurve) { addVertex(position, vertexFactory.create(x, y, z, onCurve)); @@ -442,8 +474,6 @@ public final class OutlineShape implements Comparable<OutlineShape> { /** * Add a vertex to the last open outline to the shape's tail. * - * The constructed shape should be {@link Winding#CCW}. - * * The vertex is passed as a float array and its offset where its attributes are located. * The attributes should be continuous (stride = 0). * Attributes which value are not set (when length less than 3) @@ -451,8 +481,8 @@ public final class OutlineShape implements Comparable<OutlineShape> { * @param coordsBuffer the coordinate array where the vertex attributes are to be picked from * @param offset the offset in the buffer to the x coordinate * @param length the number of attributes to pick from the buffer (maximum 3) - * @param onCurve flag if this vertex is on the final curve or defines a curved region - * of the shape around this vertex. + * @param onCurve flag if this vertex is on the final curve or defines a curved region of the shape around this vertex. + * @see <a href="#windingrules">see winding rules</a> */ public final void addVertex(final float[] coordsBuffer, final int offset, final int length, final boolean onCurve) { addVertex(vertexFactory.create(coordsBuffer, offset, length, onCurve)); @@ -461,8 +491,6 @@ public final class OutlineShape implements Comparable<OutlineShape> { /** * Add a vertex to the last open outline to the shape at {@code position}. * - * The constructed shape should be {@link Winding#CCW}. - * * The vertex is passed as a float array and its offset where its attributes are located. * The attributes should be continuous (stride = 0). * Attributes which value are not set (when length less than 3) @@ -471,8 +499,8 @@ public final class OutlineShape implements Comparable<OutlineShape> { * @param coordsBuffer the coordinate array where the vertex attributes are to be picked from * @param offset the offset in the buffer to the x coordinate * @param length the number of attributes to pick from the buffer (maximum 3) - * @param onCurve flag if this vertex is on the final curve or defines a curved region - * of the shape around this vertex. + * @param onCurve flag if this vertex is on the final curve or defines a curved region of the shape around this vertex. + * @see <a href="#windingrules">see winding rules</a> */ public final void addVertex(final int position, final float[] coordsBuffer, final int offset, final int length, final boolean onCurve) { addVertex(position, vertexFactory.create(coordsBuffer, offset, length, onCurve)); @@ -647,72 +675,75 @@ public final class OutlineShape implements Comparable<OutlineShape> { /** * Start a new position for the next line segment at given point x/y (P1). * - * The constructed shape should be {@link Winding#CCW}. - * * @param x point (P1) * @param y point (P1) + * @param z point (P1) * @see Path2F#moveTo(float, float) * @see #addPath(com.jogamp.graph.geom.plane.Path2F.Iterator, boolean) + * @see <a href="#windingrules">see winding rules</a> */ - public final void moveTo(final float x, final float y) { + public final void moveTo(final float x, final float y, final float z) { if ( 0 == getLastOutline().getVertexCount() ) { - addVertex(x, y, true); + addVertex(x, y, z, true); } else { closeLastOutline(false); addEmptyOutline(); - addVertex(x, y, true); + addVertex(x, y, z, true); } } /** * Add a line segment, intersecting the last point and the given point x/y (P1). * - * The constructed shape should be {@link Winding#CCW}. - * * @param x final point (P1) * @param y final point (P1) + * @param z final point (P1) * @see Path2F#lineTo(float, float) * @see #addPath(com.jogamp.graph.geom.plane.Path2F.Iterator, boolean) + * @see <a href="#windingrules">see winding rules</a> */ - public final void lineTo(final float x, final float y) { - addVertex(x, y, true); + public final void lineTo(final float x, final float y, final float z) { + addVertex(x, y, z, true); } /** * Add a quadratic curve segment, intersecting the last point and the second given point x2/y2 (P2). * - * The constructed shape should be {@link Winding#CCW}. - * * @param x1 quadratic parametric control point (P1) * @param y1 quadratic parametric control point (P1) + * @param z1 quadratic parametric control point (P1) * @param x2 final interpolated control point (P2) * @param y2 final interpolated control point (P2) + * @param z2 quadratic parametric control point (P2) * @see Path2F#quadTo(float, float, float, float) * @see #addPath(com.jogamp.graph.geom.plane.Path2F.Iterator, boolean) + * @see <a href="#windingrules">see winding rules</a> */ - public final void quadTo(final float x1, final float y1, final float x2, final float y2) { - addVertex(x1, y1, false); - addVertex(x2, y2, true); + public final void quadTo(final float x1, final float y1, final float z1, final float x2, final float y2, final float z2) { + addVertex(x1, y1, z1, false); + addVertex(x2, y2, z2, true); } /** * Add a cubic Bézier curve segment, intersecting the last point and the second given point x3/y3 (P3). * - * The constructed shape should be {@link Winding#CCW}. - * * @param x1 Bézier control point (P1) * @param y1 Bézier control point (P1) + * @param z1 Bézier control point (P1) * @param x2 Bézier control point (P2) * @param y2 Bézier control point (P2) + * @param z2 Bézier control point (P2) * @param x3 final interpolated control point (P3) * @param y3 final interpolated control point (P3) + * @param z3 final interpolated control point (P3) * @see Path2F#cubicTo(float, float, float, float, float, float) * @see #addPath(com.jogamp.graph.geom.plane.Path2F.Iterator, boolean) + * @see <a href="#windingrules">see winding rules</a> */ - public final void cubicTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3) { - addVertex(x1, y1, false); - addVertex(x2, y2, false); - addVertex(x3, y3, true); + public final void cubicTo(final float x1, final float y1, final float z1, final float x2, final float y2, final float z2, final float x3, final float y3, final float z3) { + addVertex(x1, y1, z1, false); + addVertex(x2, y2, z2, false); + addVertex(x3, y3, z3, true); } /** diff --git a/src/jogl/classes/com/jogamp/graph/curve/opengl/RegionRenderer.java b/src/jogl/classes/com/jogamp/graph/curve/opengl/RegionRenderer.java index 0c782d93e..1014553d3 100644 --- a/src/jogl/classes/com/jogamp/graph/curve/opengl/RegionRenderer.java +++ b/src/jogl/classes/com/jogamp/graph/curve/opengl/RegionRenderer.java @@ -152,17 +152,25 @@ public class RegionRenderer { private final GLCallback enableCallback; private final GLCallback disableCallback; - private int vp_width; - private int vp_height; + private final int[] viewport = new int[] { 0, 0, 0, 0 }; private boolean initialized; private boolean vboSupported = false; public final boolean isInitialized() { return initialized; } + /** Copies the current viewport in given target and returns it for chaining. */ + public final int[/*4*/] getViewport(final int[/*4*/] target) { + System.arraycopy(viewport, 0, target, 0, 4); + return target; + } + /** Borrows the current viewport w/o copying. */ + public final int[/*4*/] getViewport() { + return viewport; + } /** Return width of current viewport */ - public final int getWidth() { return vp_width; } + public final int getWidth() { return viewport[2]; } /** Return height of current viewport */ - public final int getHeight() { return vp_height; } + public final int getHeight() { return viewport[3]; } public final PMVMatrix getMatrix() { return rs.getMatrix(); } @@ -261,15 +269,16 @@ public class RegionRenderer { } } - /** No PMVMatrix operation is performed here. PMVMatrix is marked dirty. */ + /** + * No PMVMatrix operation is performed here. + */ public final void reshapeNotify(final int width, final int height) { - this.vp_width = width; - this.vp_height = height; + viewport[2] = width; + viewport[3] = height; } public final void reshapePerspective(final float angle, final int width, final int height, final float near, final float far) { - this.vp_width = width; - this.vp_height = height; + reshapeNotify(width, height); final float ratio = (float)width/(float)height; final PMVMatrix p = rs.getMatrix(); p.glMatrixMode(GLMatrixFunc.GL_PROJECTION); @@ -277,9 +286,11 @@ public class RegionRenderer { p.gluPerspective(angle, ratio, near, far); } + /** + * Perspective orthogonal, method calls {@link #reshapeNotify(int, int, int, int)}. + */ public final void reshapeOrtho(final int width, final int height, final float near, final float far) { - this.vp_width = width; - this.vp_height = height; + reshapeNotify(width, height); final PMVMatrix p = rs.getMatrix(); p.glMatrixMode(GLMatrixFunc.GL_PROJECTION); p.glLoadIdentity(); diff --git a/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java b/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java index 4af40bf1c..fb77775ad 100644 --- a/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java +++ b/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java @@ -1,5 +1,5 @@ /** - * Copyright 2014 JogAmp Community. All rights reserved. + * Copyright 2014-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: @@ -33,12 +33,11 @@ import java.util.Iterator; import com.jogamp.opengl.GL2ES2; import com.jogamp.opengl.GLException; +import com.jogamp.opengl.math.geom.AABBox; import com.jogamp.graph.curve.OutlineShape; import com.jogamp.graph.curve.Region; import com.jogamp.graph.font.Font; import com.jogamp.graph.font.Font.Glyph; -import com.jogamp.graph.geom.Vertex; -import com.jogamp.graph.geom.Vertex.Factory; import com.jogamp.graph.geom.plane.AffineTransform; /** @@ -55,18 +54,6 @@ public class TextRegionUtil { this.renderModes = renderModes; } - public static interface ShapeVisitor { - /** - * Visiting the given {@link OutlineShape} with it's corresponding {@link AffineTransform}. - * <p> - * The shape is in font em-size [0..1]. - * </p> - * @param shape may be used as is, otherwise a copy shall be made if intended to be modified. - * @param t may be used immediately as is, otherwise a copy shall be made if stored. - */ - public void visit(final OutlineShape shape, final AffineTransform t); - } - public static int getCharCount(final String s, final char c) { final int sz = s.length(); int count = 0; @@ -79,85 +66,53 @@ public class TextRegionUtil { } /** - * Visit each {@link Font.Glyph}'s {@link OutlineShape} with the given {@link ShapeVisitor} - * additionally passing the progressed {@link AffineTransform}. + * Add the string in 3D space w.r.t. the font in font em-size [0..1] at the end of the {@link GLRegion} + * while passing the progressed {@link AffineTransform}. * <p> - * The produced shapes are in font em-size [0..1], but can be adjusted with the given transform, progressed and passed to the visitor. + * The shapes added to the GLRegion are in font em-size [0..1], but can be adjusted with the given transform, progressed and passed to the visitor. * </p> - * @param visitor - * @param transform optional given transform + * <p> + * Origin of rendered text is 0/0 at bottom left. + * </p> + * @param region the {@link GLRegion} sink * @param font the target {@link Font} + * @param transform optional given transform * @param str string text - * @param temp1 temporary AffineTransform storage, mandatory - * @param temp2 temporary AffineTransform storage, mandatory + * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. + * @return the bounding box of the given string by taking each glyph's font em-sized [0..1] OutlineShape into account. */ - public static void processString(final ShapeVisitor visitor, final AffineTransform transform, - final Font font, final CharSequence str, - final AffineTransform temp1, final AffineTransform temp2) { - final int charCount = str.length(); - - // region.setFlipped(true); - final float lineHeight = font.getLineHeight(); - - float y = 0; - float advanceTotal = 0; - Font.Glyph left_glyph = null; - - for(int i=0; i< charCount; i++) { - final char character = str.charAt(i); - if( '\n' == character ) { - y -= lineHeight; - advanceTotal = 0; - left_glyph = null; - } else if (character == ' ') { - advanceTotal += font.getAdvanceWidth(Glyph.ID_SPACE); - left_glyph = null; - } else { - // reset transform - if( null != transform ) { - temp1.setTransform(transform); - } else { - temp1.setToIdentity(); - } - final Font.Glyph glyph = font.getGlyph(character); - final OutlineShape glyphShape = glyph.getShape(); - if( null == glyphShape ) { - left_glyph = null; - continue; - } - if( null != left_glyph ) { - advanceTotal += left_glyph.getKerning(glyph.getID()); - } - temp1.translate(advanceTotal, y, temp2); - visitor.visit(glyphShape, temp1); - advanceTotal += glyph.getAdvance(); - left_glyph = glyph; - } - } + public static AABBox addStringToRegion(final Region region, final Font font, final AffineTransform transform, + final CharSequence str, final float[] rgbaColor) { + return addStringToRegion(region, font, transform, str, rgbaColor, new AffineTransform(), new AffineTransform()); } /** - * Add the string in 3D space w.r.t. the font in font em-size [0..1] at the end of the {@link GLRegion}. + * Add the string in 3D space w.r.t. the font in font em-size [0..1] at the end of the {@link GLRegion} + * while passing the progressed {@link AffineTransform}. * <p> - * The shapes added to the GLRegion are in font em-size [0..1]. + * The shapes added to the GLRegion are in font em-size [0..1], but can be adjusted with the given transform, progressed and passed to the visitor. + * </p> + * <p> + * Origin of rendered text is 0/0 at bottom left. * </p> * @param region the {@link GLRegion} sink - * @param vertexFactory vertex impl factory {@link Factory} * @param font the target {@link Font} + * @param transform optional given transform * @param str string text * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param temp1 temporary AffineTransform storage, mandatory * @param temp2 temporary AffineTransform storage, mandatory + * @return the bounding box of the given string by taking each glyph's font em-sized [0..1] OutlineShape into account. */ - public static void addStringToRegion(final GLRegion region, final Factory<? extends Vertex> vertexFactory, - final Font font, final CharSequence str, final float[] rgbaColor, - final AffineTransform temp1, final AffineTransform temp2) { - final ShapeVisitor visitor = new ShapeVisitor() { + public static AABBox addStringToRegion(final Region region, final Font font, final AffineTransform transform, + final CharSequence str, final float[] rgbaColor, + final AffineTransform temp1, final AffineTransform temp2) { + final OutlineShape.Visitor visitor = new OutlineShape.Visitor() { @Override public final void visit(final OutlineShape shape, final AffineTransform t) { region.addOutlineShape(shape, t, region.hasColorChannel() ? rgbaColor : null); } }; - processString(visitor, null, font, str, temp1, temp2); + return font.processString(visitor, transform, str, temp1, temp2); } /** @@ -166,6 +121,9 @@ public class TextRegionUtil { * The shapes added to the GLRegion are in font em-size [0..1]. * </p> * <p> + * Origin of rendered text is 0/0 at bottom left. + * </p> + * <p> * Cached {@link GLRegion}s will be destroyed w/ {@link #clear(GL2ES2)} or to free memory. * </p> * @param gl the current GL state @@ -175,22 +133,28 @@ public class TextRegionUtil { * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param sampleCount desired multisampling sample count for msaa-rendering. * The actual used scample-count is written back when msaa-rendering is enabled, otherwise the store is untouched. + * @return the bounding box of the given string from the produced and rendered GLRegion * @throws Exception if TextRenderer not initialized */ - public void drawString3D(final GL2ES2 gl, - final RegionRenderer renderer, final Font font, final CharSequence str, - final float[] rgbaColor, final int[/*1*/] sampleCount) { + public AABBox drawString3D(final GL2ES2 gl, + final RegionRenderer renderer, final Font font, final CharSequence str, + final float[] rgbaColor, final int[/*1*/] sampleCount) { if( !renderer.isInitialized() ) { throw new GLException("TextRendererImpl01: not initialized!"); } final int special = 0; GLRegion region = getCachedRegion(font, str, special); + AABBox res; if(null == region) { region = GLRegion.create(renderModes, null); - addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, str, rgbaColor, tempT1, tempT2); + res = addStringToRegion(region, font, null, str, rgbaColor, tempT1, tempT2); addCachedRegion(gl, font, str, special, region); + } else { + res = new AABBox(); + res.copy(region.getBounds()); } region.draw(gl, renderer, sampleCount); + return res; } /** @@ -199,9 +163,12 @@ public class TextRegionUtil { * The shapes added to the GLRegion are in font em-size [0..1]. * </p> * <p> + * Origin of rendered text is 0/0 at bottom left. + * </p> + * <p> * In case of a multisampling region renderer, i.e. {@link Region#VBAA_RENDERING_BIT}, recreating the {@link GLRegion} * is a huge performance impact. - * In such case better use {@link #drawString3D(GL2ES2, GLRegion, RegionRenderer, Font, CharSequence, float[], int[], AffineTransform, AffineTransform)} + * In such case better use {@link #drawString3D(GL2ES2, GLRegion, RegionRenderer, Font, CharSequence, float[], int[])} * instead. * </p> * @param gl the current GL state @@ -211,21 +178,20 @@ public class TextRegionUtil { * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param sampleCount desired multisampling sample count for msaa-rendering. * The actual used scample-count is written back when msaa-rendering is enabled, otherwise the store is untouched. - * @param temp1 temporary AffineTransform storage, mandatory - * @param temp2 temporary AffineTransform storage, mandatory * @throws Exception if TextRenderer not initialized + * @return the bounding box of the given string from the produced and rendered GLRegion */ - public static void drawString3D(final GL2ES2 gl, final int renderModes, - final RegionRenderer renderer, final Font font, final CharSequence str, - final float[] rgbaColor, final int[/*1*/] sampleCount, final AffineTransform temp1, - final AffineTransform temp2) { + public static AABBox drawString3D(final GL2ES2 gl, final int renderModes, + final RegionRenderer renderer, final Font font, final CharSequence str, + final float[] rgbaColor, final int[/*1*/] sampleCount) { if(!renderer.isInitialized()){ throw new GLException("TextRendererImpl01: not initialized!"); } final GLRegion region = GLRegion.create(renderModes, null); - addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, str, rgbaColor, temp1, temp2); + final AABBox res = addStringToRegion(region, font, null, str, rgbaColor); region.draw(gl, renderer, sampleCount); region.destroy(gl); + return res; } /** @@ -234,26 +200,28 @@ public class TextRegionUtil { * <p> * The shapes added to the GLRegion are in font em-size [0..1]. * </p> + * <p> + * Origin of rendered text is 0/0 at bottom left. + * </p> * @param gl the current GL state * @param font {@link Font} to be used * @param str text to be rendered * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param sampleCount desired multisampling sample count for msaa-rendering. * The actual used scample-count is written back when msaa-rendering is enabled, otherwise the store is untouched. - * @param temp1 temporary AffineTransform storage, mandatory - * @param temp2 temporary AffineTransform storage, mandatory + * @return the bounding box of the given string from the produced and rendered GLRegion * @throws Exception if TextRenderer not initialized */ - public static void drawString3D(final GL2ES2 gl, final GLRegion region, final RegionRenderer renderer, - final Font font, final CharSequence str, final float[] rgbaColor, - final int[/*1*/] sampleCount, final AffineTransform temp1, - final AffineTransform temp2) { + public static AABBox drawString3D(final GL2ES2 gl, final GLRegion region, final RegionRenderer renderer, + final Font font, final CharSequence str, final float[] rgbaColor, + final int[/*1*/] sampleCount) { if(!renderer.isInitialized()){ throw new GLException("TextRendererImpl01: not initialized!"); } region.clear(gl); - addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, str, rgbaColor, temp1, temp2); + final AABBox res = addStringToRegion(region, font, null, str, rgbaColor); region.draw(gl, renderer, sampleCount); + return res; } /** diff --git a/src/jogl/classes/com/jogamp/graph/font/Font.java b/src/jogl/classes/com/jogamp/graph/font/Font.java index 2d26b1a85..fdef6a612 100644 --- a/src/jogl/classes/com/jogamp/graph/font/Font.java +++ b/src/jogl/classes/com/jogamp/graph/font/Font.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: @@ -81,42 +81,42 @@ public interface Font { */ public interface Metrics { /** - * @return ascent in font-units to be divided by {@link #getUnitsPerEM()} + * @return ascent in font-units, sourced from `hheaTable' table. */ int getAscentFU(); /** - * @return ascent in font em-size [0..1] + * @return ascent in font em-size [0..1], sourced from `hheaTable' table. */ float getAscent(); /** - * @return descent in font-units to be divided by {@link #getUnitsPerEM()} + * @return descent in font-units, sourced from `hheaTable' table. */ int getDescentFU(); /** - * @return descend in font em-size [0..1] + * @return descend in font em-size [0..1], sourced from `hheaTable' table. */ float getDescent(); /** - * @return line-gap in font-units to be divided by {@link #getUnitsPerEM()} + * @return line-gap in font-units, sourced from `hheaTable' table. */ int getLineGapFU(); /** - * @return line-gap in font em-size [0..1] + * @return line-gap in font em-size [0..1], sourced from `hheaTable' table. */ float getLineGap(); /** - * @return max-extend in font-units to be divided by {@link #getUnitsPerEM()} + * @return max-extend in font-units, sourced from `hheaTable' table. */ int getMaxExtendFU(); /** - * @return max-extend in font em-size [0..1] + * @return max-extend in font em-size [0..1], sourced from `hheaTable' table. */ float getMaxExtend(); @@ -161,7 +161,6 @@ public interface Font { public static final int ID_SPACE = 3; Font getFont(); - char getSymbol(); /** Return this glyph's ID */ int getID(); @@ -177,25 +176,31 @@ public interface Font { float getScale(final int funits); /** - * Return the AABBox in font-units to be divided by unitsPerEM + * Return the AABBox in font-units, borrowing internal instance. */ AABBox getBBoxFU(); /** - * Return the AABBox in font-units to be divided by unitsPerEM + * Return the AABBox in font-units, copying into given dest. * @param dest AABBox instance set to this metrics boundary in font-units * @return the given and set AABBox 'dest' in font-units */ AABBox getBBoxFU(final AABBox dest); /** + * Return the AABBox in font em-size [0..1], copying into given dest. * @param dest AABBox instance set to this metrics boundary in font em-size [0..1] * @param tmpV3 caller provided temporary 3-component vector * @return the given and set AABBox 'dest' in font em-size [0..1] */ AABBox getBBox(final AABBox dest, float[] tmpV3); - /** Return advance in font units to be divided by unitsPerEM */ + /** + * Return the AABBox in font em-size [0..1], creating a new copy. + */ + AABBox getBBox(); + + /** Return advance in font units, sourced from `hmtx` table. */ int getAdvanceFU(); /** Return advance in font em-size [0..1] */ @@ -210,10 +215,10 @@ public interface Font { int getKerningPairCount(); /** - * Returns the optional kerning inter-glyph distance within words between this glyph and the given right glyph_id in font-units to be divided by unitsPerEM + * Returns the optional kerning inter-glyph distance within words between this glyph and the given right glyph_id in font-units. * * @param right_glyphid right glyph code id - * @return font-units to be divided by unitsPerEM + * @return font-units */ int getKerningFU(final int right_glyphid); @@ -247,7 +252,7 @@ public interface Font { StringBuilder getAllNames(final StringBuilder string, final String separator); /** - * Return advance-width of given glyphID in font-units to be divided by unitsPerEM + * Return advance-width of given glyphID in font-units, sourced from `hmtx` table. * @param glyphID */ int getAdvanceWidthFU(final int glyphID); @@ -262,12 +267,22 @@ public interface Font { int getGlyphID(final char symbol); - Glyph getGlyph(final char symbol); + Glyph getGlyph(final int glyph_id); int getNumGlyphs(); /** - * Return line height in font-units to be divided by unitsPerEM + * Return line height in font-units, composed from `hheaTable' table entries. + * <pre> + * return abs(lineGap) + abs(descent) abs(ascent); + * </pre> + * or + * <pre> + * // lineGap negative value + * // descent positive value + * // ascent negative value + * return -1 * ( lineGap - descent + ascent ); + * </pre> */ int getLineHeightFU(); @@ -276,43 +291,126 @@ public interface Font { */ float getLineHeight(); - /** Return metric-width in font-units */ - int getMetricWidthFU(final CharSequence string); - - /** Return metric-width in font em-size */ - float getMetricWidth(final CharSequence string); - - /** Return metric-height in font-units */ - int getMetricHeightFU(final CharSequence string); - - /** Return metric-height in font em-size */ - float getMetricHeight(final CharSequence string); - - /** Return layout metric-bounds in font-units, see {@link #getMetricBounds(CharSequence, float)} */ + /** + * Returns metric-bounds in font-units. + * <p> + * Metric bounds is based on the `hmtx` table's advance of each glyph and `hheaTable' composed line height. + * </p> + * <p> + * For accurate layout consider using {@link #getGlyphBoundsFU(CharSequence)}. + * </p> + * @see #getMetricBounds(CharSequence) + * @see #getGlyphBoundsFU(CharSequence) + */ AABBox getMetricBoundsFU(final CharSequence string); - /** Return layout metric-bounds in font em-size, see {@link #getMetricBounds(CharSequence, float)} */ + /** + * Returns metric-bounds in font em-size. + * <p> + * Metric bounds is based on the `hmtx` table's advance of each glyph and `hheaTable' composed line height. + * </p> + * <p> + * For accurate layout consider using {@link #getGlyphBounds(CharSequence)}. + * </p> + * @see #getMetricBoundsFU(CharSequence) + * @see #getGlyphBounds(CharSequence) + * @see #getGlyphShapeBounds(CharSequence) + */ AABBox getMetricBounds(final CharSequence string); /** - * Return the bounding box by taking each glyph's font-unit sized bounding box into account. - * @param transform optional given transform + * Returns accurate bounding box by taking each glyph's font em-sized bounding box into account. + * <p> + * Glyph bounds is based on each glyph's bounding box and `hheaTable' composed line height. + * </p> + * @param string string text + * @return the bounding box of the given string in font em-size [0..1] + * @see #getGlyphBoundsFU(CharSequence) + * @see #getGlyphShapeBounds(CharSequence) + * @see #getMetricBounds(CharSequence) + */ + AABBox getGlyphBounds(final CharSequence string); + + /** + * Returns accurate bounding box by taking each glyph's font-units sized bounding box into account. + * <p> + * Glyph bounds is based on each glyph's bounding box and `hheaTable' composed line height. + * </p> * @param string string text * @return the bounding box of the given string in font-units [0..1] + * @see #getGlyphBounds(CharSequence) */ - AABBox getPointsBoundsFU(final AffineTransform transform, final CharSequence string); + AABBox getGlyphBoundsFU(final CharSequence string); /** - * Return the bounding box by taking each glyph's font em-sized bounding box into account. + * Returns accurate bounding box by taking each glyph's font em-sized {@link OutlineShape} into account. + * <p> + * Glyph shape bounds is based on each glyph's {@link OutlineShape} and `hheaTable' composed line height. + * </p> + * <p> + * This method is only exposed to validate the produced {@link OutlineShape} against {@link #getGlyphBounds(CharSequence)}. + * </p> * @param transform optional given transform * @param string string text - * @return the bounding box of the given string in font em-size [0..1] + * @return the bounding box of the given string in font-units [0..1] + * @see #getGlyphShapeBounds(CharSequence) + * @see #getGlyphBounds(CharSequence) + * @see #getMetricBounds(CharSequence) */ - AABBox getPointsBounds(final AffineTransform transform, final CharSequence string); + AABBox getGlyphShapeBounds(final AffineTransform transform, final CharSequence string); + + /** + * Returns accurate bounding box by taking each glyph's font em-sized {@link OutlineShape} into account. + * <p> + * Glyph shape bounds is based on each glyph's {@link OutlineShape} and `hheaTable' composed line height. + * </p> + * <p> + * This method is only exposed to validate the produced {@link OutlineShape} against {@link #getGlyphBounds(CharSequence)}. + * </p> + * @param string string text + * @return the bounding box of the given string in font-units [0..1] + * @see #getGlyphShapeBounds(AffineTransform, CharSequence) + * @see #getGlyphBounds(CharSequence) + * @see #getMetricBounds(CharSequence) + */ + AABBox getGlyphShapeBounds(final CharSequence string); boolean isPrintableChar(final char c); - /** Shall return {@link #getFullFamilyName()} */ + /** + * Visit each {@link Glyph}'s {@link OutlineShape} of the string with the {@link OutlineShape.Visitor} + * while passing the progressed {@link AffineTransform}. + * <p> + * The produced shapes are in font em-size [0..1], but can be adjusted with the given transform, progressed and passed to the visitor. + * </p> + * @param visitor handling each glyph's outline shape in font em-size [0..1] and the given {@link AffineTransform} + * @param transform optional given transform + * @param font the target {@link Font} + * @param string string text + * @return the bounding box of the given string by taking each glyph's font em-sized [0..1] {@link OutlineShape} into account. + */ + AABBox processString(final OutlineShape.Visitor visitor, final AffineTransform transform, + final CharSequence string); + + /** + * Visit each {@link Glyph}'s {@link OutlineShape} of the string with the {@link OutlineShape.Visitor} + * while passing the progressed {@link AffineTransform}. + * <p> + * The produced shapes are in font em-size [0..1], but can be adjusted with the given transform, progressed and passed to the visitor. + * </p> + * @param visitor handling each glyph's outline shape in font em-size [0..1] and the given {@link AffineTransform} + * @param transform optional given transform + * @param font the target {@link Font} + * @param string string text + * @param temp1 temporary AffineTransform storage, mandatory + * @param temp2 temporary AffineTransform storage, mandatory + * @return the bounding box of the given string by taking each glyph's font em-sized [0..1] {@link OutlineShape} into account. + */ + AABBox processString(final OutlineShape.Visitor visitor, final AffineTransform transform, + final CharSequence string, + final AffineTransform temp1, final AffineTransform temp2); + + /** Returns {@link #getFullFamilyName()} */ @Override public String toString(); diff --git a/src/jogl/classes/com/jogamp/graph/font/FontFactory.java b/src/jogl/classes/com/jogamp/graph/font/FontFactory.java index 5c317bd2b..ac42274f6 100644 --- a/src/jogl/classes/com/jogamp/graph/font/FontFactory.java +++ b/src/jogl/classes/com/jogamp/graph/font/FontFactory.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 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: diff --git a/src/jogl/classes/com/jogamp/graph/font/FontSet.java b/src/jogl/classes/com/jogamp/graph/font/FontSet.java index 60a16b241..607aab433 100644 --- a/src/jogl/classes/com/jogamp/graph/font/FontSet.java +++ b/src/jogl/classes/com/jogamp/graph/font/FontSet.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 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: diff --git a/src/jogl/classes/com/jogamp/graph/geom/Outline.java b/src/jogl/classes/com/jogamp/graph/geom/Outline.java index b18d51849..7c9cb69c9 100644 --- a/src/jogl/classes/com/jogamp/graph/geom/Outline.java +++ b/src/jogl/classes/com/jogamp/graph/geom/Outline.java @@ -30,6 +30,7 @@ package com.jogamp.graph.geom; import java.util.ArrayList; import com.jogamp.graph.geom.plane.AffineTransform; +import com.jogamp.graph.geom.plane.Winding; import com.jogamp.graph.curve.OutlineShape; import com.jogamp.graph.curve.Region; import com.jogamp.opengl.math.FloatUtil; @@ -54,6 +55,8 @@ public class Outline implements Comparable<Outline> { private boolean closed; private final AABBox bbox; private boolean dirtyBBox; + private Winding winding; + private boolean dirtyWinding; /**Create an outline defined by control vertices. * An outline can contain off Curve vertices which define curved @@ -64,14 +67,19 @@ public class Outline implements Comparable<Outline> { closed = false; bbox = new AABBox(); dirtyBBox = false; + winding = Winding.CCW; + dirtyWinding = false; } /** * Copy ctor */ public Outline(final Outline src) { - vertices = new ArrayList<Vertex>(src.vertices.size()); - for(int i=0; i<vertices.size(); i++) { + final int count = src.vertices.size(); + vertices = new ArrayList<Vertex>(count); + winding = src.getWinding(); + dirtyWinding = false; + for(int i=0; i<count; i++) { vertices.add( src.vertices.get(i).clone() ); } closed = src.closed; @@ -79,6 +87,74 @@ public class Outline implements Comparable<Outline> { dirtyBBox = src.dirtyBBox; } + /** + * Copy ctor w/ enforced Winding + * <p> + * If the enforced {@link Winding} doesn't match the source Outline, the vertices reversed copied into this new instance. + * </p> + * @param src the source Outline + * @param enforce {@link Winding} to be enforced on this copy + */ + public Outline(final Outline src, final Winding enforce) { + final int count = src.vertices.size(); + vertices = new ArrayList<Vertex>(count); + final Winding had_winding = src.getWinding();; + winding = had_winding; + dirtyWinding = false; + if( enforce != had_winding ) { + for(int i=count-1; i>=0; --i) { + vertices.add( src.vertices.get(i).clone() ); + } + winding = enforce; + } else { + for(int i=0; i<count; ++i) { + vertices.add( src.vertices.get(i).clone() ); + } + } + closed = src.closed; + bbox = new AABBox(src.bbox); + dirtyBBox = src.dirtyBBox; + } + + /** + * Sets {@link Winding} to this outline + * <p> + * If the enforced {@link Winding} doesn't match this Outline, the vertices are reversed. + * </p> + * @param enforce to be enforced {@link Winding} + */ + public final void setWinding(final Winding enforce) { + final Winding had_winding = getWinding(); + if( enforce != had_winding ) { + final int count = vertices.size(); + final ArrayList<Vertex> ccw = new ArrayList<Vertex>(count); + for(int i=count-1; i>=0; --i) { + ccw.add(vertices.get(i)); + } + vertices = ccw; + winding = enforce; + } + } + + /** + * Compute the winding of the {@link #getLastOutline()} using the {@link #area(ArrayList)} function over all of its vertices. + * @return {@link Winding#CCW} or {@link Winding#CW} + */ + public final Winding getWinding() { + if( !dirtyWinding ) { + return winding; + } + final int count = getVertexCount(); + if( 3 > count ) { + winding = Winding.CCW; + } else { + final ArrayList<Vertex> vertices = getVertices(); + winding = VectorUtil.getWinding(vertices); + } + dirtyWinding = false; + return winding; + } + public final int getVertexCount() { return vertices.size(); } @@ -107,6 +183,7 @@ public class Outline implements Comparable<Outline> { if(!dirtyBBox) { bbox.resize(vertex.getCoord()); } + dirtyWinding = true; } /** Replaces the {@link Vertex} element at the given {@code position}. @@ -123,6 +200,7 @@ public class Outline implements Comparable<Outline> { } vertices.set(position, vertex); dirtyBBox = true; + dirtyWinding = true; } public final Vertex getVertex(final int index){ @@ -141,6 +219,7 @@ public class Outline implements Comparable<Outline> { */ public final Vertex removeVertex(final int position) throws IndexOutOfBoundsException { dirtyBBox = true; + dirtyWinding = true; return vertices.remove(position); } diff --git a/src/jogl/classes/com/jogamp/opengl/math/VectorUtil.java b/src/jogl/classes/com/jogamp/opengl/math/VectorUtil.java index 3ee504a29..d7e72e245 100644 --- a/src/jogl/classes/com/jogamp/opengl/math/VectorUtil.java +++ b/src/jogl/classes/com/jogamp/opengl/math/VectorUtil.java @@ -888,25 +888,35 @@ public final class VectorUtil { return triAreaVec2(a,b,c) > 0; } - /** Compute the winding of given points + /** + * Compute the winding of the 3 given points + * <p> + * Consider using {@link #getWinding(ArrayList)} using the {@link #area(ArrayList)} function over all points + * on complex shapes for a reliable result! + * </p> * @param a first vertex * @param b second vertex * @param c third vertex - * @return Winding + * @return {@link Winding#CCW} or {@link Winding#CW} + * @see #getWinding(ArrayList) */ public static Winding getWinding(final Vert2fImmutable a, final Vert2fImmutable b, final Vert2fImmutable c) { return triAreaVec2(a,b,c) > 0 ? Winding.CCW : Winding.CW ; } - /** Computes the area of a list of vertices to check if ccw + /** + * Computes the area of a list of vertices. + * <p> + * This method is utilized e.g. to reliably compute the {@link Winding} of complex shapes. + * </p> * @param vertices * @return positive area if ccw else negative area value + * @see #getWinding(ArrayList) */ public static float area(final ArrayList<? extends Vert2fImmutable> vertices) { final int n = vertices.size(); float area = 0.0f; - for (int p = n - 1, q = 0; q < n; p = q++) - { + for (int p = n - 1, q = 0; q < n; p = q++) { final float[] pCoord = vertices.get(p).getCoord(); final float[] qCoord = vertices.get(q).getCoord(); area += pCoord[0] * qCoord[1] - qCoord[0] * pCoord[1]; @@ -914,9 +924,15 @@ public final class VectorUtil { return area; } - /** Compute the general winding of the vertices + /** + * Compute the winding using the {@link #area(ArrayList)} function over all vertices for complex shapes. + * <p> + * Uses the {@link #area(ArrayList)} function over all points + * on complex shapes for a reliable result! + * </p> * @param vertices array of Vertices - * @return CCW or CW {@link Winding} + * @return {@link Winding#CCW} or {@link Winding#CW} + * @see #area(ArrayList) */ public static Winding getWinding(final ArrayList<? extends Vert2fImmutable> vertices) { return area(vertices) >= 0 ? Winding.CCW : Winding.CW ; diff --git a/src/jogl/classes/jogamp/graph/curve/tess/CDTriangulator2D.java b/src/jogl/classes/jogamp/graph/curve/tess/CDTriangulator2D.java index 369d0b493..331116f7e 100644 --- a/src/jogl/classes/jogamp/graph/curve/tess/CDTriangulator2D.java +++ b/src/jogl/classes/jogamp/graph/curve/tess/CDTriangulator2D.java @@ -80,20 +80,20 @@ public class CDTriangulator2D implements Triangulator { @Override public final void addCurve(final List<Triangle> sink, final Outline polyline, final float sharpness) { - Loop loop = null; - - if(!loops.isEmpty()) { - loop = getContainerLoop(polyline); - } + Loop loop = getContainerLoop(polyline); if(loop == null) { - final GraphOutline outline = new GraphOutline(polyline); + final Winding winding = Winding.CCW; // -> HEdge.BOUNDARY + // Too late: polyline.setWinding(winding); + final GraphOutline outline = new GraphOutline(polyline); // , winding); final GraphOutline innerPoly = extractBoundaryTriangles(sink, outline, false, sharpness); // vertices.addAll(polyline.getVertices()); - loop = new Loop(innerPoly, Winding.CCW); + loop = new Loop(innerPoly, winding); loops.add(loop); } else { - final GraphOutline outline = new GraphOutline(polyline); + // final Winding winding = Winding.CW; // -> HEdge.HOLE + // Not required, handled in Loop.initFromPolyline(): polyline.setWinding(winding); + final GraphOutline outline = new GraphOutline(polyline); // , winding); final GraphOutline innerPoly = extractBoundaryTriangles(sink, outline, true, sharpness); // vertices.addAll(innerPoly.getVertices()); loop.addConstraintCurve(innerPoly); @@ -221,12 +221,15 @@ public class CDTriangulator2D implements Triangulator { } private Loop getContainerLoop(final Outline polyline) { - final ArrayList<Vertex> vertices = polyline.getVertices(); - for(int i=0; i < loops.size(); i++) { - final Loop loop = loops.get(i); - for(int j=0; j < vertices.size(); j++) { - if( loop.checkInside( vertices.get(j) ) ) { - return loop; + final int count = loops.size(); + if( 0 < count ) { + final ArrayList<Vertex> vertices = polyline.getVertices(); + for(int i=0; i < count; i++) { + final Loop loop = loops.get(i); + for(int j=0; j < vertices.size(); j++) { + if( loop.checkInside( vertices.get(j) ) ) { + return loop; + } } } } diff --git a/src/jogl/classes/jogamp/graph/curve/tess/GraphOutline.java b/src/jogl/classes/jogamp/graph/curve/tess/GraphOutline.java index 81e6efdad..75192d45a 100644 --- a/src/jogl/classes/jogamp/graph/curve/tess/GraphOutline.java +++ b/src/jogl/classes/jogamp/graph/curve/tess/GraphOutline.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import com.jogamp.graph.geom.Outline; import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.plane.Winding; public class GraphOutline { final private Outline outline; @@ -40,11 +41,14 @@ public class GraphOutline { this.outline = new Outline(); } - /**Create a control polyline of control vertices + /** + * Create a control polyline of control vertices * the curve pieces can be identified by onCurve flag * of each cp the control polyline is open by default + * + * @param ol the source {@link Outline} */ - public GraphOutline(final Outline ol){ + public GraphOutline(final Outline ol) { this.outline = ol; final ArrayList<Vertex> vertices = this.outline.getVertices(); for(int i = 0; i< vertices.size(); i++){ diff --git a/src/jogl/classes/jogamp/graph/curve/tess/Loop.java b/src/jogl/classes/jogamp/graph/curve/tess/Loop.java index 5d1bc051f..1d8264607 100644 --- a/src/jogl/classes/jogamp/graph/curve/tess/Loop.java +++ b/src/jogl/classes/jogamp/graph/curve/tess/Loop.java @@ -34,6 +34,7 @@ import com.jogamp.graph.geom.Vertex; import com.jogamp.graph.geom.plane.Winding; import com.jogamp.graph.geom.Triangle; import com.jogamp.opengl.math.VectorUtil; +import com.jogamp.opengl.math.Vert2fImmutable; import com.jogamp.opengl.math.geom.AABBox; public class Loop { @@ -91,9 +92,24 @@ public class Loop { return (root.getNext().getNext().getNext() == root); } - /**Create a connected list of half edges (loop) + private static float area(final ArrayList<GraphVertex> vertices) { + final int n = vertices.size(); + float area = 0.0f; + for (int p = n - 1, q = 0; q < n; p = q++) { + final float[] pCoord = vertices.get(p).getCoord(); + final float[] qCoord = vertices.get(q).getCoord(); + area += pCoord[0] * qCoord[1] - qCoord[0] * pCoord[1]; + } + return area; + } + private static Winding getWinding(final ArrayList<GraphVertex> vertices) { + return area(vertices) >= 0 ? Winding.CCW : Winding.CW ; + } + + /** + * Create a connected list of half edges (loop) * from the boundary profile - * @param reqWinding requested winding of edges (CCW or CW) + * @param reqWinding requested winding of edges, either {@link Winding#CCW} for {@link HEdge#BOUNDARY} or {@link Winding#CW} for {@link HEdge#HOLE} */ private HEdge initFromPolyline(final GraphOutline outline, final Winding reqWinding){ final ArrayList<GraphVertex> vertices = outline.getGraphPoint(); @@ -101,57 +117,64 @@ public class Loop { if(vertices.size()<3) { throw new IllegalArgumentException("outline's vertices < 3: " + vertices.size()); } - final Winding hasWinding = VectorUtil.getWinding( - vertices.get(0).getPoint(), - vertices.get(1).getPoint(), - vertices.get(2).getPoint()); - //FIXME: handle case when vertices come inverted - Rami - // skips inversion CW -> CCW - final boolean invert = hasWinding != reqWinding && - reqWinding == Winding.CW; - - final int max; + final Winding hasWinding = getWinding( vertices ); // requires area-winding detection + final int edgeType = reqWinding == Winding.CCW ? HEdge.BOUNDARY : HEdge.HOLE ; - int index; HEdge firstEdge = null; HEdge lastEdge = null; - if(!invert) { - max = vertices.size(); - index = 0; - } else { - max = -1; - index = vertices.size() -1; - } - - while(index != max){ - final GraphVertex v1 = vertices.get(index); - box.resize(v1.getX(), v1.getY(), v1.getZ()); - - final HEdge edge = new HEdge(v1, edgeType); - - v1.addEdge(edge); - if(lastEdge != null) { - lastEdge.setNext(edge); - edge.setPrev(lastEdge); - } else { - firstEdge = edge; - } - - if(!invert) { - if(index == vertices.size()-1) { + /** + * The winding conversion CW -> CCW can't be resolved here (-> Rami?) + * Therefore we require outline boundaries to be in CCW, see API-doc comment in OutlineShape. + * + * Original comment: + * FIXME: handle case when vertices come inverted - Rami + * Skips inversion CW -> CCW + */ + if( hasWinding == reqWinding || reqWinding == Winding.CCW ) { + // Correct Winding or skipped CW -> CCW (no inversion possible here, too late ??) + final int max = vertices.size() - 1; + for(int index = 0; index <= max; ++index) { + final GraphVertex v1 = vertices.get(index); + box.resize(v1.getX(), v1.getY(), v1.getZ()); + + final HEdge edge = new HEdge(v1, edgeType); + + v1.addEdge(edge); + if(lastEdge != null) { + lastEdge.setNext(edge); + edge.setPrev(lastEdge); + } else { + firstEdge = edge; + } + if(index == max ) { edge.setNext(firstEdge); firstEdge.setPrev(edge); } - index++; - } else { + lastEdge = edge; + } + } else { // if( reqWinding == Winding.CW ) { + // CCW -> CW + for(int index = vertices.size() - 1; index >= 0; --index) { + final GraphVertex v1 = vertices.get(index); + box.resize(v1.getX(), v1.getY(), v1.getZ()); + + final HEdge edge = new HEdge(v1, edgeType); + + v1.addEdge(edge); + if(lastEdge != null) { + lastEdge.setNext(edge); + edge.setPrev(lastEdge); + } else { + firstEdge = edge; + } + if (index == 0) { edge.setNext(firstEdge); firstEdge.setPrev(edge); } - index--; + lastEdge = edge; } - lastEdge = edge; } return firstEdge; } @@ -159,7 +182,7 @@ public class Loop { public void addConstraintCurve(final GraphOutline polyline) { // GraphOutline outline = new GraphOutline(polyline); /**needed to generate vertex references.*/ - initFromPolyline(polyline, Winding.CW); + initFromPolyline(polyline, Winding.CW); // -> HEdge.HOLE final GraphVertex v3 = locateClosestVertex(polyline); final HEdge v3Edge = v3.findBoundEdge(); diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java index 382d35a73..f5358b74b 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2011-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: @@ -56,7 +56,7 @@ class TypecastFont implements Font { /* pp */ final TTFont font; private final CmapFormat cmapFormat; private final int cmapentries; - private final IntObjectHashMap char2Glyph; + private final IntObjectHashMap idToGlyph; private final TypecastHMetrics metrics; // FIXME: Add cache size to limit memory usage ?? @@ -147,7 +147,7 @@ class TypecastFont implements Font { } } } - char2Glyph = new IntObjectHashMap(cmapentries + cmapentries/4); + idToGlyph = new IntObjectHashMap(cmapentries + cmapentries/4); metrics = new TypecastHMetrics(this); } @@ -197,11 +197,9 @@ class TypecastFont implements Font { } @Override - public Glyph getGlyph(final char symbol) { - TypecastGlyph result = (TypecastGlyph) char2Glyph.get(symbol); + public Glyph getGlyph(final int glyph_id) { + TypecastGlyph result = (TypecastGlyph) idToGlyph.get(glyph_id); if (null == result) { - final int glyph_id = getGlyphID( symbol ); - jogamp.graph.font.typecast.ot.Glyph glyph = font.getGlyph(glyph_id); final int glyph_advance; final AABBox glyph_bbox; @@ -222,9 +220,9 @@ class TypecastFont implements Font { break; } if(null == glyph) { - throw new RuntimeException("Could not retrieve glyph for symbol: <"+symbol+"> "+(int)symbol+" -> glyph id "+glyph_id); + throw new RuntimeException("Could not retrieve glyph for glyph id "+glyph_id); } - final OutlineShape shape = TypecastRenderer.buildShape(metrics.getUnitsPerEM(), symbol, glyph, vertexFactory); + final OutlineShape shape = TypecastRenderer.buildShape(metrics.getUnitsPerEM(), glyph, vertexFactory); KernSubtable kernSub = null; { final KernTable kern = font.getKernTable(); @@ -232,17 +230,17 @@ class TypecastFont implements Font { kernSub = kern.getSubtable0(); } } - result = new TypecastGlyph(this, symbol, glyph_id, glyph_bbox, glyph_advance, kernSub, shape); + result = new TypecastGlyph(this, glyph_id, glyph_bbox, glyph_advance, kernSub, shape); if(DEBUG) { final PostTable post = font.getPostTable(); final String glyph_name = null != post ? post.getGlyphName(glyph_id) : "n/a"; - System.err.println("New glyph: " + (int)symbol + " ( " + symbol +" ) -> " + glyph_id + "/'"+glyph_name+"', contours " + glyph.getPointCount() + ": " + shape); + System.err.println("New glyph: " + glyph_id + "/'"+glyph_name+"', contours " + glyph.getPointCount() + ": " + shape); System.err.println(" "+glyph); System.err.println(" "+result); } glyph.clearPointData(); - char2Glyph.put(symbol, result); + idToGlyph.put(glyph_id, result); } return result; } @@ -256,106 +254,154 @@ class TypecastFont implements Font { public int getLineHeightFU() { final Metrics metrics = getMetrics(); final int lineGap = metrics.getLineGapFU() ; // negative value! - final int ascent = metrics.getAscentFU() ; // negative value! final int descent = metrics.getDescentFU() ; // positive value! + final int ascent = metrics.getAscentFU() ; // negative value! final int advanceY = lineGap - descent + ascent; // negative value! return -advanceY; } @Override - public float getMetricWidth(final CharSequence string) { - return metrics.getScale( getMetricWidthFU(string) ); + public AABBox getMetricBounds(final CharSequence string) { + return getMetricBoundsFU(string).scale2(1.0f/metrics.getUnitsPerEM(), new float[3]); } @Override - public int getMetricWidthFU(final CharSequence string) { - int width = 0; - final int len = string.length(); - for (int i=0; i< len; i++) { + public AABBox getMetricBoundsFU(final CharSequence string) { + if (null == string || 0 == string.length() ) { + return new AABBox(); + } + final AABBox res = new AABBox(); + final int charCount = string.length(); + + final int lineHeight = getLineHeightFU(); + + int y = 0; + int advanceTotal = 0; + + for (int i=0; i<charCount; i++) { final char character = string.charAt(i); if (character == '\n') { - width = 0; + advanceTotal = 0; + y -= lineHeight; + } else if (character == ' ') { + advanceTotal += getAdvanceWidthFU(Glyph.ID_SPACE); } else { - final Glyph glyph = getGlyph(character); - width += glyph.getAdvanceFU(); + advanceTotal += getAdvanceWidthFU( getGlyphID( character ) ); } + res.resize(advanceTotal, y, 0f); + } + if( 0 < advanceTotal ) { + // add one line for current non '\n' terminated + y -= lineHeight; + res.resize(advanceTotal, y, 0f); } - return width; + return res; } @Override - public float getMetricHeight(final CharSequence string) { - return metrics.getScale( getMetricHeightFU(string) ); + public AABBox getGlyphBounds(final CharSequence string) { + return getGlyphBoundsFU(string).scale2(1.0f/metrics.getUnitsPerEM(), new float[3]); } @Override - public int getMetricHeightFU(final CharSequence string) { - int height = 0; + public AABBox getGlyphBoundsFU(final CharSequence string) { + if (null == string || 0 == string.length() ) { + return new AABBox(); + } + final AffineTransform temp1 = new AffineTransform(); + final AffineTransform temp2 = new AffineTransform(); + + final AABBox res = new AABBox(); + final int charCount = string.length(); + + final int lineHeight = getLineHeightFU(); - for (int i=0; i<string.length(); i++) { + int y = 0; + int advanceTotal = 0; + Font.Glyph left_glyph = null; + final AABBox temp_box = new AABBox(); + + for(int i=0; i< charCount; i++) { final char character = string.charAt(i); - if (character != ' ') { - final Glyph glyph = getGlyph(character); - height = (int)Math.ceil(Math.max(glyph.getBBoxFU().getHeight(), height)); + if( '\n' == character ) { + y -= lineHeight; + advanceTotal = 0; + left_glyph = null; + } else if (character == ' ') { + advanceTotal += getAdvanceWidthFU(Glyph.ID_SPACE); + left_glyph = null; + } else { + // reset transform + temp1.setToIdentity(); + final int glyph_id = getGlyphID(character); + final Font.Glyph glyph = getGlyph(glyph_id); + final OutlineShape glyphShape = glyph.getShape(); + if( null == glyphShape ) { + left_glyph = null; + continue; + } + if( null != left_glyph ) { + advanceTotal += left_glyph.getKerningFU(glyph_id); + } + temp1.translate(advanceTotal, y, temp2); + res.resize(temp1.transform(glyph.getBBoxFU(), temp_box)); + advanceTotal += glyph.getAdvanceFU(); + left_glyph = glyph; } } - return height; + return res; } @Override - public AABBox getMetricBounds(final CharSequence string) { - return getMetricBoundsFU(string).scale2(1.0f/metrics.getUnitsPerEM(), new float[3]); + public AABBox getGlyphShapeBounds(final CharSequence string) { + return getGlyphShapeBounds(null, string); } - @Override - public AABBox getMetricBoundsFU(final CharSequence string) { - if (string == null) { + public AABBox getGlyphShapeBounds(final AffineTransform transform, final CharSequence string) { + if (null == string || 0 == string.length() ) { return new AABBox(); } - final int charCount = string.length(); - final int lineHeight = getLineHeightFU(); - int totalHeight = 0; - int totalWidth = 0; - int curLineWidth = 0; - for (int i=0; i<charCount; i++) { - final char character = string.charAt(i); - if (character == '\n') { - totalWidth = Math.max(curLineWidth, totalWidth); - curLineWidth = 0; - totalHeight += lineHeight; - continue; - } - curLineWidth += getAdvanceWidthFU( getGlyphID( character ) ); - } - if (curLineWidth > 0) { - totalHeight += lineHeight; - totalWidth = Math.max(curLineWidth, totalWidth); - } - return new AABBox(0, 0, 0, totalWidth, totalHeight,0); + final OutlineShape.Visitor visitor = new OutlineShape.Visitor() { + @Override + public final void visit(final OutlineShape shape, final AffineTransform t) { + // nop + } }; + return processString(visitor, transform, string); + } + + @Override + public AABBox processString(final OutlineShape.Visitor visitor, final AffineTransform transform, + final CharSequence string) { + return processString(visitor, transform, string, new AffineTransform(), new AffineTransform()); } @Override - public AABBox getPointsBoundsFU(final AffineTransform transform, final CharSequence string) { - if (string == null) { + public AABBox processString(final OutlineShape.Visitor visitor, final AffineTransform transform, + final CharSequence string, + final AffineTransform temp1, final AffineTransform temp2) { + if (null == string || 0 == string.length() ) { return new AABBox(); } - final AffineTransform temp1 = new AffineTransform(); - final AffineTransform temp2 = new AffineTransform(); - final int charCount = string.length(); - final int lineHeight = getLineHeightFU(); - final AABBox tbox = new AABBox(); final AABBox res = new AABBox(); + final int charCount = string.length(); + + // region.setFlipped(true); + final float lineHeight = getLineHeight(); float y = 0; float advanceTotal = 0; + Font.Glyph left_glyph = null; + final AABBox temp_box = new AABBox(); for(int i=0; i< charCount; i++) { final char character = string.charAt(i); if( '\n' == character ) { y -= lineHeight; advanceTotal = 0; + left_glyph = null; } else if (character == ' ') { - advanceTotal += getAdvanceWidthFU(Glyph.ID_SPACE); + advanceTotal += getAdvanceWidth(Glyph.ID_SPACE); + left_glyph = null; } else { // reset transform if( null != transform ) { @@ -363,28 +409,28 @@ class TypecastFont implements Font { } else { temp1.setToIdentity(); } - temp1.translate(advanceTotal, y, temp2); - tbox.reset(); - - final Font.Glyph glyph = getGlyph(character); - res.resize(temp1.transform(glyph.getBBoxFU(), tbox)); - + final int glyph_id = getGlyphID(character); + final Font.Glyph glyph = getGlyph(glyph_id); final OutlineShape glyphShape = glyph.getShape(); if( null == glyphShape ) { + left_glyph = null; continue; } - advanceTotal += glyph.getAdvanceFU(); + if( null != left_glyph ) { + advanceTotal += left_glyph.getKerning(glyph_id); + } + temp1.translate(advanceTotal, y, temp2); + res.resize(temp1.transform(glyphShape.getBounds(), temp_box)); + + visitor.visit(glyphShape, temp1); + advanceTotal += glyph.getAdvance(); + left_glyph = glyph; } } return res; } @Override - public AABBox getPointsBounds(final AffineTransform transform, final CharSequence string) { - return getPointsBoundsFU(transform, string).scale2(1.0f/metrics.getUnitsPerEM(), new float[3]); - } - - @Override final public int getNumGlyphs() { return font.getNumGlyphs(); } diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java index e6a655661..12d492f6e 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2011-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: @@ -65,7 +65,7 @@ public final class TypecastGlyph implements Font.Glyph { /** in font-units */ public final AABBox getBBoxFU() { return this.bbox; } - /** Return advance in font units to be divided by unitsPerEM */ + /** Return advance in font units, sourced from `hmtx` table. */ public final int getAdvanceFU() { return this.advance; } @Override @@ -104,7 +104,6 @@ public final class TypecastGlyph implements Font.Glyph { return dst; } - private final char symbol; private final int id; private final int[/*right_glyphid*/][/*value*/] kerning; private final boolean kerning_horizontal; @@ -115,15 +114,13 @@ public final class TypecastGlyph implements Font.Glyph { /** * * @param font - * @param symbol * @param id * @param bbox in font-units * @param advance from hmtx in font-units * @param shape */ - protected TypecastGlyph(final TypecastFont font, final char symbol, final int id, final AABBox bbox, final int advance, + protected TypecastGlyph(final TypecastFont font, final int id, final AABBox bbox, final int advance, final KernSubtable kernSub, final OutlineShape shape) { - this.symbol = symbol; this.id = id; if( null != kernSub && kernSub.areKerningValues() ) { int pair_sz = 64; @@ -160,11 +157,6 @@ public final class TypecastGlyph implements Font.Glyph { return this.metrics.getFont(); } - @Override - public final char getSymbol() { - return this.symbol; - } - public final Metrics getMetrics() { return this.metrics; } @@ -195,6 +187,12 @@ public final class TypecastGlyph implements Font.Glyph { } @Override + public final AABBox getBBox() { + final AABBox dest = new AABBox(); + return dest.copy(metrics.getBBoxFU()).scale2(1.0f/metrics.getUnitsPerEM(), new float[2]); + } + + @Override public final int getAdvanceFU() { return metrics.getAdvanceFU(); } @Override diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java index 274114e4a..cae8b2755 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2011-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: diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java index 60319d842..2a895a2f1 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2011-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: @@ -27,7 +27,9 @@ */ package jogamp.graph.font.typecast; +import jogamp.graph.font.typecast.ot.Glyph; import jogamp.graph.font.typecast.ot.Point; +import jogamp.graph.font.typecast.ot.T2Glyph; import jogamp.opengl.Debug; import com.jogamp.graph.curve.OutlineShape; @@ -46,50 +48,76 @@ import com.jogamp.graph.geom.Vertex.Factory; */ public class TypecastRenderer { private static final boolean DEBUG = Debug.debug("graph.font.Renderer"); + private static final boolean PRINT_CODE = Debug.debug("graph.font.Renderer.Code"); private static void addShapeMoveTo(final float unitsPerEM, final OutlineShape shape, final Point p1) { - if( DEBUG ) { System.err.println("Shape.MoveTo: "+p1); } + if( PRINT_CODE ) { + System.err.println("// Shape.MoveTo:"); + System.err.printf("shape.closeLastOutline(false);%n"); + System.err.printf("shape.addEmptyOutline();%n"); + System.err.printf("shape.addVertex(%d, %ff, %ff, %b);%n", 0, p1.x/unitsPerEM, p1.y/unitsPerEM, true); + } shape.closeLastOutline(false); shape.addEmptyOutline(); - shape.addVertex(0, p1.x/unitsPerEM, p1.y/unitsPerEM, p1.onCurve); + shape.addVertex(0, p1.x/unitsPerEM, p1.y/unitsPerEM, true); } private static void addShapeLineTo(final float unitsPerEM, final OutlineShape shape, final Point p1) { - if( DEBUG ) { System.err.println("Shape.LineTo: "+p1); } - shape.addVertex(0, p1.x/unitsPerEM, p1.y/unitsPerEM, p1.onCurve); + if( PRINT_CODE ) { + System.err.println("// Shape.LineTo:"); + System.err.printf("shape.addVertex(%d, %ff, %ff, %b);%n", 0, p1.x/unitsPerEM, p1.y/unitsPerEM, true); + } + shape.addVertex(0, p1.x/unitsPerEM, p1.y/unitsPerEM, true); } private static void addShapeQuadTo(final float unitsPerEM, final OutlineShape shape, final Point p1, final Point p2) { - if( DEBUG ) { System.err.println("Shape.QuadTo: "+p1+", "+p2); } - shape.addVertex(0, p1.x/unitsPerEM, p1.y/unitsPerEM, p1.onCurve); - shape.addVertex(0, p2.x/unitsPerEM, p2.y/unitsPerEM, p2.onCurve); + if( PRINT_CODE ) { + System.err.println("// Shape.QuadTo:"); + System.err.printf("shape.addVertex(%d, %ff, %ff, %b);%n", 0, p1.x/unitsPerEM, p1.y/unitsPerEM, false); + System.err.printf("shape.addVertex(%d, %ff, %ff, %b);%n", 0, p2.x/unitsPerEM, p2.y/unitsPerEM, true); + } + shape.addVertex(0, p1.x/unitsPerEM, p1.y/unitsPerEM, false); + shape.addVertex(0, p2.x/unitsPerEM, p2.y/unitsPerEM, true); + } + private static void addShapeQuadTo(final float unitsPerEM, final OutlineShape shape, final Point p1, final float p2x, final float p2y) { + if( PRINT_CODE ) { + System.err.println("// Shape.QuadTo:"); + System.err.printf("shape.addVertex(%d, %ff, %ff, %b);%n", 0, p1.x/unitsPerEM, p1.y/unitsPerEM, false); + System.err.printf("shape.addVertex(%d, %ff, %ff, %b);%n", 0, p2x/unitsPerEM, p2y/unitsPerEM, true); + } + shape.addVertex(0, p1.x/unitsPerEM, p1.y/unitsPerEM, false); + shape.addVertex(0, p2x/unitsPerEM, p2y/unitsPerEM, true); } - private static void addShapeQuadTo(final float unitsPerEM, final OutlineShape shape, final Point p1, - final float p2x, final float p2y, final boolean p2OnCurve) { - if( DEBUG ) { System.err.println("Shape.QuadTo: "+p1+", p2 "+p2x+", "+p2y+", onCurve "+p2OnCurve); } - shape.addVertex(0, p1.x/unitsPerEM, p1.y/unitsPerEM, p1.onCurve); - shape.addVertex(0, p2x/unitsPerEM, p2y/unitsPerEM, p2OnCurve); + private static void addShapeCubicTo(final float unitsPerEM, final OutlineShape shape, final Point p1, final Point p2, final Point p3) { + if( PRINT_CODE ) { + System.err.println("// Shape.CubicTo:"); + System.err.printf("shape.addVertex(%d, %ff, %ff, %b);%n", 0, p1.x/unitsPerEM, p1.y/unitsPerEM, false); + System.err.printf("shape.addVertex(%d, %ff, %ff, %b);%n", 0, p2.x/unitsPerEM, p2.y/unitsPerEM, false); + System.err.printf("shape.addVertex(%d, %ff, %ff, %b);%n", 0, p3.x/unitsPerEM, p3.y/unitsPerEM, true); + } + shape.addVertex(0, p1.x/unitsPerEM, p1.y/unitsPerEM, false); + shape.addVertex(0, p2.x/unitsPerEM, p2.y/unitsPerEM, false); + shape.addVertex(0, p3.x/unitsPerEM, p3.y/unitsPerEM, true); } - /** - private static void addShapeCubicTo(final float unitsPerEM, final OutlineShape shape, Factory<? extends Vertex> vertexFactory, Point p1, Point p2, Point p3) { - shape.addVertex(0, p1.x/unitsPerEM, p1.y/unitsPerEM, p1.onCurve); - shape.addVertex(0, p2.x/unitsPerEM, p2.y/unitsPerEM, p2.onCurve); - shape.addVertex(0, p3.x/unitsPerEM, p3.y/unitsPerEM, p3.onCurve); - } */ - public static OutlineShape buildShape(final int unitsPerEM, final char symbol, final jogamp.graph.font.typecast.ot.Glyph glyph, final Factory<? extends Vertex> vertexFactory) { - // - // See Typecast: GlyphPathFactory.addContourToPath(..) - // + public static OutlineShape buildShape(final int unitsPerEM, final Glyph glyph, final Factory<? extends Vertex> vertexFactory) { if (glyph == null) { return null; } - final OutlineShape shape = new OutlineShape(vertexFactory); - buildShapeImpl(unitsPerEM, shape, symbol, glyph); - shape.setIsQuadraticNurbs(); + if (glyph instanceof T2Glyph) { + // Type 1/2: Cubic + if( PRINT_CODE ) { System.err.printf("%n// Start Type-2 Shape for Glyph %d%n", glyph.getID()); } + buildShapeType2(unitsPerEM, shape, (T2Glyph)glyph); + } else { + // TTF: quadratic only + if( PRINT_CODE ) { System.err.printf("%n// Start TTF Shape for Glyph %d%n", glyph.getID()); } + buildShapeTTF(unitsPerEM, shape, glyph); + shape.setIsQuadraticNurbs(); + } + if( PRINT_CODE ) { System.err.printf("// End Shape for Glyph %d%n%n", glyph.getID()); } return shape; } - private static void buildShapeImpl(final float unitsPerEM, final OutlineShape shape, final char symbol, final jogamp.graph.font.typecast.ot.Glyph glyph) { + private static void buildShapeTTF(final float unitsPerEM, final OutlineShape shape, final Glyph glyph) { // Iterate through all of the points in the glyph. Each time we find a // contour end point, add the point range to the path. int startIndex = 0; @@ -99,98 +127,125 @@ public class TypecastRenderer { count++; if ( glyph.getPoint(i).endOfContour ) { int offset = 0; - while ( offset < count - 1 ) { // require at least +1 point (last one is end-of-contour) - final Point p0 = glyph.getPoint(startIndex + offset%count); - final Point p1 = glyph.getPoint(startIndex + (offset+1)%count); - final Point p2 = glyph.getPoint(startIndex + (offset+2)%count); - final Point p3 = offset+3 < count ? glyph.getPoint(startIndex + offset+3) : null; + while ( offset < count ) { + final int point_0_idx = startIndex + offset%count; + final Point point_m = glyph.getPoint((offset==0) ? startIndex+count-1 : startIndex+(offset-1)%count); + final Point point_0 = glyph.getPoint(point_0_idx); + final Point point_1 = glyph.getPoint(startIndex + (offset+1)%count); + final Point point_2 = glyph.getPoint(startIndex + (offset+2)%count); + // final Point point_3 = offset+3 < count ? glyph.getPoint(startIndex + offset+3) : null; if( DEBUG ) { - System.err.println("GlyphShape<"+symbol+">: offset "+offset+" of "+count+"/"+totalPoints+" points"); - final int pMIdx= (offset==0) ? startIndex+count-1 : startIndex+(offset-1)%count; - final Point pM = glyph.getPoint(pMIdx); - final int p0Idx = startIndex + offset%count; - final int p1Idx = startIndex + (offset+1)%count; - final int p2Idx = startIndex + (offset+2)%count; - final int p3Idx = startIndex + (offset+3)%count; - System.err.println("\t pM["+pMIdx+"] "+pM); - System.err.println("\t p0["+p0Idx+"] "+p0); - System.err.println("\t p1["+p1Idx+"] "+p1); - System.err.println("\t p2["+p2Idx+"] "+p2); - System.err.println("\t p3["+p3Idx+"] "+p3); + System.err.println("GlyphShape<"+glyph.getID()+">: offset "+offset+" of "+count+"/"+totalPoints+" points"); + final int point_m_idx= (offset==0) ? startIndex+count-1 : startIndex+(offset-1)%count; + final int point_1_idx = startIndex + (offset+1)%count; + final int point_2_idx = startIndex + (offset+2)%count; + // final int point_3_idx = startIndex + (offset+3)%count; + System.err.println("\t pM["+point_m_idx+"] "+point_m); + System.err.println("\t p0["+point_0_idx+"] "+point_0); + System.err.println("\t p1["+point_1_idx+"] "+point_1); + System.err.println("\t p2["+point_2_idx+"] "+point_2); + // System.err.println("\t p3["+point_3_idx+"] "+point_3); } if(offset == 0) { - addShapeMoveTo(unitsPerEM, shape, p0); - // gp.moveTo(point.x, point.y); + addShapeMoveTo(unitsPerEM, shape, point_0); // OK } - - if( p0.endOfContour ) { - // Branch-0: EOC ** SHALL NEVER HAPPEN ** - if( DEBUG ) { System.err.println("B0 .. end-of-contour **** EOC"); } - shape.closeLastOutline(false); - break; - } else if (p0.onCurve) { - if (p1.onCurve) { + if (point_0.onCurve) { + if (point_1.onCurve) { // Branch-1: point.onCurve && p1.onCurve - if( DEBUG ) { System.err.println("B1 .. line-to p0-p1"); } - - // s = new Line2D.Float(point.x, point.y, p1.x, p1.y); - // gp.lineTo( p1.x, p1.y ); - addShapeLineTo(unitsPerEM, shape, p1); + if( PRINT_CODE ) { System.err.printf("// %03d: B1: line-to p0-p1%n", point_0_idx); } + addShapeLineTo(unitsPerEM, shape, point_1); // OK offset++; } else { - if (p2.onCurve) { + if (point_2.onCurve) { // Branch-2: point.onCurve && !p1.onCurve && p2.onCurve - if( DEBUG ) { System.err.println("B2 .. quad-to p0-p1-p2"); } - - // s = new QuadCurve2D.Float( point.x, point.y, p1.x, p1.y, p2.x, p2.y); - // gp.quadTo(p1.x, p1.y, p2.x, p2.y); - addShapeQuadTo(unitsPerEM, shape, p1, p2); + if( PRINT_CODE ) { System.err.printf("// %03d: B2: quad-to p0-p1-p2%n", point_0_idx); } + addShapeQuadTo(unitsPerEM, shape, point_1, point_2); // OK offset+=2; } else { - if (null != p3 && p3.onCurve) { + /** if (null != point_3 && point_3.onCurve) { + // Not required, handled via B4 and subsequent B6! // Branch-3: point.onCurve && !p1.onCurve && !p2.onCurve && p3.onCurve - if( DEBUG ) { System.err.println("B3 .. 2-quad p0-p1-p1_2, p1_2-p2-p3 **** 2QUAD"); } - // addShapeCubicTo(shape, vertexFactory, p1, p2, p3); - addShapeQuadTo(unitsPerEM, shape, p1, midValue(p1.x, p2.x), - midValue(p1.y, p2.y), - true); - addShapeQuadTo(unitsPerEM, shape, p2, p3); + if( PRINT_CODE ) { System.err.printf("// %03d: B3: p0-p1-p1_2, p1_2-p2-p3 **** 2QUAD%n", point_0_idx); } + addShapeQuadTo(unitsPerEM, shape, point_1, midValue(point_1.x, point_2.x), + midValue(point_1.y, point_2.y)); + addShapeQuadTo(unitsPerEM, shape, point_2, point_3); offset+=3; - } else { + } else */ { // Branch-4: point.onCurve && !p1.onCurve && !p2.onCurve && !p3.onCurve - if( DEBUG ) { System.err.println("B4 .. quad-to p0-p1-p2h **** MID"); } - - // s = new QuadCurve2D.Float(point.x,point.y,p1.x,p1.y, - // midValue(p1.x, p2.x), midValue(p1.y, p2.y)); - // gp.quadTo(p1.x, p1.y, midValue(p1.x, p2.x), midValue(p1.y, p2.y)); - addShapeQuadTo(unitsPerEM, shape, p1, midValue(p1.x, p2.x), - midValue(p1.y, p2.y), - true); + if( PRINT_CODE ) { System.err.printf("// %03d: B4: quad-to p0-p1-p2h **** MID%n", point_0_idx); } + addShapeQuadTo(unitsPerEM, shape, point_1, midValue(point_1.x, point_2.x), + midValue(point_1.y, point_2.y)); offset+=2; // Skip p2 as done in Typecast } } } } else { - if (!p1.onCurve) { + if (!point_1.onCurve) { // Branch-5: !point.onCurve && !p1.onCurve - if( DEBUG ) { System.err.println("B5 .. quad-to pMh-p0-p1h ***** MID"); } - // s = new QuadCurve2D.Float(midValue(pM.x, point.x), midValue(pM.y, point.y), - // point.x, point.y, - // midValue(point.x, p1.x), midValue(point.y, p1.y)); - addShapeQuadTo(unitsPerEM, shape, p0, midValue(p0.x, p1.x), - midValue(p0.y, p1.y), true); + if( PRINT_CODE ) { System.err.printf("// %03d: B5: quad-to pMh-p0-p1h ***** MID%n", point_0_idx); } + addShapeQuadTo(unitsPerEM, shape, point_0, midValue(point_0.x, point_1.x), // OK + midValue(point_0.y, point_1.y) ); offset++; } else { // Branch-6: !point.onCurve && p1.onCurve - if( DEBUG ) { System.err.println("B6 .. quad-to pMh-p0-p1"); } - // s = new QuadCurve2D.Float(midValue(pM.x, point.x), midValue(pM.y, point.y), - // point.x, point.y, p1.x, p1.y); - // gp.quadTo(point.x, point.y, p1.x, p1.y); - addShapeQuadTo(unitsPerEM, shape, p0, p1); + if( PRINT_CODE ) { System.err.printf("// %03d: B6: quad-to pMh-p0-p1%n", point_0_idx); } + addShapeQuadTo(unitsPerEM, shape, point_0, point_1); // OK offset++; } } } + if( PRINT_CODE ) { System.err.printf("shape.closeLastOutline(false);%n%n"); } + shape.closeLastOutline(false); + startIndex = i + 1; + count = 0; + } + } + } + + private static void buildShapeType2(final float unitsPerEM, final OutlineShape shape, final T2Glyph glyph) { + // Iterate through all of the points in the glyph. Each time we find a + // contour end point, add the point range to the path. + int startIndex = 0; + int count = 0; + final int totalPoints = glyph.getPointCount(); + for (int i = 0; i < totalPoints; i++) { + count++; + if ( glyph.getPoint(i).endOfContour ) { + int offset = 0; + while ( offset < count ) { + final int point_0_idx = startIndex + offset%count; + final Point point_0 = glyph.getPoint(point_0_idx); + final Point point_1 = glyph.getPoint(startIndex + (offset+1)%count); + final Point point_2 = glyph.getPoint(startIndex + (offset+2)%count); + final Point point_3 = glyph.getPoint(startIndex + (offset+3)%count); + if( DEBUG ) { + System.err.println("GlyphShape<"+glyph.getID()+">: offset "+offset+" of "+count+"/"+totalPoints+" points"); + final int point_1_idx = startIndex + (offset+1)%count; + final int point_2_idx = startIndex + (offset+2)%count; + final int point_3_idx = startIndex + (offset+3)%count; + System.err.println("\t p0["+point_0_idx+"] "+point_0); + System.err.println("\t p1["+point_1_idx+"] "+point_1); + System.err.println("\t p2["+point_2_idx+"] "+point_2); + System.err.println("\t p3["+point_3_idx+"] "+point_3); + } + if(offset == 0) { + addShapeMoveTo(unitsPerEM, shape, point_0); // OK + } + if (point_0.onCurve && point_1.onCurve) { + // Branch-1: point.onCurve && p1.onCurve + if( PRINT_CODE ) { System.err.printf("// %03d: C1: line-to p0-p1%n", point_0_idx); } + addShapeLineTo(unitsPerEM, shape, point_1); // OK + offset++; + } else if (point_0.onCurve && !point_1.onCurve && !point_2.onCurve && point_3.onCurve) { + if( PRINT_CODE ) { System.err.printf("// %03d: C2: cubic-to p0-p1-p2%n", point_0_idx); } + addShapeCubicTo(unitsPerEM, shape, point_0, point_2, point_3); + offset+=3; + } else { + System.out.println("addContourToPath case not catered for!!"); + break; + } + } + if( PRINT_CODE ) { System.err.printf("shape.closeLastOutline(false);%n%n"); } shape.closeLastOutline(false); startIndex = i + 1; count = 0; @@ -198,7 +253,103 @@ public class TypecastRenderer { } } - private static float midValue(final float a, final float b) { - return a + (b - a)/2f; + /** + * Returns the mid-value of two. + * <p> + * Intentionally using integer arithmetic on unitPerEM sized values w/o rounding. + * </p> + */ + private static int midValue(final int a, final int b) { + return a + (b - a)/2; + } + + // + // Leaving Typecast's orig rendering loop in here, transformed to using our OutlineShape. + // This is now actually the same since ours has been re-aligned on 2023-02-15. + // + + @SuppressWarnings("unused") + private static void buildShapeImplX(final float unitsPerEM, final OutlineShape shape, final Glyph glyph) { + // Iterate through all of the points in the glyph. Each time we find a + // contour end point, add the point range to the path. + int firstIndex = 0; + int count = 0; + final int totalPoints = glyph.getPointCount(); + if (glyph instanceof T2Glyph) { + // addContourToPath(unitsPerEM, shape, (T2Glyph) glyph, firstIndex, count); + throw new RuntimeException("T2Glyph Not Yet Supported: "+glyph); + } + if( PRINT_CODE ) { System.err.printf("%n// Start Shape for Glyph %d%n", glyph.getID()); } + for (int i = 0; i < totalPoints; i++) { + count++; + if ( glyph.getPoint(i).endOfContour ) { + addContourToPathX1(unitsPerEM, shape, glyph, firstIndex, count); + firstIndex = i + 1; + count = 0; + } + } + if( PRINT_CODE ) { System.err.printf("// End Shape for Glyph %d%n%n", glyph.getID()); } + } + private static void addContourToPathX1(final float unitsPerEM, final OutlineShape shape, final Glyph glyph, final int startIndex, final int count) { + int offset = 0; + while ( offset < count ) { + final int point_0_idx = startIndex + offset%count; + final Point point_m = glyph.getPoint((offset==0) ? startIndex+count-1 : startIndex+(offset-1)%count); + final Point point_0 = glyph.getPoint(point_0_idx); + final Point point_1 = glyph.getPoint(startIndex + (offset+1)%count); + final Point point_2 = glyph.getPoint(startIndex + (offset+2)%count); + // final Point point_3 = offset+3 < count ? glyph.getPoint(startIndex + offset+3) : null; + if( DEBUG ) { + System.err.println("GlyphShape<"+glyph.getID()+">: offset "+offset+" of "+count+" points"); + final int point_m_idx= (offset==0) ? startIndex+count-1 : startIndex+(offset-1)%count; + final int point_1_idx = startIndex + (offset+1)%count; + final int point_2_idx = startIndex + (offset+2)%count; + // final int p3Idx = startIndex + (offset+3)%count; + System.err.println("\t pM["+point_m_idx+"] "+point_m); + System.err.println("\t p0["+point_0_idx+"] "+point_0); + System.err.println("\t p1["+point_1_idx+"] "+point_1); + System.err.println("\t p2["+point_2_idx+"] "+point_2); + // System.err.println("\t p3["+p3Idx+"] "+point_3); + } + if(offset == 0) { + addShapeMoveTo(unitsPerEM, shape, point_0); + } + if (point_0.onCurve && point_1.onCurve) { + // Branch-1: point.onCurve && p1.onCurve + if( PRINT_CODE ) { System.err.printf("// %03d: B1: line-to p0-p1%n", point_0_idx); } + addShapeLineTo(unitsPerEM, shape, point_1); + offset++; + } else if (point_0.onCurve && !point_1.onCurve && point_2.onCurve) { + // Branch-2: point.onCurve && !p1.onCurve && p2.onCurve + if( PRINT_CODE ) { System.err.printf("// %03d: B2: quad-to p0-p1-p2%n", point_0_idx); } + addShapeQuadTo(unitsPerEM, shape, point_1, point_2); + offset+=2; + } else if (point_0.onCurve && !point_1.onCurve && !point_2.onCurve) { + // Branch-4: point.onCurve && !p1.onCurve && !p2.onCurve && !p3.onCurve + if( PRINT_CODE ) { System.err.printf("// %03d: B4: quad-to p0-p1-p2h **** MID%n", point_0_idx); } + addShapeQuadTo(unitsPerEM, shape, point_1, + midValue(point_1.x, point_2.x), + midValue(point_1.y, point_2.y)); + + offset+=2; + } else if (!point_0.onCurve && !point_1.onCurve) { + // Branch-5: !point.onCurve && !p1.onCurve + if( PRINT_CODE ) { System.err.printf("// %03d: B5: quad-to pMh-p0-p1h ***** MID%n", point_0_idx); } + addShapeQuadTo(unitsPerEM, shape, point_0, + midValue(point_0.x, point_1.x), + midValue(point_0.y, point_1.y) ); + offset++; + } else if (!point_0.onCurve && point_1.onCurve) { + // Branch-6: !point.onCurve && p1.onCurve + if( PRINT_CODE ) { System.err.printf("// %03d: B6: quad-to pMh-p0-p1%n", point_0_idx); } + addShapeQuadTo(unitsPerEM, shape, point_0, point_1); + offset++; + } else { + System.out.println("addContourToPath case not catered for!!"); + break; + } + } + if( PRINT_CODE ) { System.err.printf("shape.closeLastOutline(false);%n%n"); } + shape.closeLastOutline(false); } } diff --git a/src/jogl/classes/jogamp/graph/font/typecast/ot/Glyph.java b/src/jogl/classes/jogamp/graph/font/typecast/ot/Glyph.java index 7b29a6c48..5bb610a1c 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/ot/Glyph.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/ot/Glyph.java @@ -31,25 +31,31 @@ public abstract class Glyph { public Glyph(final int glyph_id) { _glyph_id = glyph_id; } - + /** Return the assigned glyph ID of this instance */ - public final int getGlyphIndex() { return _glyph_id; } - + public final int getID() { return _glyph_id; } + public abstract void clearPointData(); - /** Return the AABBox in font-units */ + /** + * Return the AABBox in font-units. + * <p> + * This is either the GlyphDescripton's min- and maximum for TTF + * or the calculated box over all points. + * </p> + */ public final AABBox getBBox() { return _bbox; } - + /** hmtx value */ public abstract int getAdvanceWidth(); - + /** hmtx value */ public abstract short getLeftSideBearing(); public abstract Point getPoint(int i); public abstract int getPointCount(); - + @Override public abstract String toString(); } diff --git a/src/jogl/classes/jogamp/graph/font/typecast/ot/table/GlyfDescript.java b/src/jogl/classes/jogamp/graph/font/typecast/ot/table/GlyfDescript.java index 2a8deaeeb..45d06e8cb 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/ot/table/GlyfDescript.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/ot/table/GlyfDescript.java @@ -67,19 +67,20 @@ public abstract class GlyfDescript extends Program implements GlyphDescription { static final byte yDual = 0x20; final GlyfTable _parentTable; - private int _glyphIndex; + private final int _glyphIndex; private final int _numberOfContours; - private short _xMin; - private short _yMin; - private short _xMax; - private short _yMax; + private final short _xMin; + private final short _yMin; + private final short _xMax; + private final short _yMax; GlyfDescript( - GlyfTable parentTable, - int glyphIndex, - short numberOfContours, - DataInput di) throws IOException { + final GlyfTable parentTable, + final int glyphIndex, + final short numberOfContours, + final DataInput di) throws IOException { _parentTable = parentTable; + _glyphIndex = glyphIndex; _numberOfContours = numberOfContours; _xMin = di.readShort(); _yMin = di.readShort(); @@ -91,26 +92,32 @@ public abstract class GlyfDescript extends Program implements GlyphDescription { return _numberOfContours; } + @Override public int getGlyphIndex() { return _glyphIndex; } + @Override public short getXMaximum() { return _xMax; } + @Override public short getXMinimum() { return _xMin; } + @Override public short getYMaximum() { return _yMax; } + @Override public short getYMinimum() { return _yMin; } - + + @Override public String toString() { return " numberOfContours: " + _numberOfContours + "\n xMin: " + _xMin + diff --git a/src/test/com/jogamp/opengl/test/junit/graph/FontSet01.java b/src/test/com/jogamp/opengl/test/junit/graph/FontSet01.java index 38eda9adf..a2dc8430d 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/FontSet01.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/FontSet01.java @@ -12,8 +12,8 @@ public class FontSet01 { final Font[] fonts = new Font[11]; int i = 0; fonts[i++] = FontFactory.get(FontFactory.UBUNTU).getDefault(); // FontSet.FAMILY_REGULAR, FontSet.STYLE_NONE - fonts[i++] = FontFactory.get(FontFactory.UBUNTU).get(FontSet.FAMILY_REGULAR, FontSet.STYLE_ITALIC); - fonts[i++] = FontFactory.get(FontFactory.UBUNTU).get(FontSet.FAMILY_REGULAR, FontSet.STYLE_BOLD); + fonts[i++] = FontFactory.get(FontFactory.UBUNTU).get(FontSet.FAMILY_LIGHT, FontSet.STYLE_NONE); + fonts[i++] = FontFactory.get(FontFactory.UBUNTU).get(FontSet.FAMILY_LIGHT, FontSet.STYLE_ITALIC); fonts[i++] = FontFactory.get(IOUtil.getResource("fonts/freefont/FreeMono.ttf", TestTextRendererNEWTBugXXXX.class.getClassLoader(), TestTextRendererNEWTBugXXXX.class).getInputStream(), true); fonts[i++] = FontFactory.get(IOUtil.getResource("fonts/freefont/FreeMonoBold.ttf", diff --git a/src/test/com/jogamp/opengl/test/junit/graph/TestFontsNEWT00.java b/src/test/com/jogamp/opengl/test/junit/graph/TestFontsNEWT00.java index be654d345..5b254d46d 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/TestFontsNEWT00.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/TestFontsNEWT00.java @@ -83,7 +83,7 @@ public class TestFontsNEWT00 extends UITestCase { void testFontGlyph01(final Font font, final char c, final float pixelSize) { final int glyphID = font.getGlyphID(c); final int s0 = font.getAdvanceWidthFU(glyphID); - final Font.Glyph glyph = font.getGlyph(c); + final Font.Glyph glyph = font.getGlyph(glyphID); final int s1 = glyph.getAdvanceFU(); final int unitsPerEM = font.getMetrics().getUnitsPerEM(); @@ -117,7 +117,7 @@ public class TestFontsNEWT00 extends UITestCase { void testFontGlyph02(final Font font, final char left, final char right) { final int glyphid_left = font.getGlyphID(left); final int glyphid_right = font.getGlyphID(right); - final Font.Glyph glyph_left = font.getGlyph(left); + final Font.Glyph glyph_left = font.getGlyph(glyphid_left); final int k_val = glyph_left.getKerningFU(glyphid_right); diff --git a/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT01.java b/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT01.java index 8826275b1..ec539d6db 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT01.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT01.java @@ -48,8 +48,8 @@ import org.junit.runners.MethodSorters; import com.jogamp.common.os.Platform; import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.RenderState; +import com.jogamp.graph.font.Font; import com.jogamp.graph.font.FontFactory; -import com.jogamp.graph.font.FontSet; import com.jogamp.graph.geom.SVertex; import com.jogamp.newt.opengl.GLWindow; import com.jogamp.opengl.test.junit.graph.demos.GPUTextRendererListenerBase01; @@ -142,51 +142,87 @@ public class TestTextRendererNEWT01 extends UITestCase { final Runnable action_per_font = new Runnable() { @Override public void run() { + if( false ) { + textGLListener.setHeadBox(1, false); + textGLListener.setSampleCount(2); + window.display(); + textGLListener.printScreenOnGLThread(window, "./", window.getTitle(), "", false); + sleep(); + + textGLListener.setHeadBox(2, false); + textGLListener.setSampleCount(2); + window.display(); + textGLListener.printScreenOnGLThread(window, "./", window.getTitle(), "", false); + sleep(); + } + textGLListener.setHeadBox(1, false); - textGLListener.setSampleCount(2); + textGLListener.setSampleCount(4); window.display(); textGLListener.printScreenOnGLThread(window, "./", window.getTitle(), "", false); sleep(); textGLListener.setHeadBox(2, false); - textGLListener.setSampleCount(2); + textGLListener.setSampleCount(4); window.display(); textGLListener.printScreenOnGLThread(window, "./", window.getTitle(), "", false); sleep(); + } }; - textGLListener.setHeadBox(1, false); - textGLListener.setSampleCount(3); - window.display(); - textGLListener.printScreenOnGLThread(window, "./", window.getTitle(), "", false); - sleep(); + final Font[] fonts = FontSet01.getSet01(); + for(final Font f : fonts) { + if( textGLListener.setFont(f) ) { + action_per_font.run(); + } + } + if(textGLListener.setFontSet(FontFactory.JAVA, 0, 0)) { + action_per_font.run(); + } + destroyWindow(window); + } - textGLListener.setHeadBox(2, false); - textGLListener.setSampleCount(3); - window.display(); - textGLListener.printScreenOnGLThread(window, "./", window.getTitle(), "", false); - sleep(); + @Test + public void testTextRendererMSAA01() throws InterruptedException, GLException, IOException { + final GLProfile glp = GLProfile.get(GLProfile.GL2ES2); + final GLCapabilities caps = new GLCapabilities(glp); + caps.setAlphaBits(4); + caps.setSampleBuffers(true); + caps.setNumSamples(4); + System.err.println("Requested: "+caps); + + final GLWindow window = createWindow("text-vbaa0-msaa1", caps, 1024, 640); + window.display(); + System.err.println("Chosen: "+window.getChosenGLCapabilities()); + final RenderState rs = RenderState.createRenderState(SVertex.factory()); + final TextGLListener textGLListener = new TextGLListener(rs, 0, 0 /* sampleCount */, DEBUG, TRACE); + textGLListener.attachInputListenerTo(window); + window.addGLEventListener(textGLListener); + textGLListener.setHeadBox(2, true); + window.display(); + + final Runnable action_per_font = new Runnable() { + @Override + public void run() { textGLListener.setHeadBox(1, false); - textGLListener.setSampleCount(4); + textGLListener.setSampleCount(0); window.display(); textGLListener.printScreenOnGLThread(window, "./", window.getTitle(), "", false); sleep(); textGLListener.setHeadBox(2, false); - textGLListener.setSampleCount(4); + textGLListener.setSampleCount(0); window.display(); textGLListener.printScreenOnGLThread(window, "./", window.getTitle(), "", false); sleep(); } }; - if(textGLListener.setFontSet(FontFactory.UBUNTU, FontSet.FAMILY_LIGHT, FontSet.STYLE_NONE)) { - action_per_font.run(); - } - - if(textGLListener.setFontSet(FontFactory.UBUNTU, FontSet.FAMILY_REGULAR, FontSet.STYLE_NONE)) { - action_per_font.run(); + final Font[] fonts = FontSet01.getSet01(); + for(final Font f : fonts) { + if( textGLListener.setFont(f) ) { + action_per_font.run(); + } } - if(textGLListener.setFontSet(FontFactory.JAVA, 0, 0)) { action_per_font.run(); } @@ -195,15 +231,13 @@ public class TestTextRendererNEWT01 extends UITestCase { } @Test - public void testTextRendererMSAA01() throws InterruptedException, GLException, IOException { + public void testTextRendererNoSampling() throws InterruptedException, GLException, IOException { final GLProfile glp = GLProfile.get(GLProfile.GL2ES2); final GLCapabilities caps = new GLCapabilities(glp); caps.setAlphaBits(4); - caps.setSampleBuffers(true); - caps.setNumSamples(4); System.err.println("Requested: "+caps); - final GLWindow window = createWindow("text-vbaa0-msaa1", caps, 1024, 640); + final GLWindow window = createWindow("text-vbaa0-msaa0", caps, 1024, 640); window.display(); System.err.println("Chosen: "+window.getChosenGLCapabilities()); @@ -230,14 +264,12 @@ public class TestTextRendererNEWT01 extends UITestCase { sleep(); } }; - if(textGLListener.setFontSet(FontFactory.UBUNTU, FontSet.FAMILY_LIGHT, FontSet.STYLE_NONE)) { - action_per_font.run(); - } - - if(textGLListener.setFontSet(FontFactory.UBUNTU, FontSet.FAMILY_REGULAR, FontSet.STYLE_NONE)) { - action_per_font.run(); + final Font[] fonts = FontSet01.getSet01(); + for(final Font f : fonts) { + if( textGLListener.setFont(f) ) { + action_per_font.run(); + } } - if(textGLListener.setFontSet(FontFactory.JAVA, 0, 0)) { action_per_font.run(); } diff --git a/src/test/com/jogamp/opengl/test/junit/graph/TextRendererGLELBase.java b/src/test/com/jogamp/opengl/test/junit/graph/TextRendererGLELBase.java index 6bc120cde..f307a1196 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/TextRendererGLELBase.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/TextRendererGLELBase.java @@ -272,11 +272,9 @@ public abstract class TextRendererGLELBase implements GLEventListener { if( cacheRegion ) { textRenderUtil.drawString3D(gl, renderer, font, text, null, vbaaSampleCount); } else if( null != region ) { - TextRegionUtil.drawString3D(gl, region, renderer, font, text, null, vbaaSampleCount, - textRenderUtil.tempT1, textRenderUtil.tempT2); + TextRegionUtil.drawString3D(gl, region, renderer, font, text, null, vbaaSampleCount); } else { - TextRegionUtil.drawString3D(gl, renderModes, renderer, font, text, null, vbaaSampleCount, - textRenderUtil.tempT1, textRenderUtil.tempT2); + TextRegionUtil.drawString3D(gl, renderModes, renderer, font, text, null, vbaaSampleCount); } renderer.enable(gl, false); diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPURegionGLListener01.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPURegionGLListener01.java index 7dc217c2d..b78826a6d 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPURegionGLListener01.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPURegionGLListener01.java @@ -99,30 +99,30 @@ public class GPURegionGLListener01 extends GPURendererListenerBase01 { } private void createTestOutline01(){ - outlineShape.moveTo(0.0f,-10.0f); - outlineShape.lineTo(15.0f,-10.0f); - outlineShape.quadTo(10.0f,5.0f, 15.0f,10.0f); - outlineShape.cubicTo(6.0f,15.0f, 5.0f,8.0f, 0.0f,10.0f); + outlineShape.moveTo(0.0f,-10.0f, 0f); + outlineShape.lineTo(15.0f,-10.0f, 0f); + outlineShape.quadTo(10.0f,5.0f,0f, 15.0f,10.0f,0f); + outlineShape.cubicTo(6.0f,15.0f,0f, 5.0f,8.0f,0f, 0.0f,10.0f,0f); outlineShape.closePath(); - outlineShape.moveTo(5.0f,-5.0f); - outlineShape.quadTo(10.0f,-5.0f, 10.0f,0.0f); - outlineShape.quadTo(5.0f,0.0f, 5.0f,-5.0f); + outlineShape.moveTo(5.0f,-5.0f,0f); + outlineShape.quadTo(10.0f,-5.0f,0f, 10.0f,0.0f,0f); + outlineShape.quadTo(5.0f,0.0f,0f, 5.0f,-5.0f,0f); outlineShape.closePath(); /** Same shape as above but without any off-curve vertices */ final float offset = 30; - outlineShape.moveTo(offset+0.0f,-10.0f); - outlineShape.lineTo(offset+17.0f,-10.0f); - outlineShape.lineTo(offset+11.0f,5.0f); - outlineShape.lineTo(offset+16.0f,10.0f); - outlineShape.lineTo(offset+7.0f,15.0f); - outlineShape.lineTo(offset+6.0f,8.0f); - outlineShape.lineTo(offset+0.0f,10.0f); + outlineShape.moveTo(offset+0.0f,-10.0f,0f); + outlineShape.lineTo(offset+17.0f,-10.0f,0f); + outlineShape.lineTo(offset+11.0f,5.0f,0f); + outlineShape.lineTo(offset+16.0f,10.0f,0f); + outlineShape.lineTo(offset+7.0f,15.0f,0f); + outlineShape.lineTo(offset+6.0f,8.0f,0f); + outlineShape.lineTo(offset+0.0f,10.0f,0f); outlineShape.closePath(); - outlineShape.moveTo(offset+5.0f,0.0f); - outlineShape.lineTo(offset+5.0f,-5.0f); - outlineShape.lineTo(offset+10.0f,-5.0f); - outlineShape.lineTo(offset+10.0f,0.0f); + outlineShape.moveTo(offset+5.0f,0.0f,0f); + outlineShape.lineTo(offset+5.0f,-5.0f,0f); + outlineShape.lineTo(offset+10.0f,-5.0f,0f); + outlineShape.lineTo(offset+10.0f,0.0f,0f); outlineShape.closePath(); } diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextGLListener0A.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextGLListener0A.java index bf0a907b0..5b7044fee 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextGLListener0A.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextGLListener0A.java @@ -39,14 +39,11 @@ import com.jogamp.newt.opengl.GLWindow; public class GPUTextGLListener0A extends GPUTextRendererListenerBase01 { - public GPUTextGLListener0A() { - super(RenderState.createRenderState(SVertex.factory()), Region.VBAA_RENDERING_BIT, 4, true, false, false); - } - public GPUTextGLListener0A(final RenderState rs, final int renderModes, final int sampleCount, final boolean blending, final boolean debug, final boolean trace) { super(rs, renderModes, sampleCount, blending, debug, trace); } + @Override public void init(final GLAutoDrawable drawable) { if(drawable instanceof GLWindow) { final GLWindow glw = (GLWindow) drawable; @@ -64,6 +61,7 @@ public class GPUTextGLListener0A extends GPUTextRendererListenerBase01 { rs.setColorStatic(0.1f, 0.1f, 0.1f, 1.0f); } + @Override public void dispose(final GLAutoDrawable drawable) { if(drawable instanceof GLWindow) { final GLWindow glw = (GLWindow) drawable; diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextNewtDemo.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextNewtDemo.java index ade84b3a3..59956b435 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextNewtDemo.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextNewtDemo.java @@ -148,7 +148,7 @@ public class GPUTextNewtDemo { // FPSAnimator animator = new FPSAnimator(60); final Animator animator = new Animator(); - animator.setUpdateFPSFrames(60, System.err); + // animator.setUpdateFPSFrames(60, System.err); animator.add(window); window.addKeyListener(new KeyAdapter() { diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextRendererListenerBase01.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextRendererListenerBase01.java index abf7151d0..7b1fe6177 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextRendererListenerBase01.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextRendererListenerBase01.java @@ -34,7 +34,6 @@ import com.jogamp.opengl.GL2ES2; import com.jogamp.opengl.GLAnimatorControl; import com.jogamp.opengl.GLAutoDrawable; import com.jogamp.opengl.GLException; -import com.jogamp.opengl.GLRunnable; import com.jogamp.opengl.fixedfunc.GLMatrixFunc; import com.jogamp.common.util.InterruptSource; import com.jogamp.graph.curve.Region; @@ -46,7 +45,6 @@ import com.jogamp.graph.font.Font; import com.jogamp.graph.font.FontFactory; import com.jogamp.graph.font.FontScale; import com.jogamp.graph.font.FontSet; -import com.jogamp.graph.font.Font.Glyph; import com.jogamp.newt.Window; import com.jogamp.newt.event.KeyEvent; import com.jogamp.newt.event.KeyListener; @@ -114,21 +112,21 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB "in lorem. Maecenas in ipsum ac justo scelerisque sollicitudin. Quisque sit amet neque lorem, \n" + "-------Press H to change text---------"; - public static final String textX2 = // Kvæven -> Kvaven (error) + public static final String textX2 = "I “Ask Jeff” or ‘Ask Jeff’. Take the chef d’œuvre! Two of [of] (of) ‘of’ “of” of? of! of*. X\n"+ "Les Woëvres, the Fôret de Wœvres, the Voire and Vauvise. Yves is in heaven; D’Amboise is in jail. X\n"+ "Lyford’s in Texas & L’Anse-aux-Griffons in Québec; the Łyna in Poland. Yriarte, Yciar and Ysaÿe are at Yale. X\n"+ - "Kyoto and Ryotsu are both in Japan, Kwikpak on the Yukon delta, Kvaven in Norway, Kyulu in Kenya, not in Rwanda.… X\n"+ + "Kyoto and Ryotsu are both in Japan, Kwikpak on the Yukon delta, Kvæven in Norway, Kyulu in Kenya, not in Rwanda.… X\n"+ "Von-Vincke-Straße in Münster, Vdovino in Russia, Ytterbium in the periodic table. Are Toussaint L’Ouverture, Wölfflin, Wolfe, X\n"+ "Miłosz and Wū Wŭ all in the library? 1510–1620, 11:00 pm, and the 1980s are over. X\n"+ "-------Press H to change text---------"; - public static final String textX20 = // Kvæven -> Kvaven (error) + public static final String textX20 = "I “Ask Jeff” or ‘Ask Jeff’. Take the chef d’œuvre! Two of [of] (of) ‘of’ “of” of? of! of*.\n"+ "Two of [of] (of) ‘of’ “of” of? of! of*. Ydes, Yffignac and Ygrande are in France: so are Ypres,\n"+ "Les Woëvres, the Fôret de Wœvres, the Voire and Vauvise. Yves is in heaven; D’Amboise is in jail.\n"+ "Lyford’s in Texas & L’Anse-aux-Griffons in Québec; the Łyna in Poland. Yriarte, Yciar and Ysaÿe are at Yale.\n"+ - "Kyoto and Ryotsu are both in Japan, Kwikpak on the Yukon delta, Kvaven in Norway, Kyulu in Kenya, not in Rwanda.…\n"+ + "Kyoto and Ryotsu are both in Japan, Kwikpak on the Yukon delta, Kvæven in Norway, Kyulu in Kenya, not in Rwanda.…\n"+ "Walton’s in West Virginia, but «Wren» is in Oregon. Tlálpan is near Xochimilco in México.\n"+ "The Zygos & Xylophagou are in Cyprus, Zwettl in Austria, Fænø in Denmark, the Vøringsfossen and Værøy in Norway.\n"+ "Tchula is in Mississippi, the Tittabawassee in Michigan. Twodot is here in Montana, Ywamun in Burma.\n"+ @@ -154,15 +152,7 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB this.regionFPS = GLRegion.create(renderModes, null); this.regionHead = GLRegion.create(renderModes, null); this.regionBottom = GLRegion.create(renderModes, null); - try { - // this.font = FontFactory.get(fontSet).getDefault(); - this.font = FontFactory.get(fontSet).get(FontSet.FAMILY_LIGHT, FontSet.STYLE_NONE); - - this.fontName = font.toString(); - } catch (final IOException ioe) { - System.err.println("Caught: "+ioe.getMessage()); - ioe.printStackTrace(); - } + setFontSet(fontSet, FontSet.FAMILY_LIGHT, FontSet.STYLE_NONE); setMatrix(0, 0, 0, 0f, sampleCount); } @@ -206,6 +196,7 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB } final int w2 = Math.max(window.getSurfaceWidth(), w); final int h2 = Math.max(window.getSurfaceHeight(), h); + System.err.println("upsizeWindowSurface: "+window.getSurfaceWidth()+"x"+window.getSurfaceHeight()+" -> "+w+"x"+h+" -> "+w2+"x"+h2); if( off_thread ) { new InterruptSource.Thread() { @Override @@ -231,9 +222,17 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB } else { System.err.println("Using vertical default DPI of "+dpiV+", "+ppmmV+" pixel/mm"); } - fontNameBox = font.getMetricBounds(fontName); + fontNameBox = font.getGlyphBounds(fontName); setHeadBox(headType, true); - + { + final float pixelSizeFName = FontScale.toPixels(fontSizeFName, dpiV); + System.err.println("XXX: fontName size "+fontSizeFName+"pt, dpiV "+dpiV+" -> "+pixelSizeFName+"px"); + System.err.println("XXX: fontName boxM fu "+font.getMetricBoundsFU(fontName)); + System.err.println("XXX: fontName boxG fu "+font.getGlyphBoundsFU(fontName)); + System.err.println("XXX: fontName boxM em "+font.getMetricBounds(fontName)); + System.err.println("XXX: fontName boxG em "+font.getGlyphBounds(fontName)); + System.err.println("XXX: fontName box height px "+(fontNameBox.getHeight() * pixelSizeFName)); + } } @Override @@ -299,6 +298,7 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB renderer.enable(gl, true); if( drawFPS ) { + pmv.glPushMatrix(); final float pixelSizeFPS = FontScale.toPixels(fontSizeFPS, dpiV); final float lfps, tfps, td; final GLAnimatorControl animator = drawable.getAnimator(); @@ -327,29 +327,30 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB pmv.glScalef(sxy, sxy, 1.0f); } // No cache, keep region alive! - TextRegionUtil.drawString3D(gl, regionFPS, renderer, font, text, null, sampleCountFPS, textRegionUtil.tempT1, - textRegionUtil.tempT2); + TextRegionUtil.drawString3D(gl, regionFPS, renderer, font, text, null, sampleCountFPS); + pmv.glPopMatrix(); } - float dx = width - ( fontNameBox.getWidth() - font.getAdvanceWidth(Glyph.ID_SPACE) ) * pixelSizeFName; + // float dx = width - ( fontNameBox.getWidth() + font.getAdvanceWidth( Glyph.ID_SPACE ) ) * pixelSizeFName; + float dx = width - ( fontNameBox.getWidth() + 2 * font.getAdvanceWidth( font.getGlyphID('X') ) ) * pixelSizeFName; float dy = height - fontNameBox.getHeight() * pixelSizeFName; - - pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); - pmv.glLoadIdentity(); - pmv.glTranslatef(nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy), nearPlaneZ0); { - final float sxy = nearPlaneS * pixelSizeFName; - pmv.glScalef(sxy, sxy, 1.0f); + pmv.glPushMatrix(); + pmv.glTranslatef(nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy), nearPlaneZ0); + { + final float sxy = nearPlaneS * pixelSizeFName; + pmv.glScalef(sxy, sxy, 1.0f); + } + // System.err.printf("FontN: [%f %f] -> [%f %f]%n", dx, dy, nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy)); + textRegionUtil.drawString3D(gl, renderer, font, fontName, null, getSampleCount()); + pmv.glPopMatrix(); } - // System.err.printf("FontN: [%f %f] -> [%f %f]%n", dx, dy, nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy)); - textRegionUtil.drawString3D(gl, renderer, font, fontName, null, getSampleCount()); dx = 10f; dy += -fontNameBox.getHeight() * pixelSizeFName - 10f; if(null != headtext) { - pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); - pmv.glLoadIdentity(); + pmv.glPushMatrix(); // System.err.printf("Head: [%f %f] -> [%f %f]%n", dx, dy, nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy)); pmv.glTranslatef(nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy), nearPlaneZ0); { @@ -358,39 +359,40 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB } // pmv.glTranslatef(x0, y1, z0); textRegionUtil.drawString3D(gl, renderer, font, headtext, null, getSampleCount()); + pmv.glPopMatrix(); } dy += ( -headbox.getHeight() - font.getLineHeight() ) * pixelSizeCenter; - pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); - pmv.glLoadIdentity(); - pmv.glTranslatef(nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy), nearPlaneZ0); - // System.err.printf("Bottom: [%f %f] -> [%f %f]%n", dx, dy, nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy)); - pmv.glTranslatef(getXTran(), getYTran(), getZTran()); - pmv.glRotatef(getAngle(), 0, 1, 0); { - final float sxy = nearPlaneS * pixelSizeCenter; - pmv.glScalef(sxy, sxy, 1.0f); - } - rs.setColorStatic(0.9f, 0.0f, 0.0f, 1.0f); + pmv.glPushMatrix(); + pmv.glTranslatef(nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy), nearPlaneZ0); + // System.err.printf("Bottom: [%f %f] -> [%f %f]%n", dx, dy, nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy)); + pmv.glTranslatef(getXTran(), getYTran(), getZTran()); + pmv.glRotatef(getAngle(), 0, 1, 0); + { + final float sxy = nearPlaneS * pixelSizeCenter; + pmv.glScalef(sxy, sxy, 1.0f); + } + rs.setColorStatic(0.9f, 0.0f, 0.0f, 1.0f); - if( bottomTextUseFrustum ) { - regionBottom.setFrustum(pmv.glGetFrustum()); - } - if(!userInput) { if( bottomTextUseFrustum ) { - TextRegionUtil.drawString3D(gl, regionBottom, renderer, font, text2, null, getSampleCount(), textRegionUtil.tempT1, - textRegionUtil.tempT2); - } else { - textRegionUtil.drawString3D(gl, renderer, font, text2, null, getSampleCount()); + regionBottom.setFrustum(pmv.glGetFrustum()); } - } else { - if( bottomTextUseFrustum ) { - TextRegionUtil.drawString3D(gl, regionBottom, renderer, font, userString.toString(), null, getSampleCount(), textRegionUtil.tempT1, - textRegionUtil.tempT2); + if(!userInput) { + if( bottomTextUseFrustum ) { + TextRegionUtil.drawString3D(gl, regionBottom, renderer, font, text2, null, getSampleCount()); + } else { + textRegionUtil.drawString3D(gl, renderer, font, text2, null, getSampleCount()); + } } else { - textRegionUtil.drawString3D(gl, renderer, font, userString.toString(), null, getSampleCount()); + if( bottomTextUseFrustum ) { + TextRegionUtil.drawString3D(gl, regionBottom, renderer, font, userString.toString(), null, getSampleCount()); + } else { + textRegionUtil.drawString3D(gl, renderer, font, userString.toString(), null, getSampleCount()); + } } + pmv.glPopMatrix(); } renderer.enable(gl, false); } @@ -444,6 +446,17 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB return false; } + public boolean setFont(final Font _font) { + if(null != _font) { + // fontSet = ??? + font = _font; + fontName = font.getFullFamilyName()+" (head "+fontSizeHead+"pt)"; + fontNameBox = font.getMetricBounds(fontName); + return true; + } + return false; + } + public boolean isUserInputMode() { return userInput; } void dumpMatrix(final boolean bbox) { diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/CrossHair.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/CrossHair.java index a7c0bf84b..0c8595c70 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/CrossHair.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/CrossHair.java @@ -35,7 +35,7 @@ import com.jogamp.graph.geom.Vertex; import com.jogamp.graph.geom.Vertex.Factory; /** - * GPU based resolution independent Button impl + * GPU based resolution independent Crosshair */ public class CrossHair extends UIShape { private float width, height, lineWidth; @@ -70,31 +70,29 @@ public class CrossHair extends UIShape { protected void addShapeToRegion(final GL2ES2 gl, final RegionRenderer renderer) { final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory()); + final float lwh = lineWidth/2f; + final float tw = getWidth(); final float th = getHeight(); final float twh = tw/2f; final float thh = th/2f; - final float lwh = lineWidth/2f; final float ctrX = 0f, ctrY = 0f; - float ctrZ = 0f; - - // vertical (CCW!) - shape.addVertex(ctrX-lwh, ctrY-thh, ctrZ, true); - shape.addVertex(ctrX+lwh, ctrY-thh, ctrZ, true); - shape.addVertex(ctrX+lwh, ctrY+thh, ctrZ, true); - shape.addVertex(ctrX-lwh, ctrY+thh, ctrZ, true); - shape.closeLastOutline(true); - - ctrZ -= 0.05f; - - // horizontal (CCW!) - shape.addEmptyOutline(); - shape.addVertex(ctrX-twh, ctrY-lwh, ctrZ, true); - shape.addVertex(ctrX+twh, ctrY-lwh, ctrZ, true); - shape.addVertex(ctrX+twh, ctrY+lwh, ctrZ, true); - shape.addVertex(ctrX-twh, ctrY+lwh, ctrZ, true); - shape.closeLastOutline(true); + final float ctrZ = 0f; + + // middle vertical (CCW!) + shape.moveTo(ctrX-lwh, ctrY-thh, ctrZ); + shape.lineTo(ctrX+lwh, ctrY-thh, ctrZ); + shape.lineTo(ctrX+lwh, ctrY+thh, ctrZ); + shape.lineTo(ctrX-lwh, ctrY+thh, ctrZ); + shape.closePath(); + + // middle horizontal (CCW!) + shape.moveTo(ctrX-twh, ctrY-lwh, ctrZ); + shape.lineTo(ctrX+twh, ctrY-lwh, ctrZ); + shape.lineTo(ctrX+twh, ctrY+lwh, ctrZ); + shape.lineTo(ctrX-twh, ctrY+lwh, ctrZ); + shape.closePath(); shape.setIsQuadraticNurbs(); shape.setSharpness(shapesSharpness); diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/Label.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/Label.java index eb86aba81..749ac7965 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/Label.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/Label.java @@ -30,7 +30,6 @@ package com.jogamp.opengl.test.junit.graph.demos.ui; import com.jogamp.opengl.GL2ES2; import com.jogamp.graph.curve.OutlineShape; import com.jogamp.graph.curve.opengl.RegionRenderer; -import com.jogamp.graph.curve.opengl.TextRegionUtil; import com.jogamp.graph.font.Font; import com.jogamp.graph.geom.Vertex; import com.jogamp.graph.geom.Vertex.Factory; @@ -91,7 +90,7 @@ public class Label extends UIShape { private final AffineTransform tempT1 = new AffineTransform(); private final AffineTransform tempT2 = new AffineTransform(); - private final TextRegionUtil.ShapeVisitor shapeVisitor = new TextRegionUtil.ShapeVisitor() { + private final OutlineShape.Visitor shapeVisitor = new OutlineShape.Visitor() { @Override public void visit(final OutlineShape shape, final AffineTransform t) { shape.setSharpness(shapesSharpness); @@ -104,7 +103,7 @@ public class Label extends UIShape { protected void addShapeToRegion(final GL2ES2 gl, final RegionRenderer renderer) { final AffineTransform t_sxy = new AffineTransform(); t_sxy.setToScale(pixelSize, pixelSize); - TextRegionUtil.processString(shapeVisitor, t_sxy, font, text, tempT1, tempT2); + font.processString(shapeVisitor, t_sxy, text, tempT1, tempT2); final float[] ctr = box.getCenter(); setRotationOrigin( ctr[0], ctr[1], ctr[2]); } diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/Label0.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/Label0.java index c43714b31..5cf0f9af5 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/Label0.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/Label0.java @@ -27,7 +27,6 @@ */ package com.jogamp.opengl.test.junit.graph.demos.ui; -import com.jogamp.graph.curve.OutlineShape; import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.TextRegionUtil; import com.jogamp.graph.font.Font; @@ -38,13 +37,11 @@ public class Label0 { protected Font font; protected String text; protected final float[] rgbaColor; - protected final AABBox box; public Label0(final Font font, final String text, final float[] rgbaColor) { this.font = font; this.text = text; this.rgbaColor = rgbaColor; - this.box = new AABBox(); } public final String getText() { return text; } @@ -68,31 +65,10 @@ public class Label0 { this.font = font; } - public final AABBox getBounds() { return box; } - - private final float[] tmpV3 = new float[3]; - private final AffineTransform tempT1 = new AffineTransform(); - private final AffineTransform tempT2 = new AffineTransform(); - - private final TextRegionUtil.ShapeVisitor shapeVisitor = new TextRegionUtil.ShapeVisitor() { - @Override - public void visit(final OutlineShape shape, final AffineTransform t) { - region.addOutlineShape(shape, t, rgbaColor); - box.resize(shape.getBounds(), t, tmpV3); - } - }; - - private Region region; - - public final AABBox addShapeToRegion(final float pixelSize, final Region region, final AffineTransform tLeft) { - box.reset(); - this.region = region; + public final AABBox addShapeToRegion(final float scale, final Region region, final AffineTransform tLeft) { final AffineTransform t_sxy = new AffineTransform(tLeft); - final AffineTransform tmp = new AffineTransform(); - t_sxy.scale(pixelSize, pixelSize, tmp); - TextRegionUtil.processString(shapeVisitor, t_sxy, font, text, tempT1, tempT2); - this.region = null; - return box; + t_sxy.scale(scale, scale, new AffineTransform()); + return TextRegionUtil.addStringToRegion(region, font, t_sxy, text, rgbaColor); } @Override diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/LabelButton.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/LabelButton.java index db1ec239a..59514375c 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/LabelButton.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/LabelButton.java @@ -47,7 +47,7 @@ public class LabelButton extends RoundButton { /** {@value} */ public static final float DEFAULT_SPACING_Y = 0.40f; - private static final float DEFAULT_2PASS_LABEL_ZOFFSET = -0.05f; + private static final float DEFAULT_2PASS_LABEL_ZOFFSET = -0.005f; // -0.05f; private final Label0 label; private float spacingX = DEFAULT_SPACING_X; @@ -64,6 +64,8 @@ public class LabelButton extends RoundButton { setToggleOnColorMod(0.85f, 0.85f, 0.85f, 1.0f); } + public Font getFont() { return label.font; } + @Override public void drawShape(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { if( false ) { @@ -91,35 +93,33 @@ public class LabelButton extends RoundButton { box.resize(shape.getBounds()); // Precompute text-box size .. guessing pixelSize - final float lPixelSize0 = 10f; final float lw = width * ( 1f - spacingX ) ; final float lh = height * ( 1f - spacingY ) ; - final AABBox lbox0_em = label.font.getMetricBounds(label.text); + final AABBox lbox0_em = label.font.getGlyphBounds(label.text); final float lsx = lw / lbox0_em.getWidth(); final float lsy = lh / lbox0_em.getHeight(); - final float lPixelSize1 = lsx < lsy ? lsx : lsy; - if( DRAW_DEBUG_BOX ) { - final AABBox lbox0_px = new AABBox(lbox0_em).scale2(lPixelSize0, new float[3]); - System.err.println("RIButton: dim "+width+" x "+height+", spacing "+spacingX+", "+spacingY); - System.err.println("RIButton: net-text "+lw+" x "+lh+" px"); - System.err.println("RIButton: shape "+box+" px"); - System.err.println("RIButton: text "+lbox0_em+" em, "+label.text); - System.err.println("RIButton: text "+lbox0_px+" px, "+label.text); - System.err.println("RIButton: lscale "+lsx+" x "+lsy+": pixelSize "+lPixelSize0+" -> "+lPixelSize1); - } + final float lScale = lsx < lsy ? lsx : lsy; // Setting left-corner transform using text-box in font em-size [0..1] - final AABBox lbox1_s = label.font.getPointsBounds(null, label.text).scale2(lPixelSize1, new float[3]); + final AABBox lbox1_s = new AABBox(lbox0_em).scale2(lScale, new float[3]); // Center text .. (share same center w/ button) final float[] lctr = lbox1_s.getCenter(); final float[] ctr = box.getCenter(); final float[] ltx = new float[] { ctr[0] - lctr[0], ctr[1] - lctr[1], 0f }; - final AABBox lbox2 = label.addShapeToRegion(lPixelSize1, region, tempT1.setToTranslation(ltx[0], ltx[1])); if( DRAW_DEBUG_BOX ) { - System.err.printf("RIButton.0: tleft %f / %f, %f / %f%n", ltx[0], ltx[1], ltx[0] * lPixelSize1, ltx[1] * lPixelSize1); - System.err.printf("RIButton.0: lbox1 %s scaled%n", lbox1_s); - System.err.printf("RIButton.0: lbox2 %s%n", lbox2); + System.err.println("RIButton: dim "+width+" x "+height+", spacing "+spacingX+", "+spacingY); + System.err.println("RIButton: net-text "+lw+" x "+lh); + System.err.println("RIButton: shape "+box); + System.err.println("RIButton: text_em "+lbox0_em+" em, "+label.text); + System.err.println("RIButton: lscale "+lsx+" x "+lsy+" -> "+lScale); + System.err.printf ("RIButton: text_s %s%n", lbox1_s); + System.err.printf ("RIButton: tleft %f / %f, %f / %f%n", ltx[0], ltx[1], ltx[0] * lScale, ltx[1] * lScale); + } + + final AABBox lbox2 = label.addShapeToRegion(lScale, region, tempT1.setToTranslation(ltx[0], ltx[1])); + if( DRAW_DEBUG_BOX ) { + System.err.printf("RIButton.X: lbox2 %s%n", lbox2); } setRotationOrigin( ctr[0], ctr[1], ctr[2]); diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/Rectangle.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/Rectangle.java new file mode 100644 index 000000000..aa9400050 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/Rectangle.java @@ -0,0 +1,113 @@ +/** + * Copyright 2010-2023 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.graph.demos.ui; + +import com.jogamp.opengl.GL2ES2; + +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; + +/** + * GPU based resolution independent Rectangle + */ +public class Rectangle extends UIShape { + private float width, height, lineWidth; + + public Rectangle(final Factory<? extends Vertex> factory, final int renderModes, final float width, final float height, final float linewidth) { + super(factory, renderModes); + this.width = width; + this.height = height; + this.lineWidth = linewidth; + } + + public final float getWidth() { return width; } + public final float getHeight() { return height; } + public final float getLineWidth() { return lineWidth; } + + public void setDimension(final float width, final float height, final float lineWidth) { + this.width = width; + this.height = height; + this.lineWidth = lineWidth; + markShapeDirty(); + } + + @Override + protected void clearImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @Override + protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @Override + protected void addShapeToRegion(final GL2ES2 gl, final RegionRenderer renderer) { + final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory()); + + final float lwh = lineWidth/2f; + + final float tw = getWidth(); + final float th = getHeight(); + + final float twh = tw/2f; + final float twh_o = twh+lwh; + final float twh_i = twh-lwh; + final float thh = th/2f; + final float thh_o = thh+lwh; + final float thh_i = thh-lwh; + + final float ctrX = 0f, ctrY = 0f; + final float ctrZ = 0f; + + // outer (CCW!) + shape.moveTo(ctrX-twh_o, ctrY-thh_o, ctrZ); + shape.lineTo(ctrX+twh_o, ctrY-thh_o, ctrZ); + shape.lineTo(ctrX+twh_o, ctrY+thh_o, ctrZ); + shape.lineTo(ctrX-twh_o, ctrY+thh_o, ctrZ); + shape.closePath(); + + // inner (CCW!) + shape.moveTo(ctrX-twh_i, ctrY-thh_i, ctrZ); + shape.lineTo(ctrX+twh_i, ctrY-thh_i, ctrZ); + shape.lineTo(ctrX+twh_i, ctrY+thh_i, ctrZ); + shape.lineTo(ctrX-twh_i, ctrY+thh_i, ctrZ); + shape.closePath(); + + shape.setIsQuadraticNurbs(); + shape.setSharpness(shapesSharpness); + region.addOutlineShape(shape, null, rgbaColor); + + box.resize(shape.getBounds()); + } + + @Override + public String getSubString() { + return super.getSubString()+", dim "+getWidth() + "x" + getHeight(); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/SceneUIController.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/SceneUIController.java index 98ddc1db2..9de206cac 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/SceneUIController.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/SceneUIController.java @@ -20,9 +20,7 @@ import com.jogamp.newt.event.PinchToZoomGesture; import com.jogamp.newt.event.GestureHandler.GestureEvent; import com.jogamp.newt.opengl.GLWindow; import com.jogamp.opengl.math.FloatUtil; -import com.jogamp.opengl.math.Quaternion; import com.jogamp.opengl.math.Ray; -import com.jogamp.opengl.math.VectorUtil; import com.jogamp.opengl.math.geom.AABBox; import com.jogamp.opengl.util.PMVMatrix; @@ -130,35 +128,6 @@ public class SceneUIController implements GLEventListener{ cDrawable = drawable; } - private void transformShape(final PMVMatrix pmv, final UIShape uiShape) { - final float[] uiTranslate = uiShape.getTranslate(); - pmv.glTranslatef(uiTranslate[0], uiTranslate[1], uiTranslate[2]); - // final float dz = 100f; - - final Quaternion quat = uiShape.getRotation(); - final boolean rotate = !quat.isIdentity(); - final float[] uiScale = uiShape.getScale(); - final boolean scale = !VectorUtil.isVec3Equal(uiScale, 0, VectorUtil.VEC3_ONE, 0, FloatUtil.EPSILON); - if( rotate || scale ) { - final float[] rotOrigin = uiShape.getRotationOrigin(); - final boolean pivot = !VectorUtil.isVec3Zero(rotOrigin, 0, FloatUtil.EPSILON); - // pmv.glTranslatef(0f, 0f, dz); - if( pivot ) { - pmv.glTranslatef(rotOrigin[0], rotOrigin[1], rotOrigin[2]); - } - if( scale ) { - pmv.glScalef(uiScale[0], uiScale[1], uiScale[2]); - } - if( rotate ) { - pmv.glRotate(quat); - } - if( pivot ) { - pmv.glTranslatef(-rotOrigin[0], -rotOrigin[1], -rotOrigin[2]); - } - // pmv.glTranslatef(0f, 0f, -dz); - } - } - private static Comparator<UIShape> shapeZAscComparator = new Comparator<UIShape>() { @Override public int compare(final UIShape s1, final UIShape s2) { @@ -198,7 +167,7 @@ public class SceneUIController implements GLEventListener{ if( uiShape.isEnabled() ) { uiShape.validate(gl, renderer); pmv.glPushMatrix(); - transformShape(pmv, uiShape); + uiShape.setTransform(pmv); uiShape.drawShape(gl, renderer, sampleCount); pmv.glPopMatrix(); } @@ -243,7 +212,7 @@ public class SceneUIController implements GLEventListener{ if( uiShape.isEnabled() ) { pmv.glPushMatrix(); - transformShape(pmv, uiShape); + uiShape.setTransform(pmv); final boolean ok = pmv.gluUnProjectRay(glWinX, glWinY, winZ0, winZ1, viewport, 0, ray); pmv.glPopMatrix(); if( ok ) { @@ -265,6 +234,14 @@ public class SceneUIController implements GLEventListener{ private final float[] dpyTmp2V3 = new float[3]; private final float[] dpyTmp3V3 = new float[3]; + /** + * Calling {@link UIShape#winToObjCoord(RegionRenderer, int, int, float[])}, retrieving its object position. + * @param activeShape + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param objPos resulting object position + * @param runnable action + */ public void windowToShapeCoords(final UIShape activeShape, final int glWinX, final int glWinY, final float[] objPos, final Runnable runnable) { if( null == cDrawable || null == activeShape ) { return; @@ -272,30 +249,12 @@ public class SceneUIController implements GLEventListener{ cDrawable.invoke(false, new GLRunnable() { @Override public boolean run(final GLAutoDrawable drawable) { - if( windowToShapeCoordsImpl(activeShape, glWinX, glWinY, objPos) ) { + if( activeShape.winToObjCoord(renderer, glWinX, glWinY, objPos) ) { runnable.run(); } return true; } } ); } - private boolean windowToShapeCoordsImpl(final UIShape activeShape, final int glWinX, final int glWinY, final float[] objPos) { - final PMVMatrix pmv = renderer.getMatrix(); - pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); - - pmv.glPushMatrix(); - transformShape(pmv, activeShape); - boolean res = false; - final float[] ctr = activeShape.getBounds().getCenter(); - if( pmv.gluProject(ctr[0], ctr[1], ctr[2], viewport, 0, dpyTmp1V3, 0) ) { - // System.err.printf("winToShapeCoords.0: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", shapeId, ctr[0], ctr[1], ctr[2], dpyTmp1V3[0], dpyTmp1V3[1], dpyTmp1V3[2]); - if( pmv.gluUnProject(glWinX, glWinY, dpyTmp1V3[2], viewport, 0, objPos, 0) ) { - // System.err.printf("winToShapeCoords.1: shape %d: win [%d, %d, %f] -> obj [%f, %f, %f]%n", shapeId, glWinX, glWinY, dpyTmp1V3[2], objPos[0], objPos[1], objPos[2]); - res = true; - } - } - pmv.glPopMatrix(); - return res; - } @Override public void dispose(final GLAutoDrawable drawable) { @@ -397,6 +356,7 @@ public class SceneUIController implements GLEventListener{ final float[] objPos = new float[3]; final UIShape shape = activeShape; windowToShapeCoords(shape, glWinX, glWinY, objPos, new Runnable() { + @Override public void run() { shape.dispatchGestureEvent(gh, glWinX, glWinY, objPos); } } ); @@ -405,6 +365,12 @@ public class SceneUIController implements GLEventListener{ } } + /** + * Dispatch mouse event, either directly sending to activeShape or picking one + * @param e original Newt {@link MouseEvent} + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + */ final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY) { if( null == activeShape ) { dispatchMouseEventPickShape(e, glWinX, glWinY, true); @@ -412,20 +378,36 @@ public class SceneUIController implements GLEventListener{ dispatchMouseEventForShape(activeShape, e, glWinX, glWinY); } } + /** + * Pick the shape using the event coordinates + * @param e original Newt {@link MouseEvent} + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param setActive + */ final void dispatchMouseEventPickShape(final MouseEvent e, final int glWinX, final int glWinY, final boolean setActive) { final float[] objPos = new float[3]; final UIShape[] shape = { null }; pickShape(glWinX, glWinY, objPos, shape, new Runnable() { - public void run() { + @Override + public void run() { if( setActive ) { setActiveShape(shape[0]); } shape[0].dispatchMouseEvent(e, glWinX, glWinY, objPos); } } ); } + /** + * Dispatch event to shape + * @param shape target active shape of event + * @param e original Newt {@link MouseEvent} + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + */ final void dispatchMouseEventForShape(final UIShape shape, final MouseEvent e, final int glWinX, final int glWinY) { final float[] objPos = new float[3]; windowToShapeCoords(shape, glWinX, glWinY, objPos, new Runnable() { + @Override public void run() { shape.dispatchMouseEvent(e, glWinX, glWinY, objPos); } } ); @@ -445,7 +427,7 @@ public class SceneUIController implements GLEventListener{ ly = e.getY(); lId = e.getPointerId(0); } - // flip to GL window coordinates + // flip to GL window coordinates, origin bottom-left final int glWinX = e.getX(); final int glWinY = viewport[3] - e.getY() - 1; dispatchMouseEvent(e, glWinX, glWinY); @@ -453,7 +435,7 @@ public class SceneUIController implements GLEventListener{ @Override public void mouseReleased(final MouseEvent e) { - // flip to GL window coordinates + // flip to GL window coordinates, origin bottom-left final int glWinX = e.getX(); final int glWinY = viewport[3] - e.getY() - 1; dispatchMouseEvent(e, glWinX, glWinY); @@ -484,7 +466,7 @@ public class SceneUIController implements GLEventListener{ ly = e.getY(); // dragged .. delegate to active shape! - // flip to GL window coordinates + // flip to GL window coordinates, origin bottom-left final int glWinX = lx; final int glWinY = viewport[3] - ly - 1; dispatchMouseEventForShape(activeShape, e, glWinX, glWinY); diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/TestObject01.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/TestObject01.java new file mode 100644 index 000000000..d2bff1fee --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/TestObject01.java @@ -0,0 +1,314 @@ +/** + * 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.test.junit.graph.demos.ui; + +import com.jogamp.opengl.GL2ES2; + +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.graph.geom.plane.Winding; + +/** + * GPU based resolution independent test object + */ +public class TestObject01 extends UIShape { + + public TestObject01(final Factory<? extends Vertex> factory, final int renderModes) { + super(factory, renderModes); + } + + @Override + protected void clearImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @Override + protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @SuppressWarnings("unused") + @Override + protected void addShapeToRegion(final GL2ES2 gl, final RegionRenderer renderer) { + final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory()); + + // lower case 'o' + // Start TTF Shape for Glyph 82 + if( false ) { + // Original Outer shape: Winding.CW + // Moved into OutlineShape reverse -> Winding.CCW -> OK + // + // Shape.MoveTo: + shape.closeLastOutline(false); + shape.addEmptyOutline(); + shape.addVertex(0, 0.527000f, 0.258000f, true); + // 000: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.527000f, 0.197000f, false); + shape.addVertex(0, 0.510000f, 0.147000f, true); + // 002: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.492000f, 0.097000f, false); + shape.addVertex(0, 0.461000f, 0.062000f, true); + // 003: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.429000f, 0.027000f, false); + shape.addVertex(0, 0.386000f, 0.008000f, true); + // 004: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.343000f, -0.012000f, false); + shape.addVertex(0, 0.291000f, -0.012000f, true); + // 005: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.239000f, -0.012000f, false); + shape.addVertex(0, 0.196000f, 0.007000f, true); + // 007: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.153000f, 0.027000f, false); + shape.addVertex(0, 0.122000f, 0.062000f, true); + // 008: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.090000f, 0.097000f, false); + shape.addVertex(0, 0.073000f, 0.147000f, true); + // 009: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.055000f, 0.197000f, false); + shape.addVertex(0, 0.055000f, 0.258000f, true); + // 010: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.055000f, 0.319000f, false); + shape.addVertex(0, 0.072000f, 0.369000f, true); + // 012: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.090000f, 0.419000f, false); + shape.addVertex(0, 0.121000f, 0.454000f, true); + // 013: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.153000f, 0.490000f, false); + shape.addVertex(0, 0.196000f, 0.509000f, true); + // 014: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.239000f, 0.529000f, false); + shape.addVertex(0, 0.291000f, 0.529000f, true); + // 015: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.343000f, 0.529000f, false); + shape.addVertex(0, 0.386000f, 0.510000f, true); + // 017: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.429000f, 0.490000f, false); + shape.addVertex(0, 0.460000f, 0.455000f, true); + // 018: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.492000f, 0.419000f, false); + shape.addVertex(0, 0.509000f, 0.369000f, true); + // 019: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.527000f, 0.319000f, false); + shape.addVertex(0, 0.527000f, 0.258000f, true); + System.err.println("TestObject01.shape01a.winding_area: "+shape.getWindingOfLastOutline()); + shape.closeLastOutline(false); + } else { + // Outer shape: Winding.CW + // Moved into OutlineShape same-order -> Winding.CW -> ERROR (so we fix it in the end, see below) + // + // Shape.MoveTo: + shape.closeLastOutline(false); + shape.addEmptyOutline(); + shape.addVertex(0.527000f, 0.258000f, true); + // 000: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.527000f, 0.197000f, false); + shape.addVertex(0.510000f, 0.147000f, true); + // 002: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.492000f, 0.097000f, false); + shape.addVertex(0.461000f, 0.062000f, true); + // 003: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.429000f, 0.027000f, false); + shape.addVertex(0.386000f, 0.008000f, true); + // 004: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.343000f, -0.012000f, false); + shape.addVertex(0.291000f, -0.012000f, true); + // 005: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.239000f, -0.012000f, false); + shape.addVertex(0.196000f, 0.007000f, true); + // 007: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.153000f, 0.027000f, false); + shape.addVertex(0.122000f, 0.062000f, true); + // 008: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.090000f, 0.097000f, false); + shape.addVertex(0.073000f, 0.147000f, true); + // 009: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.055000f, 0.197000f, false); + shape.addVertex(0.055000f, 0.258000f, true); + // 010: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.055000f, 0.319000f, false); + shape.addVertex(0.072000f, 0.369000f, true); + // 012: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.090000f, 0.419000f, false); + shape.addVertex(0.121000f, 0.454000f, true); + // 013: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.153000f, 0.490000f, false); + shape.addVertex(0.196000f, 0.509000f, true); + // 014: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.239000f, 0.529000f, false); + shape.addVertex(0.291000f, 0.529000f, true); + // 015: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.343000f, 0.529000f, false); + shape.addVertex(0.386000f, 0.510000f, true); + // 017: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.429000f, 0.490000f, false); + shape.addVertex(0.460000f, 0.455000f, true); + // 018: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.492000f, 0.419000f, false); + shape.addVertex(0.509000f, 0.369000f, true); + // 019: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.527000f, 0.319000f, false); + shape.addVertex(0.527000f, 0.258000f, true); + System.err.println("TestObject01.shape01b.1.winding_area: "+shape.getWindingOfLastOutline()); + shape.setWindingOfLastOutline(Winding.CCW); + System.err.println("TestObject01.shape01b.2.winding_area: "+shape.getWindingOfLastOutline()); + shape.closeLastOutline(false); + } + + if( true ) { + // Original Inner shape: Winding.CCW + // Moved into OutlineShape reverse -> Winding.CW -> OK + // + // Shape.MoveTo: + shape.closeLastOutline(false); + shape.addEmptyOutline(); + shape.addVertex(0, 0.458000f, 0.258000f, true); + // 020: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.458000f, 0.355000f, false); + shape.addVertex(0, 0.413000f, 0.412000f, true); + // 022: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.368000f, 0.470000f, false); + shape.addVertex(0, 0.291000f, 0.470000f, true); + // 023: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.214000f, 0.470000f, false); + shape.addVertex(0, 0.169000f, 0.413000f, true); + // 025: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.124000f, 0.355000f, false); + shape.addVertex(0, 0.124000f, 0.258000f, true); + // 026: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.124000f, 0.161000f, false); + shape.addVertex(0, 0.169000f, 0.104000f, true); + // 028: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.214000f, 0.047000f, false); + shape.addVertex(0, 0.291000f, 0.047000f, true); + // 029: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.368000f, 0.047000f, false); + shape.addVertex(0, 0.413000f, 0.104000f, true); + // 031: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.458000f, 0.161000f, false); + shape.addVertex(0, 0.458000f, 0.258000f, true); + System.err.println("TestObject01.shape02a.winding_area: "+shape.getWindingOfLastOutline()); + shape.closeLastOutline(false); + } else { + // Inner shape: Winding.CCW + // Moved into OutlineShape same-order -> Winding.CCW -> OK + // + // Shape.MoveTo: + shape.closeLastOutline(false); + shape.addEmptyOutline(); + + shape.addVertex(0.458000f, 0.258000f, true); + // 020: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.458000f, 0.355000f, false); + shape.addVertex(0.413000f, 0.412000f, true); + // 022: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.368000f, 0.470000f, false); + shape.addVertex(0.291000f, 0.470000f, true); + // 023: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.214000f, 0.470000f, false); + shape.addVertex(0.169000f, 0.413000f, true); + // 025: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.124000f, 0.355000f, false); + shape.addVertex(0.124000f, 0.258000f, true); + // 026: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.124000f, 0.161000f, false); + shape.addVertex(0.169000f, 0.104000f, true); + // 028: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.214000f, 0.047000f, false); + shape.addVertex(0.291000f, 0.047000f, true); + // 029: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.368000f, 0.047000f, false); + shape.addVertex(0.413000f, 0.104000f, true); + // 031: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.458000f, 0.161000f, false); + shape.addVertex(0.458000f, 0.258000f, true); + + System.err.println("TestObject01.shape02b.winding_area: "+shape.getWindingOfLastOutline()); + shape.closeLastOutline(false); + } + // End Shape for Glyph 82 + + shape.setIsQuadraticNurbs(); + shape.setSharpness(shapesSharpness); + region.addOutlineShape(shape, null, rgbaColor); + + box.resize(shape.getBounds()); + } + + @Override + public String getSubString() { + return super.getSubString(); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/TestObject02.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/TestObject02.java new file mode 100644 index 000000000..fe1d965ee --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/TestObject02.java @@ -0,0 +1,650 @@ +/** + * 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.test.junit.graph.demos.ui; + +import com.jogamp.opengl.GL2ES2; + +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; + +/** + * GPU based resolution independent test object + */ +public class TestObject02 extends UIShape { + + public TestObject02(final Factory<? extends Vertex> factory, final int renderModes) { + super(factory, renderModes); + } + + @Override + protected void clearImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @Override + protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @SuppressWarnings("unused") + @Override + protected void addShapeToRegion(final GL2ES2 gl, final RegionRenderer renderer) { + final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory()); + + // lower case 'æ' + + // Start TTF Shape for Glyph 193 + if( true ) { + // Original Inner e-shape: Winding.CCW + // Moved into OutlineShape reverse -> Winding.CW -> OK + // + // Shape.MoveTo: + shape.closeLastOutline(false); + shape.addEmptyOutline(); + shape.addVertex(0, 0.728000f, 0.300000f, true); + // 000: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.726000f, 0.381000f, false); + shape.addVertex(0, 0.690000f, 0.426000f, true); + // 002: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.654000f, 0.471000f, false); + shape.addVertex(0, 0.588000f, 0.471000f, true); + // 003: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.553000f, 0.471000f, false); + shape.addVertex(0, 0.526000f, 0.457000f, true); + // 005: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.498000f, 0.443000f, false); + shape.addVertex(0, 0.478000f, 0.420000f, true); + // 006: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.457000f, 0.396000f, false); + shape.addVertex(0, 0.446000f, 0.365000f, true); + // 007: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.434000f, 0.334000f, false); + shape.addVertex(0, 0.432000f, 0.300000f, true); + // 008: B1: line-to p0-p1 + // Shape.LineTo: + shape.addVertex(0, 0.728000f, 0.300000f, true); + System.err.println("TestObject02.shape01a.winding_area: "+shape.getWindingOfLastOutline()); + shape.closeLastOutline(false); + } else { + // Inner e-shape: Winding.CCW + // Moved into OutlineShape same-order -> Winding.CCW -> ?? + // + // Shape.MoveTo: + shape.closeLastOutline(false); + shape.addEmptyOutline(); + shape.addVertex(0.728000f, 0.300000f, true); + // 000: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.726000f, 0.381000f, false); + shape.addVertex(0.690000f, 0.426000f, true); + // 002: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.654000f, 0.471000f, false); + shape.addVertex(0.588000f, 0.471000f, true); + // 003: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.553000f, 0.471000f, false); + shape.addVertex(0.526000f, 0.457000f, true); + // 005: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.498000f, 0.443000f, false); + shape.addVertex(0.478000f, 0.420000f, true); + // 006: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.457000f, 0.396000f, false); + shape.addVertex(0.446000f, 0.365000f, true); + // 007: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.434000f, 0.334000f, false); + shape.addVertex(0.432000f, 0.300000f, true); + // 008: B1: line-to p0-p1 + // Shape.LineTo: + shape.addVertex(0.728000f, 0.300000f, true); + System.err.println("TestObject02.shape01b.winding_area: "+shape.getWindingOfLastOutline()); + shape.closeLastOutline(false); + } + + if( true ) { + // Original Outer shape: Winding.CW + // Moved into OutlineShape reverse -> Winding.CCW -> OK + // + // Shape.MoveTo: + shape.closeLastOutline(false); + shape.addEmptyOutline(); + shape.addVertex(0, 0.252000f, -0.011000f, true); + // 009: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.208000f, -0.011000f, false); + shape.addVertex(0, 0.171000f, -0.002000f, true); + // 011: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.133000f, 0.007000f, false); + shape.addVertex(0, 0.106000f, 0.026000f, true); + // 012: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.079000f, 0.046000f, false); + shape.addVertex(0, 0.064000f, 0.076000f, true); + // 013: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.048000f, 0.107000f, false); + shape.addVertex(0, 0.048000f, 0.151000f, true); + // 014: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.048000f, 0.193000f, false); + shape.addVertex(0, 0.064000f, 0.223000f, true); + // 016: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.080000f, 0.253000f, false); + shape.addVertex(0, 0.109000f, 0.272000f, true); + // 017: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.138000f, 0.292000f, false); + shape.addVertex(0, 0.178000f, 0.301000f, true); + // 018: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.218000f, 0.310000f, false); + shape.addVertex(0, 0.265000f, 0.310000f, true); + // 019: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.279000f, 0.310000f, false); + shape.addVertex(0, 0.294000f, 0.309000f, true); + // 021: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.310000f, 0.307000f, false); + shape.addVertex(0, 0.324000f, 0.305000f, true); + // 022: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.339000f, 0.302000f, false); + shape.addVertex(0, 0.349000f, 0.300000f, true); + // 023: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.360000f, 0.297000f, false); + shape.addVertex(0, 0.364000f, 0.295000f, true); + // 024: B1: line-to p0-p1 + // Shape.LineTo: + shape.addVertex(0, 0.364000f, 0.327000f, true); + // 025: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.364000f, 0.354000f, false); + shape.addVertex(0, 0.360000f, 0.379000f, true); + // 027: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.356000f, 0.405000f, false); + shape.addVertex(0, 0.343000f, 0.425000f, true); + // 028: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.329000f, 0.446000f, false); + shape.addVertex(0, 0.305000f, 0.458000f, true); + // 029: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.280000f, 0.471000f, false); + shape.addVertex(0, 0.240000f, 0.471000f, true); + // 030: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.186000f, 0.471000f, false); + shape.addVertex(0, 0.156000f, 0.464000f, true); + // 032: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.126000f, 0.456000f, false); + shape.addVertex(0, 0.113000f, 0.451000f, true); + // 033: B1: line-to p0-p1 + // Shape.LineTo: + shape.addVertex(0, 0.105000f, 0.507000f, true); + // 034: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.122000f, 0.515000f, false); + shape.addVertex(0, 0.158000f, 0.522000f, true); + // 036: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.194000f, 0.529000f, false); + shape.addVertex(0, 0.243000f, 0.529000f, true); + // 037: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.314000f, 0.529000f, false); + shape.addVertex(0, 0.354000f, 0.503000f, true); + // 039: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.395000f, 0.476000f, false); + shape.addVertex(0, 0.412000f, 0.431000f, true); + // 040: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.445000f, 0.480000f, false); + shape.addVertex(0, 0.491000f, 0.504000f, true); + // 042: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.537000f, 0.529000f, false); + shape.addVertex(0, 0.587000f, 0.529000f, true); + // 043: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.682000f, 0.529000f, false); + shape.addVertex(0, 0.738000f, 0.467000f, true); + // 045: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.795000f, 0.405000f, false); + shape.addVertex(0, 0.795000f, 0.276000f, true); + // 046: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.795000f, 0.268000f, false); + shape.addVertex(0, 0.795000f, 0.260000f, true); + // 048: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.794000f, 0.252000f, false); + shape.addVertex(0, 0.793000f, 0.245000f, true); + // 049: B1: line-to p0-p1 + // Shape.LineTo: + shape.addVertex(0, 0.430000f, 0.245000f, true); + // 050: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.433000f, 0.150000f, false); + shape.addVertex(0, 0.477000f, 0.099000f, true); + // 052: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.521000f, 0.048000f, false); + shape.addVertex(0, 0.617000f, 0.048000f, true); + // 053: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.670000f, 0.048000f, false); + shape.addVertex(0, 0.701000f, 0.058000f, true); + // 055: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.732000f, 0.068000f, false); + shape.addVertex(0, 0.746000f, 0.075000f, true); + // 056: B1: line-to p0-p1 + // Shape.LineTo: + shape.addVertex(0, 0.758000f, 0.019000f, true); + // 057: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.744000f, 0.011000f, false); + shape.addVertex(0, 0.706000f, 0.000000f, true); + // 059: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.667000f, -0.011000f, false); + shape.addVertex(0, 0.615000f, -0.011000f, true); + // 060: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.558000f, -0.011000f, false); + shape.addVertex(0, 0.514000f, 0.003000f, true); + // 062: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.470000f, 0.017000f, false); + shape.addVertex(0, 0.437000f, 0.049000f, true); + // 063: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.426000f, 0.040000f, false); + shape.addVertex(0, 0.410000f, 0.030000f, true); + // 065: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.393000f, 0.019000f, false); + shape.addVertex(0, 0.370000f, 0.010000f, true); + // 066: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.347000f, 0.001000f, false); + shape.addVertex(0, 0.318000f, -0.005000f, true); + // 067: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.289000f, -0.011000f, false); + shape.addVertex(0, 0.252000f, -0.011000f, true); + System.err.println("TestObject02.shape02a.winding_area: "+shape.getWindingOfLastOutline()); + shape.closeLastOutline(false); + } else { + // Outer shape: Winding.CW + // Moved into OutlineShape same-order -> Winding.CW -> OK now + // + // Shape.MoveTo: + shape.closeLastOutline(false); + shape.addEmptyOutline(); + shape.addVertex(0.252000f, -0.011000f, true); + // 009: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.208000f, -0.011000f, false); + shape.addVertex(0.171000f, -0.002000f, true); + // 011: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.133000f, 0.007000f, false); + shape.addVertex(0.106000f, 0.026000f, true); + // 012: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.079000f, 0.046000f, false); + shape.addVertex(0.064000f, 0.076000f, true); + // 013: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.048000f, 0.107000f, false); + shape.addVertex(0.048000f, 0.151000f, true); + // 014: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.048000f, 0.193000f, false); + shape.addVertex(0.064000f, 0.223000f, true); + // 016: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.080000f, 0.253000f, false); + shape.addVertex(0.109000f, 0.272000f, true); + // 017: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.138000f, 0.292000f, false); + shape.addVertex(0.178000f, 0.301000f, true); + // 018: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.218000f, 0.310000f, false); + shape.addVertex(0.265000f, 0.310000f, true); + // 019: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.279000f, 0.310000f, false); + shape.addVertex(0.294000f, 0.309000f, true); + // 021: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.310000f, 0.307000f, false); + shape.addVertex(0.324000f, 0.305000f, true); + // 022: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.339000f, 0.302000f, false); + shape.addVertex(0.349000f, 0.300000f, true); + // 023: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.360000f, 0.297000f, false); + shape.addVertex(0.364000f, 0.295000f, true); + // 024: B1: line-to p0-p1 + // Shape.LineTo: + shape.addVertex(0.364000f, 0.327000f, true); + // 025: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.364000f, 0.354000f, false); + shape.addVertex(0.360000f, 0.379000f, true); + // 027: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.356000f, 0.405000f, false); + shape.addVertex(0.343000f, 0.425000f, true); + // 028: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.329000f, 0.446000f, false); + shape.addVertex(0.305000f, 0.458000f, true); + // 029: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.280000f, 0.471000f, false); + shape.addVertex(0.240000f, 0.471000f, true); + // 030: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.186000f, 0.471000f, false); + shape.addVertex(0.156000f, 0.464000f, true); + // 032: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.126000f, 0.456000f, false); + shape.addVertex(0.113000f, 0.451000f, true); + // 033: B1: line-to p0-p1 + // Shape.LineTo: + shape.addVertex(0.105000f, 0.507000f, true); + // 034: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.122000f, 0.515000f, false); + shape.addVertex(0.158000f, 0.522000f, true); + // 036: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.194000f, 0.529000f, false); + shape.addVertex(0.243000f, 0.529000f, true); + // 037: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.314000f, 0.529000f, false); + shape.addVertex(0.354000f, 0.503000f, true); + // 039: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.395000f, 0.476000f, false); + shape.addVertex(0.412000f, 0.431000f, true); + // 040: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.445000f, 0.480000f, false); + shape.addVertex(0.491000f, 0.504000f, true); + // 042: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.537000f, 0.529000f, false); + shape.addVertex(0.587000f, 0.529000f, true); + // 043: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.682000f, 0.529000f, false); + shape.addVertex(0.738000f, 0.467000f, true); + // 045: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.795000f, 0.405000f, false); + shape.addVertex(0.795000f, 0.276000f, true); + // 046: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.795000f, 0.268000f, false); + shape.addVertex(0.795000f, 0.260000f, true); + // 048: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.794000f, 0.252000f, false); + shape.addVertex(0.793000f, 0.245000f, true); + // 049: B1: line-to p0-p1 + // Shape.LineTo: + shape.addVertex(0.430000f, 0.245000f, true); + // 050: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.433000f, 0.150000f, false); + shape.addVertex(0.477000f, 0.099000f, true); + // 052: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.521000f, 0.048000f, false); + shape.addVertex(0.617000f, 0.048000f, true); + // 053: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.670000f, 0.048000f, false); + shape.addVertex(0.701000f, 0.058000f, true); + // 055: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.732000f, 0.068000f, false); + shape.addVertex(0.746000f, 0.075000f, true); + // 056: B1: line-to p0-p1 + // Shape.LineTo: + shape.addVertex(0.758000f, 0.019000f, true); + // 057: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.744000f, 0.011000f, false); + shape.addVertex(0.706000f, 0.000000f, true); + // 059: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.667000f, -0.011000f, false); + shape.addVertex(0.615000f, -0.011000f, true); + // 060: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.558000f, -0.011000f, false); + shape.addVertex(0.514000f, 0.003000f, true); + // 062: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.470000f, 0.017000f, false); + shape.addVertex(0.437000f, 0.049000f, true); + // 063: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.426000f, 0.040000f, false); + shape.addVertex(0.410000f, 0.030000f, true); + // 065: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.393000f, 0.019000f, false); + shape.addVertex(0.370000f, 0.010000f, true); + // 066: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.347000f, 0.001000f, false); + shape.addVertex(0.318000f, -0.005000f, true); + // 067: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.289000f, -0.011000f, false); + shape.addVertex(0.252000f, -0.011000f, true); + System.err.println("TestObject02.shape02b.winding_area: "+shape.getWindingOfLastOutline()); + shape.closeLastOutline(false); + } + + if( true ) { + // Original Inner a-shape: Winding.CCW + // Moved into OutlineShape reverse -> Winding.CW -> OK now + // + // Shape.MoveTo: + shape.closeLastOutline(false); + shape.addEmptyOutline(); + shape.addVertex(0, 0.365000f, 0.238000f, true); + // 068: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.354000f, 0.243000f, false); + shape.addVertex(0, 0.330000f, 0.248000f, true); + // 070: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.305000f, 0.254000f, false); + shape.addVertex(0, 0.263000f, 0.254000f, true); + // 071: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.239000f, 0.254000f, false); + shape.addVertex(0, 0.213000f, 0.251000f, true); + // 073: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.187000f, 0.247000f, false); + shape.addVertex(0, 0.165000f, 0.236000f, true); + // 074: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.143000f, 0.224000f, false); + shape.addVertex(0, 0.129000f, 0.204000f, true); + // 075: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.115000f, 0.184000f, false); + shape.addVertex(0, 0.115000f, 0.151000f, true); + // 076: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.115000f, 0.122000f, false); + shape.addVertex(0, 0.125000f, 0.102000f, true); + // 078: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.135000f, 0.082000f, false); + shape.addVertex(0, 0.153000f, 0.070000f, true); + // 079: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0, 0.172000f, 0.058000f, false); + shape.addVertex(0, 0.197000f, 0.053000f, true); + // 080: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.222000f, 0.047000f, false); + shape.addVertex(0, 0.252000f, 0.047000f, true); + // 081: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.314000f, 0.047000f, false); + shape.addVertex(0, 0.350000f, 0.063000f, true); + // 083: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.386000f, 0.080000f, false); + shape.addVertex(0, 0.400000f, 0.093000f, true); + // 084: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0, 0.384000f, 0.119000f, false); + shape.addVertex(0, 0.375000f, 0.154000f, true); + // 086: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0, 0.366000f, 0.190000f, false); + shape.addVertex(0, 0.365000f, 0.238000f, true); + System.err.println("TestObject02.shape03a.winding_area: "+shape.getWindingOfLastOutline()); + shape.closeLastOutline(false); + } else { + // Inner a-shape: Winding.CCW + // Moved into OutlineShape same-order -> Winding.CCW -> OK + // + // Shape.MoveTo: + shape.closeLastOutline(false); + shape.addEmptyOutline(); + shape.addVertex(0.365000f, 0.238000f, true); + // 068: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.354000f, 0.243000f, false); + shape.addVertex(0.330000f, 0.248000f, true); + // 070: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.305000f, 0.254000f, false); + shape.addVertex(0.263000f, 0.254000f, true); + // 071: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.239000f, 0.254000f, false); + shape.addVertex(0.213000f, 0.251000f, true); + // 073: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.187000f, 0.247000f, false); + shape.addVertex(0.165000f, 0.236000f, true); + // 074: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.143000f, 0.224000f, false); + shape.addVertex(0.129000f, 0.204000f, true); + // 075: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.115000f, 0.184000f, false); + shape.addVertex(0.115000f, 0.151000f, true); + // 076: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.115000f, 0.122000f, false); + shape.addVertex(0.125000f, 0.102000f, true); + // 078: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.135000f, 0.082000f, false); + shape.addVertex(0.153000f, 0.070000f, true); + // 079: B5: quad-to pMh-p0-p1h ***** MID + // Shape.QuadTo: + shape.addVertex(0.172000f, 0.058000f, false); + shape.addVertex(0.197000f, 0.053000f, true); + // 080: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.222000f, 0.047000f, false); + shape.addVertex(0.252000f, 0.047000f, true); + // 081: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.314000f, 0.047000f, false); + shape.addVertex(0.350000f, 0.063000f, true); + // 083: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.386000f, 0.080000f, false); + shape.addVertex(0.400000f, 0.093000f, true); + // 084: B4: quad-to p0-p1-p2h **** MID + // Shape.QuadTo: + shape.addVertex(0.384000f, 0.119000f, false); + shape.addVertex(0.375000f, 0.154000f, true); + // 086: B6: quad-to pMh-p0-p1 + // Shape.QuadTo: + shape.addVertex(0.366000f, 0.190000f, false); + shape.addVertex(0.365000f, 0.238000f, true); + System.err.println("TestObject02.shape03b.winding_area: "+shape.getWindingOfLastOutline()); + shape.closeLastOutline(false); + } + // End Shape for Glyph 193 + + shape.setIsQuadraticNurbs(); + shape.setSharpness(shapesSharpness); + region.addOutlineShape(shape, null, rgbaColor); + + box.resize(shape.getBounds()); + } + + @Override + public String getSubString() { + return super.getSubString(); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UIListener01.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UIListener01.java deleted file mode 100644 index f18491a2a..000000000 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UIListener01.java +++ /dev/null @@ -1,401 +0,0 @@ -/** - * Copyright 2010 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.test.junit.graph.demos.ui; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; - -import com.jogamp.opengl.FPSCounter; -import com.jogamp.opengl.GL; -import com.jogamp.opengl.GL2ES2; -import com.jogamp.opengl.GLAnimatorControl; -import com.jogamp.opengl.GLAutoDrawable; -import com.jogamp.opengl.GLEventListener; -import com.jogamp.opengl.GLException; -import com.jogamp.opengl.GLPipelineFactory; -import com.jogamp.opengl.GLRunnable; -import com.jogamp.opengl.fixedfunc.GLMatrixFunc; -import com.jogamp.opengl.test.junit.graph.demos.MSAATool; -import com.jogamp.graph.curve.Region; -import com.jogamp.graph.curve.opengl.RegionRenderer; -import com.jogamp.graph.curve.opengl.RenderState; -import com.jogamp.graph.font.Font; -import com.jogamp.graph.font.FontFactory; -import com.jogamp.graph.geom.SVertex; -import com.jogamp.newt.event.KeyEvent; -import com.jogamp.newt.event.KeyListener; -import com.jogamp.newt.event.MouseEvent; -import com.jogamp.newt.event.MouseListener; -import com.jogamp.newt.opengl.GLWindow; -import com.jogamp.opengl.util.GLReadBufferUtil; -import com.jogamp.opengl.util.PMVMatrix; - -/** - * - * Action Keys: - * - 1/2: zoom in/out - * - 4/5: increase/decrease shape/text spacing - * - 6/7: increase/decrease corner size - * - 0/9: rotate - * - v: toggle v-sync - * - s: screenshot - */ -public class UIListener01 implements GLEventListener { - private final GLReadBufferUtil screenshot; - private final int renderModes; - private final RegionRenderer rRenderer; - private final boolean debug; - private final boolean trace; - - protected LabelButton button; - - private KeyAction keyAction; - private MouseAction mouseAction; - - private volatile GLAutoDrawable autoDrawable = null; - - private final float[] position = new float[] {0,0,0}; - - private float xTran = -10; - private float yTran = 10; - private float ang = 0f; - private float zoom = -70f; - - boolean ignoreInput = false; - - public UIListener01(final int renderModes, final RenderState rs, final boolean debug, final boolean trace) { - this.renderModes = renderModes; - this.rRenderer = RegionRenderer.create(rs, RegionRenderer.defaultBlendEnable, RegionRenderer.defaultBlendDisable); - this.debug = debug; - this.trace = trace; - this.screenshot = new GLReadBufferUtil(false, false); - - setMatrix(-4, -2, 0f, -10); - try { - final Font font = FontFactory.get(FontFactory.UBUNTU).getDefault(); - button = new LabelButton(SVertex.factory(), 0, font, "Click me!", 4f, 2f); - button.translate(2,1,0); - /** Button defaults ! - button.setLabelColor(1.0f,1.0f,1.0f); - button.setButtonColor(0.6f,0.6f,0.6f); - button.setCorner(1.0f); - button.setSpacing(2.0f); - */ - System.err.println(button); - } catch (final IOException ex) { - System.err.println("Caught: "+ex.getMessage()); - ex.printStackTrace(); - } - } - - public final RegionRenderer getRegionRenderer() { return rRenderer; } - public final float getZoom() { return zoom; } - public final float getXTran() { return xTran; } - public final float getYTran() { return yTran; } - public final float getAngle() { return ang; } - public final float[] getPosition() { return position; } - - public void setMatrix(final float xtrans, final float ytrans, final float angle, final int zoom) { - this.xTran = xtrans; - this.yTran = ytrans; - this.ang = angle; - this.zoom = zoom; - } - - @Override - public void init(final GLAutoDrawable drawable) { - autoDrawable = drawable; - GL2ES2 gl = drawable.getGL().getGL2ES2(); - if(debug) { - gl = gl.getContext().setGL( GLPipelineFactory.create("com.jogamp.opengl.Debug", null, gl, null) ).getGL2ES2(); - } - if(trace) { - gl = gl.getContext().setGL( GLPipelineFactory.create("com.jogamp.opengl.Trace", null, gl, new Object[] { System.err } ) ).getGL2ES2(); - } - gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); - getRegionRenderer().init(gl, renderModes); - - gl.setSwapInterval(1); - gl.glEnable(GL.GL_DEPTH_TEST); - gl.glEnable(GL.GL_POLYGON_OFFSET_FILL); - MSAATool.dump(drawable); - } - - @Override - public void reshape(final GLAutoDrawable drawable, final int xstart, final int ystart, final int width, final int height) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - - gl.glViewport(xstart, ystart, width, height); - rRenderer.reshapePerspective(45.0f, width, height, 0.1f, 7000.0f); - dumpMatrix(); - } - - @Override - public void display(final GLAutoDrawable drawable) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - - gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); - gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); - - final int[] sampleCount = { 4 }; - final float[] translate = button.getTranslate(); - - final RegionRenderer regionRenderer = getRegionRenderer(); - final PMVMatrix pmv = regionRenderer.getMatrix(); - pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); - pmv.glLoadIdentity(); - pmv.glTranslatef(getXTran(), getYTran(), getZoom()); - pmv.glRotatef(getAngle(), 0, 1, 0); - pmv.glTranslatef(translate[0], translate[1], 0); - button.drawShape(gl, regionRenderer, sampleCount); - } - - @Override - public void dispose(final GLAutoDrawable drawable) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - button.destroy(gl, getRegionRenderer()); - - autoDrawable = null; - screenshot.dispose(gl); - rRenderer.destroy(gl); - } - - public void zoom(final int v){ - zoom += v; - dumpMatrix(); - } - - public void move(final float x, final float y){ - xTran += x; - yTran += y; - dumpMatrix(); - } - public void rotate(final float delta){ - ang += delta; - ang %= 360.0f; - dumpMatrix(); - } - - void dumpMatrix() { - System.err.println("Matrix: " + xTran + "/" + yTran + " x"+zoom + " @"+ang); - } - - /** Attach the input listener to the window */ - public void attachInputListenerTo(final GLWindow window) { - if ( null == keyAction ) { - keyAction = new KeyAction(); - window.addKeyListener(keyAction); - } - if ( null == mouseAction ) { - mouseAction = new MouseAction(); - window.addMouseListener(mouseAction); - } - } - - public void detachFrom(final GLWindow window) { - if ( null == keyAction ) { - return; - } - if ( null == mouseAction ) { - return; - } - window.removeGLEventListener(this); - window.removeKeyListener(keyAction); - window.removeMouseListener(mouseAction); - } - - public void printScreen(final GLAutoDrawable drawable, final String dir, final String tech, final String objName, final boolean exportAlpha) throws GLException, IOException { - final StringWriter sw = new StringWriter(); - final PrintWriter pw = new PrintWriter(sw); - pw.printf("-%03dx%03d-Z%04d-T%04d-%s", drawable.getSurfaceWidth(), drawable.getSurfaceHeight(), (int)Math.abs(zoom), 0, objName); - - final String filename = dir + tech + sw +".png"; - if(screenshot.readPixels(drawable.getGL(), false)) { - screenshot.write(new File(filename)); - } - } - - int screenshot_num = 0; - - public void setIgnoreInput(final boolean v) { - ignoreInput = v; - } - public boolean getIgnoreInput() { - return ignoreInput; - } - - public class MouseAction implements MouseListener{ - - @Override - public void mouseClicked(final MouseEvent e) { - - } - - @Override - public void mouseEntered(final MouseEvent e) { - } - - @Override - public void mouseExited(final MouseEvent e) { - } - - @Override - public void mousePressed(final MouseEvent e) { - button.setLabelColor(0.8f,0.8f,0.8f); - button.setColor(0.1f, 0.1f, 0.1f, 1.0f); - } - - @Override - public void mouseReleased(final MouseEvent e) { - button.setLabelColor(1.0f,1.0f,1.0f); - button.setColor(0.6f,0.6f,0.6f, 1.0f); - } - - @Override - public void mouseMoved(final MouseEvent e) { - // TODO Auto-generated method stub - - } - - @Override - public void mouseDragged(final MouseEvent e) { - // TODO Auto-generated method stub - - } - - @Override - public void mouseWheelMoved(final MouseEvent e) { - // TODO Auto-generated method stub - - } - - } - - public class KeyAction implements KeyListener { - @Override - public void keyPressed(final KeyEvent arg0) { - if(ignoreInput) { - return; - } - - if(arg0.getKeyCode() == KeyEvent.VK_1){ - zoom(2); - } - else if(arg0.getKeyCode() == KeyEvent.VK_2){ - zoom(-2); - } - else if(arg0.getKeyCode() == KeyEvent.VK_UP){ - move(0, 1); - } - else if(arg0.getKeyCode() == KeyEvent.VK_DOWN){ - move(0, -1); - } - else if(arg0.getKeyCode() == KeyEvent.VK_LEFT){ - move(-1, 0); - } - else if(arg0.getKeyCode() == KeyEvent.VK_RIGHT){ - move(1, 0); - } - else if(arg0.getKeyCode() == KeyEvent.VK_4){ - button.setSpacing(button.getSpacingX()-0.01f, button.getSpacingY()-0.005f); - System.err.println("Button Spacing: " + button.getSpacingX()); - } - else if(arg0.getKeyCode() == KeyEvent.VK_5){ - button.setSpacing(button.getSpacingX()+0.01f, button.getSpacingY()+0.005f); - System.err.println("Button Spacing: " + button.getSpacingX()); - } - else if(arg0.getKeyCode() == KeyEvent.VK_6){ - button.setCorner(button.getCorner()-0.01f); - System.err.println("Button Corner: " + button.getCorner()); - } - else if(arg0.getKeyCode() == KeyEvent.VK_7){ - button.setCorner(button.getCorner()+0.01f); - System.err.println("Button Corner: " + button.getCorner()); - } - else if(arg0.getKeyCode() == KeyEvent.VK_0){ - rotate(1); - } - else if(arg0.getKeyCode() == KeyEvent.VK_9){ - rotate(-1); - } - else if(arg0.getKeyCode() == KeyEvent.VK_V) { - if(null != autoDrawable) { - autoDrawable.invoke(false, new GLRunnable() { - @Override - public boolean run(final GLAutoDrawable drawable) { - final GL gl = drawable.getGL(); - final int _i = gl.getSwapInterval(); - final int i; - switch(_i) { - case 0: i = 1; break; - case 1: i = -1; break; - case -1: i = 0; break; - default: i = 1; break; - } - gl.setSwapInterval(i); - - final GLAnimatorControl a = drawable.getAnimator(); - if( null != a ) { - a.resetFPSCounter(); - } - if(drawable instanceof FPSCounter) { - ((FPSCounter)drawable).resetFPSCounter(); - } - System.err.println("Swap Interval: "+_i+" -> "+i+" -> "+gl.getSwapInterval()); - return true; - } - }); - } - } - else if(arg0.getKeyCode() == KeyEvent.VK_S){ - rotate(-1); - if(null != autoDrawable) { - autoDrawable.invoke(false, new GLRunnable() { - @Override - public boolean run(final GLAutoDrawable drawable) { - try { - final String type = Region.getRenderModeString(renderModes); - printScreen(drawable, "./", "demo-"+type, "snap"+screenshot_num, false); - screenshot_num++; - } catch (final GLException e) { - e.printStackTrace(); - } catch (final IOException e) { - e.printStackTrace(); - } - return true; - } - }); - } - } - } - @Override - public void keyReleased(final KeyEvent arg0) {} - } -} diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UINewtDemo01.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UINewtDemo01.java deleted file mode 100644 index d211a9b80..000000000 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UINewtDemo01.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright 2010 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.test.junit.graph.demos.ui; - -import com.jogamp.opengl.GLCapabilities; -import com.jogamp.opengl.GLProfile; -import com.jogamp.common.util.InterruptSource; -import com.jogamp.graph.curve.opengl.RenderState; -import com.jogamp.graph.geom.SVertex; -import com.jogamp.newt.event.KeyAdapter; -import com.jogamp.newt.event.KeyEvent; -import com.jogamp.newt.event.WindowAdapter; -import com.jogamp.newt.event.WindowEvent; -import com.jogamp.newt.opengl.GLWindow; -import com.jogamp.opengl.util.Animator; - -/** Demonstrate the rendering of multiple outlines into one region/OutlineShape - * These Outlines are not necessary connected or contained. - * The output of this demo shows two identical shapes but the left one - * has some vertices with off-curve flag set to true, and the right allt he vertices - * are on the curve. Demos the Res. Independent Nurbs based Curve rendering - * - */ -public class UINewtDemo01 { - static final boolean DEBUG = false; - static final boolean TRACE = false; - - public static void main(final String[] args) { - final GLProfile glp = GLProfile.getGL2ES2(); - final GLCapabilities caps = new GLCapabilities(glp); - caps.setAlphaBits(4); - caps.setSampleBuffers(true); - caps.setNumSamples(4); - System.out.println("Requested: " + caps); - - final GLWindow window = GLWindow.create(caps); - window.setPosition(10, 10); - window.setSize(800, 400); - window.setTitle("GPU UI Newt Demo 01"); - final RenderState rs = RenderState.createRenderState(SVertex.factory()); - final UIListener01 uiGLListener = new UIListener01 (0, rs, DEBUG, TRACE); - uiGLListener.attachInputListenerTo(window); - window.addGLEventListener(uiGLListener); - window.setVisible(true); - - final Animator animator = new Animator(); - animator.setUpdateFPSFrames(60, System.err); - animator.add(window); - - window.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(final KeyEvent arg0) { - if(arg0.getKeyCode() == KeyEvent.VK_F4) { - new InterruptSource.Thread() { - @Override - public void run() { - window.destroy(); - } }.start(); - } - } - }); - window.addWindowListener(new WindowAdapter() { - @Override - public void windowDestroyed(final WindowEvent e) { - animator.stop(); - } - }); - - animator.start(); - } -} diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UIShape.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UIShape.java index 4976c78e8..f8c04d4bf 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UIShape.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UIShape.java @@ -31,7 +31,7 @@ import java.util.ArrayList; import com.jogamp.nativewindow.NativeWindowException; import com.jogamp.opengl.GL2ES2; - +import com.jogamp.opengl.fixedfunc.GLMatrixFunc; import com.jogamp.graph.curve.OutlineShape; import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.GLRegion; @@ -44,11 +44,14 @@ import com.jogamp.newt.event.MouseAdapter; import com.jogamp.newt.event.NEWTEvent; import com.jogamp.newt.event.MouseEvent; import com.jogamp.newt.event.MouseListener; +import com.jogamp.opengl.math.FloatUtil; import com.jogamp.opengl.math.Quaternion; +import com.jogamp.opengl.math.VectorUtil; import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.opengl.util.PMVMatrix; public abstract class UIShape { - public static final boolean DRAW_DEBUG_BOX = true; + public static final boolean DRAW_DEBUG_BOX = false; protected static final int DIRTY_SHAPE = 1 << 0 ; protected static final int DIRTY_STATE = 1 << 1 ; @@ -280,7 +283,7 @@ public abstract class UIShape { region.clear(gl); } addShapeToRegion(gl, renderer); - if( false && DRAW_DEBUG_BOX ) { + if( DRAW_DEBUG_BOX ) { region.clear(gl); final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory()); shape.setSharpness(shapesSharpness); @@ -295,6 +298,124 @@ public abstract class UIShape { } } + public void setTransform(final PMVMatrix pmv) { + final float[] uiTranslate = getTranslate(); + pmv.glTranslatef(uiTranslate[0], uiTranslate[1], uiTranslate[2]); + + final Quaternion quat = getRotation(); + final boolean rotate = !quat.isIdentity(); + final float[] uiScale = getScale(); + final boolean scale = !VectorUtil.isVec3Equal(uiScale, 0, VectorUtil.VEC3_ONE, 0, FloatUtil.EPSILON); + if( rotate || scale ) { + final float[] rotOrigin = getRotationOrigin(); + final boolean pivot = !VectorUtil.isVec3Zero(rotOrigin, 0, FloatUtil.EPSILON); + if( pivot ) { + pmv.glTranslatef(rotOrigin[0], rotOrigin[1], rotOrigin[2]); + } + if( scale ) { + pmv.glScalef(uiScale[0], uiScale[1], uiScale[2]); + } + if( rotate ) { + pmv.glRotate(quat); + } + if( pivot ) { + pmv.glTranslatef(-rotOrigin[0], -rotOrigin[1], -rotOrigin[2]); + } + } + } + + /** + * Retrieve window surface size of this shape + * @param renderer source of viewport and PMVMatrix + * @param surfaceSize target surface size + * @return true for successful gluProject(..) operation, otherwise false + */ + public boolean getSurfaceSize(final RegionRenderer renderer, final int[/*2*/] surfaceSize) { + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + + pmv.glPushMatrix(); + setTransform(pmv); + boolean res = false; + final int[/*4*/] viewport = renderer.getViewport(new int[4]); + // System.err.println("UIShape::getSurfaceSize.VP "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]); + final float[] winCoordHigh = new float[3]; + final float[] winCoordLow = new float[3]; + final float[] high = getBounds().getHigh(); + final float[] low = getBounds().getLow(); + + if( pmv.gluProject(high[0], high[1], high[2], viewport, 0, winCoordHigh, 0) ) { + // System.err.printf("UIShape::surfaceSize.H: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), high[0], high[1], high[2], winCoordHigh[0], winCoordHigh[1], winCoordHigh[2]); + if( pmv.gluProject(low[0], low[1], low[2], viewport, 0, winCoordLow, 0) ) { + // System.err.printf("UIShape::surfaceSize.L: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), low[0], low[1], low[2], winCoordLow[0], winCoordLow[1], winCoordLow[2]); + surfaceSize[0] = (int)(winCoordHigh[0] - winCoordLow[0]); + surfaceSize[1] = (int)(winCoordHigh[1] - winCoordLow[1]); + // System.err.printf("UIShape::surfaceSize.S: shape %d: %f x %f -> %d x %d%n", getName(), winCoordHigh[0] - winCoordLow[0], winCoordHigh[1] - winCoordLow[1], surfaceSize[0], surfaceSize[1]); + res = true; + } + } + pmv.glPopMatrix(); + return res; + } + + /** + * Map given object coordinate relative to this shape to window coordinates + * @param renderer source of viewport and PMVMatrix + * @param objPos object position relative to this shape's center + * @param glWinPos target window position of objPos relative to this shape + * @return true for successful gluProject(..) operation, otherwise false + */ + public boolean objToWinCoord(final RegionRenderer renderer, final float[/*3*/] objPos, final int[/*2*/] glWinPos) { + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + + pmv.glPushMatrix(); + setTransform(pmv); + boolean res = false; + final int[/*4*/] viewport = renderer.getViewport(new int[4]); + // System.err.println("UIShape::objToWinCoordgetSurfaceSize.VP "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]); + final float[] winCoord = new float[3]; + + if( pmv.gluProject(objPos[0], objPos[1], objPos[2], viewport, 0, winCoord, 0) ) { + // System.err.printf("UIShape::objToWinCoord.0: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), objPos[0], objPos[1], objPos[2], winCoord[0], winCoord[1], winCoord[2]); + glWinPos[0] = (int)(winCoord[0]); + glWinPos[1] = (int)(winCoord[1]); + // System.err.printf("UIShape::objToWinCoord.X: shape %d: %f / %f -> %d / %d%n", getName(), winCoord[0], winCoord[1], glWinPos[0], glWinPos[1]); + res = true; + } + pmv.glPopMatrix(); + return res; + } + + /** + * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate. + * @param renderer source of viewport and PMVMatrix + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param objPos target object position of glWinX/glWinY relative to this shape + * @return @return true for successful gluProject(..) and gluUnProject(..) operations, otherwise false + */ + public boolean winToObjCoord(final RegionRenderer renderer, final int glWinX, final int glWinY, final float[/*3*/] objPos) { + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + + pmv.glPushMatrix(); + setTransform(pmv); + boolean res = false; + final float[] ctr = getBounds().getCenter(); + final int[] viewport = renderer.getViewport(new int[4]); + final float[] tmp = new float[3]; + if( pmv.gluProject(ctr[0], ctr[1], ctr[2], viewport, 0, tmp, 0) ) { + // System.err.printf("UIShape::winToObjCoord.0: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), ctr[0], ctr[1], ctr[2], tmp[0], tmp[1], tmp[2]); + if( pmv.gluUnProject(glWinX, glWinY, tmp[2], viewport, 0, objPos, 0) ) { + // System.err.printf("UIShape::winToObjCoord.1: shape %d: win [%d, %d, %f] -> obj [%f, %f, %f]%n", getName(), glWinX, glWinY, tmp[2], objPos[0], objPos[1], objPos[2]); + res = true; + } + } + pmv.glPopMatrix(); + return res; + } + public float[] getColor() { return rgbaColor; } @@ -420,15 +541,22 @@ public abstract class UIShape { * The latter fields are also normalized to lower-left zero origin, allowing easier usage. */ public static class PointerEventInfo { - /** The intended {@link UIShape} instance for this event */ + /** The associated {@link UIShape} for this event */ public final UIShape shape; - /** The relative pointer position inside the intended {@link UIShape}. */ + /** The relative object coordinate of glWinX/glWinY to the associated {@link UIShape}. */ public final float[] objPos; - /** window x-position in OpenGL model space */ + /** X-coordinate in GL window coordinates, origin bottom-left */ public final int glWinX; - /** window y-position in OpenGL model space */ + /** Y-coordinate in GL window coordinates, origin bottom-left */ public final int glWinY; + /** + * Ctor + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param shape associated shape + * @param objPos relative object coordinate of glWinX/glWinY to the associated shape. + */ PointerEventInfo(final int glWinX, final int glWinY, final UIShape shape, final float[] objPos) { this.glWinX = glWinX; this.glWinY = glWinY; @@ -455,10 +583,11 @@ public abstract class UIShape { } /** - * + * Dispatch given NEWT mouse event to this shape * @param e original Newt {@link MouseEvent} - * @param glX x-position in OpenGL model space - * @param glY y-position in OpenGL model space + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param objPos object position of mouse event within this shape */ public final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY, final float[] objPos) { e.setAttachment(new PointerEventInfo(glWinX, glWinY, this, objPos)); @@ -524,19 +653,26 @@ public abstract class UIShape { // protected OutlineShape createDebugOutline(final OutlineShape shape, final AABBox box) { - final float tw = box.getWidth(); - final float th = box.getHeight(); + final float d = 0.025f; + final float tw = box.getWidth() + d*2f; + final float th = box.getHeight() + d*2f; - final float minX = box.getMinX(); - final float minY = box.getMinY(); - final float z = box.getMinZ() + 0.025f; + final float minX = box.getMinX() - d; + final float minY = box.getMinY() - d; + final float z = 0; // box.getMinZ() + 0.025f; // CCW! - shape.addVertex(minX, minY, z, true); - shape.addVertex(minX+tw, minY, z, true); - shape.addVertex(minX+tw, minY + th, z, true); - shape.addVertex(minX, minY + th, z, true); - shape.closeLastOutline(true); + shape.moveTo(minX, minY, z); + shape.lineTo(minX+tw, minY, z); + shape.lineTo(minX+tw, minY + th, z); + shape.lineTo(minX, minY + th, z); + shape.closePath(); + + // shape.addVertex(minX, minY, z, true); + // shape.addVertex(minX+tw, minY, z, true); + // shape.addVertex(minX+tw, minY + th, z, true); + // shape.addVertex(minX, minY + th, z, true); + // shape.closeLastOutline(true); return shape; } diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UIShapeDemo01.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UIShapeDemo01.java new file mode 100644 index 000000000..4a61a2be0 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UIShapeDemo01.java @@ -0,0 +1,556 @@ +/** + * Copyright 2010-2023 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.graph.demos.ui; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +import com.jogamp.opengl.FPSCounter; +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.GLException; +import com.jogamp.opengl.GLPipelineFactory; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.GLRunnable; +import com.jogamp.opengl.fixedfunc.GLMatrixFunc; +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.opengl.test.junit.graph.demos.MSAATool; +import com.jogamp.common.util.InterruptSource; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.curve.opengl.RenderState; +import com.jogamp.graph.curve.opengl.TextRegionUtil; +import com.jogamp.graph.font.Font; +import com.jogamp.graph.font.FontFactory; +import com.jogamp.graph.font.FontSet; +import com.jogamp.graph.geom.SVertex; +import com.jogamp.newt.Window; +import com.jogamp.newt.event.KeyAdapter; +import com.jogamp.newt.event.KeyEvent; +import com.jogamp.newt.event.KeyListener; +import com.jogamp.newt.event.MouseEvent; +import com.jogamp.newt.event.MouseListener; +import com.jogamp.newt.event.WindowAdapter; +import com.jogamp.newt.event.WindowEvent; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.opengl.util.Animator; +import com.jogamp.opengl.util.GLReadBufferUtil; +import com.jogamp.opengl.util.PMVMatrix; + +/** + * Basic UIShape and Type Rendering demo. + * + * Action Keys: + * - 1/2: zoom in/out + * - 4/5: increase/decrease shape/text spacing + * - 6/7: increase/decrease corner size + * - 0/9: rotate + * - v: toggle v-sync + * - s: screenshot + */ +public class UIShapeDemo01 implements GLEventListener { + static final boolean DEBUG = false; + static final boolean TRACE = false; + + public static void main(final String[] args) throws IOException { + Font font = null; + if( 0 != args.length ) { + for(int i=0; i<args.length; i++) { + if(args[i].equals("-font")) { + i++; + font = FontFactory.get(new File(args[i])); + } + } + } + if( null == font ) { + font = FontFactory.get(FontFactory.UBUNTU).get(FontSet.FAMILY_LIGHT, FontSet.STYLE_SERIF); + } + System.err.println("Font: "+font.getFullFamilyName()); + + final GLProfile glp = GLProfile.getGL2ES2(); + final GLCapabilities caps = new GLCapabilities(glp); + caps.setAlphaBits(4); + caps.setSampleBuffers(true); + caps.setNumSamples(4); + System.out.println("Requested: " + caps); + + final GLWindow window = GLWindow.create(caps); + // window.setPosition(10, 10); + window.setSize(800, 400); + window.setTitle(UIShapeDemo01.class.getSimpleName()+": "+window.getSurfaceWidth()+" x "+window.getSurfaceHeight()); + final RenderState rs = RenderState.createRenderState(SVertex.factory()); + final UIShapeDemo01 uiGLListener = new UIShapeDemo01(font, Region.COLORCHANNEL_RENDERING_BIT, rs, DEBUG, TRACE); + uiGLListener.attachInputListenerTo(window); + window.addGLEventListener(uiGLListener); + window.setVisible(true); + + final Animator animator = new Animator(); + // animator.setUpdateFPSFrames(60, System.err); + animator.add(window); + + window.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(final KeyEvent arg0) { + if(arg0.getKeyCode() == KeyEvent.VK_F4) { + new InterruptSource.Thread() { + @Override + public void run() { + window.destroy(); + } }.start(); + } + } + }); + window.addWindowListener(new WindowAdapter() { + @Override + public void windowDestroyed(final WindowEvent e) { + animator.stop(); + } + }); + + animator.start(); + } + + private final Font font; + private final GLReadBufferUtil screenshot; + private final int renderModes; + private final RegionRenderer rRenderer; + private final boolean debug; + private final boolean trace; + + private final LabelButton button; + private final CrossHair crossHair; + + private KeyAction keyAction; + private MouseAction mouseAction; + + private volatile GLAutoDrawable autoDrawable = null; + + private final float[] position = new float[] {0,0,0}; + + private static final float xTran = 0f; + private static final float yTran = 0f; + private static final float zTran = -1/5f; + private static final float zNear = 0.1f; + private static final float zFar = 7000.0f; + + boolean ignoreInput = false; + + public UIShapeDemo01(final Font font, final int renderModes, final RenderState rs, final boolean debug, final boolean trace) { + this.font = font; + this.renderModes = renderModes; + this.rRenderer = RegionRenderer.create(rs, RegionRenderer.defaultBlendEnable, RegionRenderer.defaultBlendDisable); + this.debug = debug; + this.trace = trace; + this.screenshot = new GLReadBufferUtil(false, false); + + button = new LabelButton(SVertex.factory(), renderModes, font, "Click me!", 1/8f, 1/16f); + button.setLabelColor(0.0f,0.0f,0.0f); + /** Button defaults ! + button.setLabelColor(1.0f,1.0f,1.0f); + button.setButtonColor(0.6f,0.6f,0.6f); + button.setCorner(1.0f); + button.setSpacing(2.0f); + */ + System.err.println(button); + crossHair = new CrossHair(SVertex.factory(), renderModes, 1/20f, 1/20f, 1/1000f); + crossHair.setColor(0f,0f,1f,1f); + crossHair.setEnabled(true); + } + + public final RegionRenderer getRegionRenderer() { return rRenderer; } + public final float[] getPosition() { return position; } + + @Override + public void init(final GLAutoDrawable drawable) { + autoDrawable = drawable; + GL2ES2 gl = drawable.getGL().getGL2ES2(); + if(debug) { + gl = gl.getContext().setGL( GLPipelineFactory.create("com.jogamp.opengl.Debug", null, gl, null) ).getGL2ES2(); + } + if(trace) { + gl = gl.getContext().setGL( GLPipelineFactory.create("com.jogamp.opengl.Trace", null, gl, new Object[] { System.err } ) ).getGL2ES2(); + } + gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + getRegionRenderer().init(gl, renderModes); + + gl.setSwapInterval(1); + gl.glEnable(GL.GL_DEPTH_TEST); + gl.glEnable(GL.GL_POLYGON_OFFSET_FILL); + MSAATool.dump(drawable); + } + + @Override + public void reshape(final GLAutoDrawable drawable, final int xstart, final int ystart, final int width, final int height) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + + gl.glViewport(xstart, ystart, width, height); + rRenderer.reshapePerspective(45.0f, width, height, zNear, zFar); + // rRenderer.reshapeOrtho(width, height, zNear, zFar); + + lastWidth = width; + lastHeight = height; + if( drawable instanceof Window ) { + ((Window)drawable).setTitle(UIShapeDemo01.class.getSimpleName()+": "+drawable.getSurfaceWidth()+" x "+drawable.getSurfaceHeight()); + } + } + float lastWidth = 0f, lastHeight = 0f; + + final int[] sampleCount = { 4 }; + + private void drawShape(final GL2ES2 gl, final PMVMatrix pmv, final RegionRenderer renderer, final UIShape shape) { + pmv.glPushMatrix(); + shape.setTransform(pmv); + shape.drawShape(gl, renderer, sampleCount); + pmv.glPopMatrix(); + } + + @Override + public void display(final GLAutoDrawable drawable) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + + gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); + + final RegionRenderer renderer = getRegionRenderer(); + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + pmv.glLoadIdentity(); + pmv.glTranslatef(xTran, yTran, zTran); + renderer.enable(gl, true); + drawShape(gl, pmv, renderer, button); + drawShape(gl, pmv, renderer, crossHair); + { + final String text = "Hello Origin."; + final float full_width_o; + { + final float orthoDist = -zTran; // assume orthogonal plane at -zTran + float glWinX = 0; + float glWinY = 0; + final float winZ = FloatUtil.getOrthoWinZ(orthoDist, zNear, zFar); + final float[] objCoord0 = new float[3]; + final float[] objCoord1 = new float[3]; + if( pmv.gluUnProject(glWinX, glWinY, winZ, renderer.getViewport(), 0, objCoord0, 0) ) { + if( once ) { + System.err.printf("winToObjCoord: win [%f, %f, %f] -> obj [%f, %f, %f]%n", glWinX, glWinY, winZ, objCoord0[0], objCoord0[1], objCoord0[2]); + } + } + glWinX = drawable.getSurfaceWidth(); + glWinY = drawable.getSurfaceHeight(); + if( pmv.gluUnProject(glWinX, glWinY, winZ, renderer.getViewport(), 0, objCoord1, 0) ) { + if( once ) { + System.err.printf("winToObjCoord: win [%f, %f, %f] -> obj [%f, %f, %f]%n", glWinX, glWinY, winZ, objCoord1[0], objCoord1[1], objCoord1[2]); + } + } + full_width_o = objCoord1[0] - objCoord0[0]; + } + final AABBox txt_box_em = font.getGlyphBounds(text); + final float full_width_s = full_width_o / txt_box_em.getWidth(); + final float txt_scale = full_width_s/2f; + pmv.glPushMatrix(); + pmv.glScalef(txt_scale, txt_scale, 1f); + pmv.glTranslatef(-txt_box_em.getWidth(), 0f, 0f); + final AABBox txt_box_r = TextRegionUtil.drawString3D(gl, renderModes, renderer, font, text, new float[] { 0, 0, 0, 1 }, sampleCount); + if( once ) { + final AABBox txt_box_em2 = font.getGlyphShapeBounds(null, text); + System.err.println("XXX: full_width: "+full_width_o+" / "+txt_box_em.getWidth()+" -> "+full_width_s); + System.err.println("XXX: txt_box_em "+txt_box_em); + System.err.println("XXX: txt_box_e2 "+txt_box_em2); + System.err.println("XXX: txt_box_rg "+txt_box_r); + once = false; + } + pmv.glPopMatrix(); + } + renderer.enable(gl, false); + } + static boolean once = true; + + @Override + public void dispose(final GLAutoDrawable drawable) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + button.destroy(gl, getRegionRenderer()); + crossHair.destroy(gl, getRegionRenderer()); + + autoDrawable = null; + screenshot.dispose(gl); + rRenderer.destroy(gl); + } + + /** Attach the input listener to the window */ + public void attachInputListenerTo(final GLWindow window) { + if ( null == keyAction ) { + keyAction = new KeyAction(); + window.addKeyListener(keyAction); + } + if ( null == mouseAction ) { + mouseAction = new MouseAction(); + window.addMouseListener(mouseAction); + } + } + + public void detachFrom(final GLWindow window) { + if ( null == keyAction ) { + return; + } + if ( null == mouseAction ) { + return; + } + window.removeGLEventListener(this); + window.removeKeyListener(keyAction); + window.removeMouseListener(mouseAction); + } + + public void printScreen(final GLAutoDrawable drawable, final String dir, final String tech, final String objName, final boolean exportAlpha) throws GLException, IOException { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw); + pw.printf("-%03dx%03d-Z%04d-T%04d-%s", drawable.getSurfaceWidth(), drawable.getSurfaceHeight(), (int)Math.abs(zTran), 0, objName); + + final String filename = dir + tech + sw +".png"; + if(screenshot.readPixels(drawable.getGL(), false)) { + screenshot.write(new File(filename)); + } + } + + int screenshot_num = 0; + + public void setIgnoreInput(final boolean v) { + ignoreInput = v; + } + public boolean getIgnoreInput() { + return ignoreInput; + } + + public class MouseAction implements MouseListener{ + + @Override + public void mouseClicked(final MouseEvent e) { + + } + + @Override + public void mouseEntered(final MouseEvent e) { + } + + @Override + public void mouseExited(final MouseEvent e) { + } + + @Override + public void mousePressed(final MouseEvent e) { + autoDrawable.invoke(false, new GLRunnable() { // avoid data-race + @Override + public boolean run(final GLAutoDrawable drawable) { + System.err.println("\n\nMouse: "+e); + + final RegionRenderer renderer = getRegionRenderer(); + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + pmv.glLoadIdentity(); + pmv.glTranslatef(xTran, yTran, zTran); + + // flip to GL window coordinates, origin bottom-left + final int[] viewport = renderer.getViewport(new int[4]); + final int glWinX = e.getX(); + final int glWinY = viewport[3] - e.getY() - 1; + + { + final float[] objPos = new float[3]; + System.err.println("\n\nButton: "+button); + button.winToObjCoord(renderer, glWinX, glWinY, objPos); + System.err.println("Button: Click: Win "+glWinX+"/"+glWinY+" -> Obj "+objPos[0]+"/"+objPos[1]+"/"+objPos[1]); + + final int[] surfaceSize = new int[2]; + button.getSurfaceSize(renderer, surfaceSize); + System.err.println("Button: Size: Pixel "+surfaceSize[0]+" x "+surfaceSize[1]); + } + { + final float[] objPosC = crossHair.getBounds().getCenter(); + final int[] objWinPos = new int[2]; + System.err.println("\n\nCrossHair: "+crossHair); + if( crossHair.objToWinCoord(renderer, objPosC, objWinPos) ) { + System.err.println("CrossHair: Obj: Obj "+objPosC[0]+"/"+objPosC[1]+"/"+objPosC[1]+" -> Win "+objWinPos[0]+"/"+objWinPos[1]); + } + + final float[] objPos2 = new float[3]; + crossHair.winToObjCoord(renderer, objWinPos[0], objWinPos[1], objPos2); + System.err.println("CrossHair: Obj: Win "+objWinPos[0]+"/"+objWinPos[1]+" -> Obj "+objPos2[0]+"/"+objPos2[1]+"/"+objPos2[1]); + + final float[] winObjPos = new float[3]; + if( crossHair.winToObjCoord(renderer, glWinX, glWinY, winObjPos) ) { + // final float[] translate = crossHair.getTranslate(); + // final float[] objPosT = new float[] { objPosC[0]+translate[0], objPosC[1]+translate[1], objPosC[2]+translate[2] }; + final float dx = winObjPos[0] - objPosC[0]; + final float dy = winObjPos[1] - objPosC[1]; + // final float dz = winObjPos[2] - objPosT[2]; + if( !FloatUtil.isZero(dx, FloatUtil.EPSILON) || !FloatUtil.isZero(dy, FloatUtil.EPSILON) ) { + System.err.println("CrossHair: Move.1: Win "+glWinX+"/"+glWinY+" -> Obj "+winObjPos[0]+"/"+winObjPos[1]+"/"+winObjPos[1]+" -> diff "+dx+" / "+dy); + crossHair.translate(dx, dy, 0f); + } else { + System.err.println("CrossHair: Move.0: Win "+glWinX+"/"+glWinY+" -> Obj "+winObjPos[0]+"/"+winObjPos[1]+"/"+winObjPos[1]+" -> diff "+dx+" / "+dy); + } + } + + final int[] surfaceSize = new int[2]; + crossHair.getSurfaceSize(renderer, surfaceSize); + System.err.println("CrossHair: Size: Pixel "+surfaceSize[0]+" x "+surfaceSize[1]); + } + return true; + } } ); + + } + + @Override + public void mouseReleased(final MouseEvent e) { + } + + @Override + public void mouseMoved(final MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseDragged(final MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseWheelMoved(final MouseEvent e) { + // TODO Auto-generated method stub + + } + + } + + public class KeyAction implements KeyListener { + @Override + public void keyPressed(final KeyEvent arg0) { + if(ignoreInput) { + return; + } + + if(arg0.getKeyCode() == KeyEvent.VK_1){ + button.translate(0f, 0f, -zTran/10f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_2){ + button.translate(0f, 0f, zTran/10f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_UP){ + button.translate(0f, button.getHeight()/10f, 0f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_DOWN){ + button.translate(0f, -button.getHeight()/10f, 0f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_LEFT){ + button.translate(-button.getWidth()/10f, 0f, 0f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_RIGHT){ + button.translate(button.getWidth()/10f, 0f, 0f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_4){ + button.setSpacing(button.getSpacingX()-0.01f, button.getSpacingY()-0.005f); + System.err.println("Button Spacing: " + button.getSpacingX()); + } + else if(arg0.getKeyCode() == KeyEvent.VK_5){ + button.setSpacing(button.getSpacingX()+0.01f, button.getSpacingY()+0.005f); + System.err.println("Button Spacing: " + button.getSpacingX()); + } + else if(arg0.getKeyCode() == KeyEvent.VK_6){ + button.setCorner(button.getCorner()-0.01f); + System.err.println("Button Corner: " + button.getCorner()); + } + else if(arg0.getKeyCode() == KeyEvent.VK_7){ + button.setCorner(button.getCorner()+0.01f); + System.err.println("Button Corner: " + button.getCorner()); + } + else if(arg0.getKeyCode() == KeyEvent.VK_0){ + // rotate(1); + } + else if(arg0.getKeyCode() == KeyEvent.VK_9){ + // rotate(-1); + } + else if(arg0.getKeyCode() == KeyEvent.VK_V) { + if(null != autoDrawable) { + autoDrawable.invoke(false, new GLRunnable() { + @Override + public boolean run(final GLAutoDrawable drawable) { + final GL gl = drawable.getGL(); + final int _i = gl.getSwapInterval(); + final int i; + switch(_i) { + case 0: i = 1; break; + case 1: i = -1; break; + case -1: i = 0; break; + default: i = 1; break; + } + gl.setSwapInterval(i); + + final GLAnimatorControl a = drawable.getAnimator(); + if( null != a ) { + a.resetFPSCounter(); + } + if(drawable instanceof FPSCounter) { + ((FPSCounter)drawable).resetFPSCounter(); + } + System.err.println("Swap Interval: "+_i+" -> "+i+" -> "+gl.getSwapInterval()); + return true; + } + }); + } + } + else if(arg0.getKeyCode() == KeyEvent.VK_S){ + if(null != autoDrawable) { + autoDrawable.invoke(false, new GLRunnable() { + @Override + public boolean run(final GLAutoDrawable drawable) { + try { + final String type = Region.getRenderModeString(renderModes); + printScreen(drawable, "./", "demo-"+type, "snap"+screenshot_num, false); + screenshot_num++; + } catch (final GLException e) { + e.printStackTrace(); + } catch (final IOException e) { + e.printStackTrace(); + } + return true; + } + }); + } + } + } + @Override + public void keyReleased(final KeyEvent arg0) {} + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UITypeDemo01.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UITypeDemo01.java new file mode 100644 index 000000000..b3a56d9b7 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/UITypeDemo01.java @@ -0,0 +1,584 @@ +/** + * Copyright 2010-2023 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.graph.demos.ui; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +import com.jogamp.opengl.FPSCounter; +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.GLException; +import com.jogamp.opengl.GLPipelineFactory; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.GLRunnable; +import com.jogamp.opengl.fixedfunc.GLMatrixFunc; +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.opengl.test.junit.graph.demos.MSAATool; +import com.jogamp.opengl.test.junit.util.MiscUtils; +import com.jogamp.common.util.InterruptSource; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.GLRegion; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.curve.opengl.RenderState; +import com.jogamp.graph.curve.opengl.TextRegionUtil; +import com.jogamp.graph.font.Font; +import com.jogamp.graph.font.FontFactory; +import com.jogamp.graph.font.FontSet; +import com.jogamp.graph.font.Font.Glyph; +import com.jogamp.graph.geom.SVertex; +import com.jogamp.newt.Window; +import com.jogamp.newt.event.KeyAdapter; +import com.jogamp.newt.event.KeyEvent; +import com.jogamp.newt.event.KeyListener; +import com.jogamp.newt.event.MouseEvent; +import com.jogamp.newt.event.MouseListener; +import com.jogamp.newt.event.WindowAdapter; +import com.jogamp.newt.event.WindowEvent; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.opengl.util.Animator; +import com.jogamp.opengl.util.GLReadBufferUtil; +import com.jogamp.opengl.util.PMVMatrix; + +/** + * Basic UIShape and Type Rendering demo. + * + * Action Keys: + * - 1/2: zoom in/out + * - 4/5: increase/decrease shape/text spacing + * - 6/7: increase/decrease corner size + * - 0/9: rotate + * - v: toggle v-sync + * - s: screenshot + */ +public class UITypeDemo01 implements GLEventListener { + static final boolean DEBUG = false; + static final boolean TRACE = false; + + public static void main(final String[] args) throws IOException { + Font font = null; + String text = "Hello Origin."; + int glyph_id = 0; + if( 0 != args.length ) { + for(int i=0; i<args.length; i++) { + if(args[i].equals("-font")) { + i++; + font = FontFactory.get(new File(args[i])); + } else if(args[i].equals("-text")) { + i++; + text = args[i]; + } else if(args[i].equals("-glyph")) { + i++; + glyph_id = MiscUtils.atoi(args[i], 0); + } + } + } + if( null == font ) { + font = FontFactory.get(FontFactory.UBUNTU).get(FontSet.FAMILY_LIGHT, FontSet.STYLE_SERIF); + } + System.err.println("Font: "+font.getFullFamilyName()); + System.err.println("Text: "+text); + + final GLProfile glp = GLProfile.getGL2ES2(); + final GLCapabilities caps = new GLCapabilities(glp); + caps.setAlphaBits(4); + caps.setSampleBuffers(true); + caps.setNumSamples(4); + System.out.println("Requested: " + caps); + + final GLWindow window = GLWindow.create(caps); + // window.setPosition(10, 10); + window.setSize(800, 400); + window.setTitle(UITypeDemo01.class.getSimpleName()+": "+window.getSurfaceWidth()+" x "+window.getSurfaceHeight()); + final RenderState rs = RenderState.createRenderState(SVertex.factory()); + final UITypeDemo01 uiGLListener = new UITypeDemo01(font, glyph_id, text, Region.COLORCHANNEL_RENDERING_BIT, rs, DEBUG, TRACE); + uiGLListener.attachInputListenerTo(window); + window.addGLEventListener(uiGLListener); + window.setVisible(true); + + final Animator animator = new Animator(); + // animator.setUpdateFPSFrames(60, System.err); + animator.add(window); + + window.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(final KeyEvent arg0) { + if(arg0.getKeyCode() == KeyEvent.VK_F4) { + new InterruptSource.Thread() { + @Override + public void run() { + window.destroy(); + } }.start(); + } + } + }); + window.addWindowListener(new WindowAdapter() { + @Override + public void windowDestroyed(final WindowEvent e) { + animator.stop(); + } + }); + + animator.start(); + } + + private final float[] fg_color = new float[] { 0, 0, 0, 1 }; + private final Font font; + private final String text; + private final int glyph_id; + private final GLReadBufferUtil screenshot; + private final int renderModes; + private final RegionRenderer rRenderer; + private final boolean debug; + private final boolean trace; + + private final CrossHair crossHair; + private final UIShape testObj; + + private KeyAction keyAction; + private MouseAction mouseAction; + + private volatile GLAutoDrawable autoDrawable = null; + + private final float[] position = new float[] {0,0,0}; + + private static final float xTran = 0f; + private static final float yTran = 0f; + private static final float zTran = -1/5f; + private static final float zNear = 0.1f; + private static final float zFar = 7000.0f; + + boolean ignoreInput = false; + + @SuppressWarnings("unused") + public UITypeDemo01(final Font font, final int glyph_id, final String text, final int renderModes, final RenderState rs, final boolean debug, final boolean trace) { + this.font = font; + this.text = text; + this.glyph_id = glyph_id; + this.renderModes = renderModes; + this.rRenderer = RegionRenderer.create(rs, RegionRenderer.defaultBlendEnable, RegionRenderer.defaultBlendDisable); + this.debug = debug; + this.trace = trace; + this.screenshot = new GLReadBufferUtil(false, false); + + crossHair = new CrossHair(SVertex.factory(), renderModes, 1/20f, 1/20f, 1/1000f); + crossHair.setColor(0f,0f,1f,1f); + crossHair.setEnabled(true); + + if (false ) { + final Rectangle o = new Rectangle(SVertex.factory(), renderModes, 1/10f, 1/20f, 1/1000f); + o.translate(o.getWidth(), -o.getHeight(), 0f); + testObj = o; + } else { + final float scale = 0.15312886f; + final float size_xz = 0.541f; + final UIShape o = new TestObject02(SVertex.factory(), renderModes); + o.scale(scale, scale, 1f); + // o.translate(size_xz, -size_xz, 0f); + testObj = o; + } + testObj.setColor(0f, 0f, 0f, 1f); + testObj.setEnabled(true); + } + + public final RegionRenderer getRegionRenderer() { return rRenderer; } + public final float[] getPosition() { return position; } + + @Override + public void init(final GLAutoDrawable drawable) { + autoDrawable = drawable; + GL2ES2 gl = drawable.getGL().getGL2ES2(); + if(debug) { + gl = gl.getContext().setGL( GLPipelineFactory.create("com.jogamp.opengl.Debug", null, gl, null) ).getGL2ES2(); + } + if(trace) { + gl = gl.getContext().setGL( GLPipelineFactory.create("com.jogamp.opengl.Trace", null, gl, new Object[] { System.err } ) ).getGL2ES2(); + } + gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + getRegionRenderer().init(gl, renderModes); + + gl.setSwapInterval(1); + gl.glEnable(GL.GL_DEPTH_TEST); + gl.glEnable(GL.GL_POLYGON_OFFSET_FILL); + MSAATool.dump(drawable); + } + + @Override + public void reshape(final GLAutoDrawable drawable, final int xstart, final int ystart, final int width, final int height) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + + gl.glViewport(xstart, ystart, width, height); + rRenderer.reshapePerspective(45.0f, width, height, zNear, zFar); + // rRenderer.reshapeOrtho(width, height, zNear, zFar); + + lastWidth = width; + lastHeight = height; + if( drawable instanceof Window ) { + ((Window)drawable).setTitle(UITypeDemo01.class.getSimpleName()+": "+drawable.getSurfaceWidth()+" x "+drawable.getSurfaceHeight()); + } + } + float lastWidth = 0f, lastHeight = 0f; + + final int[] sampleCount = { 4 }; + + private void drawShape(final GL2ES2 gl, final PMVMatrix pmv, final RegionRenderer renderer, final UIShape shape) { + pmv.glPushMatrix(); + shape.setTransform(pmv); + shape.drawShape(gl, renderer, sampleCount); + pmv.glPopMatrix(); + } + + @Override + public void display(final GLAutoDrawable drawable) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + + gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); + + final RegionRenderer renderer = getRegionRenderer(); + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + pmv.glLoadIdentity(); + pmv.glTranslatef(xTran, yTran, zTran); + renderer.enable(gl, true); + drawShape(gl, pmv, renderer, testObj); + drawShape(gl, pmv, renderer, crossHair); + { + final float full_width_o; + final float full_height_o; + { + final float orthoDist = -zTran; // assume orthogonal plane at -zTran + float glWinX = 0; + float glWinY = 0; + final float winZ = FloatUtil.getOrthoWinZ(orthoDist, zNear, zFar); + final float[] objCoord0 = new float[3]; + final float[] objCoord1 = new float[3]; + if( pmv.gluUnProject(glWinX, glWinY, winZ, renderer.getViewport(), 0, objCoord0, 0) ) { + if( once ) { + System.err.printf("winToObjCoord: win [%f, %f, %f] -> obj [%f, %f, %f]%n", glWinX, glWinY, winZ, objCoord0[0], objCoord0[1], objCoord0[2]); + } + } + glWinX = drawable.getSurfaceWidth(); + glWinY = drawable.getSurfaceHeight(); + if( pmv.gluUnProject(glWinX, glWinY, winZ, renderer.getViewport(), 0, objCoord1, 0) ) { + if( once ) { + System.err.printf("winToObjCoord: win [%f, %f, %f] -> obj [%f, %f, %f]%n", glWinX, glWinY, winZ, objCoord1[0], objCoord1[1], objCoord1[2]); + } + } + full_width_o = objCoord1[0] - objCoord0[0]; + full_height_o = objCoord1[1] - objCoord0[1]; + } + final Font.Glyph glyph = Glyph.ID_UNKNOWN != glyph_id ? font.getGlyph(glyph_id) : null; + if( null != glyph && null != glyph.getShape() && glyph.getID() != Glyph.ID_UNKNOWN ) { + final AABBox txt_box_em = glyph.getBBox(); + final float full_width_s = full_width_o / txt_box_em.getWidth(); + final float full_height_s = full_height_o / txt_box_em.getHeight(); + final float txt_scale = full_width_s < full_height_s ? full_width_s/2f : full_height_s/2f; + pmv.glPushMatrix(); + pmv.glScalef(txt_scale, txt_scale, 1f); + pmv.glTranslatef(-txt_box_em.getWidth(), 0f, 0f); + { + final GLRegion region = GLRegion.create(renderModes, null); + region.addOutlineShape(glyph.getShape(), null, region.hasColorChannel() ? fg_color : null); + region.draw(gl, renderer, sampleCount); + region.destroy(gl); + } + if( once ) { + final AABBox txt_box_em2 = font.getGlyphShapeBounds(null, text); + System.err.println("XXX: full_width: "+full_width_o+" / "+txt_box_em.getWidth()+" -> "+full_width_s); + System.err.println("XXX: full_height: "+full_height_o+" / "+txt_box_em.getHeight()+" -> "+full_height_s); + System.err.println("XXX: txt_scale: "+txt_scale); + System.err.println("XXX: txt_box_em "+txt_box_em); + System.err.println("XXX: txt_box_e2 "+txt_box_em2); + once = false; + } + } else { + final AABBox txt_box_em = font.getGlyphBounds(text); + final float full_width_s = full_width_o / txt_box_em.getWidth(); + final float full_height_s = full_height_o / txt_box_em.getHeight(); + final float txt_scale = full_width_s < full_height_s ? full_width_s/2f : full_height_s/2f; + pmv.glPushMatrix(); + pmv.glScalef(txt_scale, txt_scale, 1f); + pmv.glTranslatef(-txt_box_em.getWidth(), 0f, 0f); + final AABBox txt_box_r = TextRegionUtil.drawString3D(gl, renderModes, renderer, font, text, fg_color, sampleCount); + if( once ) { + final AABBox txt_box_em2 = font.getGlyphShapeBounds(null, text); + System.err.println("XXX: full_width: "+full_width_o+" / "+txt_box_em.getWidth()+" -> "+full_width_s); + System.err.println("XXX: full_height: "+full_height_o+" / "+txt_box_em.getHeight()+" -> "+full_height_s); + System.err.println("XXX: txt_scale: "+txt_scale); + System.err.println("XXX: txt_box_em "+txt_box_em); + System.err.println("XXX: txt_box_e2 "+txt_box_em2); + System.err.println("XXX: txt_box_rg "+txt_box_r); + once = false; + } + } + pmv.glPopMatrix(); + } + renderer.enable(gl, false); + } + static boolean once = true; + + @Override + public void dispose(final GLAutoDrawable drawable) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + crossHair.destroy(gl, getRegionRenderer()); + testObj.destroy(gl, getRegionRenderer()); + + autoDrawable = null; + screenshot.dispose(gl); + rRenderer.destroy(gl); + } + + /** Attach the input listener to the window */ + public void attachInputListenerTo(final GLWindow window) { + if ( null == keyAction ) { + keyAction = new KeyAction(); + window.addKeyListener(keyAction); + } + if ( null == mouseAction ) { + mouseAction = new MouseAction(); + window.addMouseListener(mouseAction); + } + } + + public void detachFrom(final GLWindow window) { + if ( null == keyAction ) { + return; + } + if ( null == mouseAction ) { + return; + } + window.removeGLEventListener(this); + window.removeKeyListener(keyAction); + window.removeMouseListener(mouseAction); + } + + public void printScreen(final GLAutoDrawable drawable, final String dir, final String tech, final String objName, final boolean exportAlpha) throws GLException, IOException { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw); + pw.printf("-%03dx%03d-Z%04d-T%04d-%s", drawable.getSurfaceWidth(), drawable.getSurfaceHeight(), (int)Math.abs(zTran), 0, objName); + + final String filename = dir + tech + sw +".png"; + if(screenshot.readPixels(drawable.getGL(), false)) { + screenshot.write(new File(filename)); + } + } + + int screenshot_num = 0; + + public void setIgnoreInput(final boolean v) { + ignoreInput = v; + } + public boolean getIgnoreInput() { + return ignoreInput; + } + + public class MouseAction implements MouseListener{ + + @Override + public void mouseClicked(final MouseEvent e) { + + } + + @Override + public void mouseEntered(final MouseEvent e) { + } + + @Override + public void mouseExited(final MouseEvent e) { + } + + @Override + public void mousePressed(final MouseEvent e) { + autoDrawable.invoke(false, new GLRunnable() { // avoid data-race + @Override + public boolean run(final GLAutoDrawable drawable) { + System.err.println("\n\nMouse: "+e); + + final RegionRenderer renderer = getRegionRenderer(); + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + pmv.glLoadIdentity(); + pmv.glTranslatef(xTran, yTran, zTran); + + // flip to GL window coordinates, origin bottom-left + final int[] viewport = renderer.getViewport(new int[4]); + final int glWinX = e.getX(); + final int glWinY = viewport[3] - e.getY() - 1; + + { + final float[] objPosC = crossHair.getBounds().getCenter(); + final int[] objWinPos = new int[2]; + System.err.println("\n\nCrossHair: "+crossHair); + if( crossHair.objToWinCoord(renderer, objPosC, objWinPos) ) { + System.err.println("CrossHair: Obj: Obj "+objPosC[0]+"/"+objPosC[1]+"/"+objPosC[1]+" -> Win "+objWinPos[0]+"/"+objWinPos[1]); + } + + final float[] objPos2 = new float[3]; + crossHair.winToObjCoord(renderer, objWinPos[0], objWinPos[1], objPos2); + System.err.println("CrossHair: Obj: Win "+objWinPos[0]+"/"+objWinPos[1]+" -> Obj "+objPos2[0]+"/"+objPos2[1]+"/"+objPos2[1]); + + final float[] winObjPos = new float[3]; + if( crossHair.winToObjCoord(renderer, glWinX, glWinY, winObjPos) ) { + // final float[] translate = crossHair.getTranslate(); + // final float[] objPosT = new float[] { objPosC[0]+translate[0], objPosC[1]+translate[1], objPosC[2]+translate[2] }; + final float dx = winObjPos[0] - objPosC[0]; + final float dy = winObjPos[1] - objPosC[1]; + // final float dz = winObjPos[2] - objPosT[2]; + if( !FloatUtil.isZero(dx, FloatUtil.EPSILON) || !FloatUtil.isZero(dy, FloatUtil.EPSILON) ) { + System.err.println("CrossHair: Move.1: Win "+glWinX+"/"+glWinY+" -> Obj "+winObjPos[0]+"/"+winObjPos[1]+"/"+winObjPos[1]+" -> diff "+dx+" / "+dy); + crossHair.translate(dx, dy, 0f); + } else { + System.err.println("CrossHair: Move.0: Win "+glWinX+"/"+glWinY+" -> Obj "+winObjPos[0]+"/"+winObjPos[1]+"/"+winObjPos[1]+" -> diff "+dx+" / "+dy); + } + } + + final int[] surfaceSize = new int[2]; + crossHair.getSurfaceSize(renderer, surfaceSize); + System.err.println("CrossHair: Size: Pixel "+surfaceSize[0]+" x "+surfaceSize[1]); + } + return true; + } } ); + + } + + @Override + public void mouseReleased(final MouseEvent e) { + } + + @Override + public void mouseMoved(final MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseDragged(final MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseWheelMoved(final MouseEvent e) { + // TODO Auto-generated method stub + + } + + } + + public class KeyAction implements KeyListener { + @Override + public void keyPressed(final KeyEvent arg0) { + if(ignoreInput) { + return; + } + + if(arg0.getKeyCode() == KeyEvent.VK_1){ + crossHair.translate(0f, 0f, -zTran/10f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_2){ + crossHair.translate(0f, 0f, zTran/10f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_UP){ + crossHair.translate(0f, crossHair.getHeight()/10f, 0f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_DOWN){ + crossHair.translate(0f, -crossHair.getHeight()/10f, 0f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_LEFT){ + crossHair.translate(-crossHair.getWidth()/10f, 0f, 0f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_RIGHT){ + crossHair.translate(crossHair.getWidth()/10f, 0f, 0f); + } + else if(arg0.getKeyCode() == KeyEvent.VK_0){ + // rotate(1); + } + else if(arg0.getKeyCode() == KeyEvent.VK_9){ + // rotate(-1); + } + else if(arg0.getKeyCode() == KeyEvent.VK_V) { + if(null != autoDrawable) { + autoDrawable.invoke(false, new GLRunnable() { + @Override + public boolean run(final GLAutoDrawable drawable) { + final GL gl = drawable.getGL(); + final int _i = gl.getSwapInterval(); + final int i; + switch(_i) { + case 0: i = 1; break; + case 1: i = -1; break; + case -1: i = 0; break; + default: i = 1; break; + } + gl.setSwapInterval(i); + + final GLAnimatorControl a = drawable.getAnimator(); + if( null != a ) { + a.resetFPSCounter(); + } + if(drawable instanceof FPSCounter) { + ((FPSCounter)drawable).resetFPSCounter(); + } + System.err.println("Swap Interval: "+_i+" -> "+i+" -> "+gl.getSwapInterval()); + return true; + } + }); + } + } + else if(arg0.getKeyCode() == KeyEvent.VK_S){ + if(null != autoDrawable) { + autoDrawable.invoke(false, new GLRunnable() { + @Override + public boolean run(final GLAutoDrawable drawable) { + try { + final String type = Region.getRenderModeString(renderModes); + printScreen(drawable, "./", "demo-"+type, "snap"+screenshot_num, false); + screenshot_num++; + } catch (final GLException e) { + e.printStackTrace(); + } catch (final IOException e) { + e.printStackTrace(); + } + return true; + } + }); + } + } + } + @Override + public void keyReleased(final KeyEvent arg0) {} + } +} |