diff options
Diffstat (limited to 'src/jogl/classes/com')
8 files changed, 416 insertions, 213 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 ; |