diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 519fa53b..ee4df4f8 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -59,5 +59,6 @@ exports tech.fastj.systems.audio.state; exports tech.fastj.systems.behaviors; exports tech.fastj.systems.control; + exports tech.fastj.systems.collections; exports tech.fastj.systems.tags; } diff --git a/src/main/java/tech/fastj/graphics/game/Polygon2D.java b/src/main/java/tech/fastj/graphics/game/Polygon2D.java index 96c27bd1..7b31ea7a 100644 --- a/src/main/java/tech/fastj/graphics/game/Polygon2D.java +++ b/src/main/java/tech/fastj/graphics/game/Polygon2D.java @@ -1,5 +1,6 @@ package tech.fastj.graphics.game; +import tech.fastj.math.Point; import tech.fastj.math.Pointf; import tech.fastj.graphics.Drawable; @@ -14,6 +15,7 @@ import java.awt.Paint; import java.awt.Stroke; import java.awt.geom.AffineTransform; +import java.awt.geom.Path2D; import java.util.Arrays; import java.util.Objects; @@ -34,7 +36,12 @@ public class Polygon2D extends GameObject { /** {@link Color} representing the default outline color value as the color black. */ public static final Color DefaultOutlineColor = Color.black; + public static final int QuadCurve = 1; + public static final int BezierCurve = 2; + public static final int MovePath = 3; + private Pointf[] originalPoints; + private Point[] alternateIndexes; private RenderStyle renderStyle; private Paint fillPaint; @@ -44,15 +51,18 @@ public class Polygon2D extends GameObject { /** * {@code Polygon2D} constructor that takes in an array of points. *

- * This constructor defaults the fill paint to {@link #DefaultFill}, the outline stroke to {@link - * #DefaultOutlineStroke}, the outline color to {@link #DefaultOutlineColor}, the render style to {@link - * #DefaultRenderStyle}, and the {@code shouldRender} boolean to {@link Drawable#DefaultShouldRender}. + * If needed, an array of alternate point indexes can be supplied to model curves and other {@link Path2D} options. + *

+ * This constructor defaults the fill paint to {@link #DefaultFill}, the outline stroke to + * {@link #DefaultOutlineStroke}, the outline color to {@link #DefaultOutlineColor}, the render style to + * {@link #DefaultRenderStyle}, and the {@code shouldRender} boolean to {@link Drawable#DefaultShouldRender}. * * @param points {@code Pointf} array that defines the points for the polygon. */ - protected Polygon2D(Pointf[] points) { + protected Polygon2D(Pointf[] points, Point[] altIndexes) { originalPoints = points; - setCollisionPath(DrawUtil.createPath(originalPoints)); + alternateIndexes = altIndexes; + setCollisionPath(DrawUtil.createPath(originalPoints, alternateIndexes)); setFill(DefaultFill); setOutlineStroke(DefaultOutlineStroke); @@ -68,12 +78,23 @@ protected Polygon2D(Pointf[] points) { * @return A {@code Polygon2DBuilder} instance for creating a {@code Polygon2D}. */ public static Polygon2DBuilder create(Pointf[] points) { - return new Polygon2DBuilder(points, DefaultRenderStyle, Drawable.DefaultShouldRender); + return new Polygon2DBuilder(points, null, DefaultRenderStyle, Drawable.DefaultShouldRender); } /** - * Gets a {@link Polygon2DBuilder} instance while setting the eventual {@link Polygon2D}'s {@code points} and {@code - * shouldRender} fields. + * Gets a {@link Polygon2DBuilder} instance while setting the eventual {@link Polygon2D}'s {@code points} field. + *

+ * + * @param points {@code Pointf} array that defines the points for the {@code Polygon2D}. + * @return A {@code Polygon2DBuilder} instance for creating a {@code Polygon2D}. + */ + public static Polygon2DBuilder create(Pointf[] points, Point[] altIndexes) { + return new Polygon2DBuilder(points, altIndexes, DefaultRenderStyle, Drawable.DefaultShouldRender); + } + + /** + * Gets a {@link Polygon2DBuilder} instance while setting the eventual {@link Polygon2D}'s {@code points} and + * {@code shouldRender} fields. *

* * @param points {@code Pointf} array that defines the points for the {@code Polygon2D}. @@ -81,12 +102,12 @@ public static Polygon2DBuilder create(Pointf[] points) { * @return A {@code Polygon2DBuilder} instance for creating a {@code Polygon2D}. */ public static Polygon2DBuilder create(Pointf[] points, boolean shouldRender) { - return new Polygon2DBuilder(points, DefaultRenderStyle, shouldRender); + return new Polygon2DBuilder(points, null, DefaultRenderStyle, shouldRender); } /** - * Gets a {@link Polygon2DBuilder} instance while setting the eventual {@link Polygon2D}'s {@code points} and {@code - * renderStyle} fields. + * Gets a {@link Polygon2DBuilder} instance while setting the eventual {@link Polygon2D}'s {@code points} and + * {@code renderStyle} fields. *

* * @param points {@code Pointf} array that defines the points for the {@code Polygon2D}. @@ -94,12 +115,12 @@ public static Polygon2DBuilder create(Pointf[] points, boolean shouldRender) { * @return A {@code Polygon2DBuilder} instance for creating a {@code Polygon2D}. */ public static Polygon2DBuilder create(Pointf[] points, RenderStyle renderStyle) { - return new Polygon2DBuilder(points, renderStyle, Drawable.DefaultShouldRender); + return new Polygon2DBuilder(points, null, renderStyle, Drawable.DefaultShouldRender); } /** - * Gets a {@link Polygon2DBuilder} instance while setting the eventual {@link Polygon2D}'s {@code points}, {@code - * renderStyle}, and {@code shouldRender} fields. + * Gets a {@link Polygon2DBuilder} instance while setting the eventual {@link Polygon2D}'s {@code points}, + * {@code renderStyle}, and {@code shouldRender} fields. *

* * @param points {@code Pointf} array that defines the points for the {@code Polygon2D}. @@ -108,7 +129,7 @@ public static Polygon2DBuilder create(Pointf[] points, RenderStyle renderStyle) * @return A {@code Polygon2DBuilder} instance for creating a {@code Polygon2D}. */ public static Polygon2DBuilder create(Pointf[] points, RenderStyle renderStyle, boolean shouldRender) { - return new Polygon2DBuilder(points, renderStyle, shouldRender); + return new Polygon2DBuilder(points, null, renderStyle, shouldRender); } /** @@ -118,7 +139,17 @@ public static Polygon2DBuilder create(Pointf[] points, RenderStyle renderStyle, * @return The resulting {@code Polygon2D}. */ public static Polygon2D fromPoints(Pointf[] points) { - return new Polygon2DBuilder(points, DefaultRenderStyle, Drawable.DefaultShouldRender).build(); + return new Polygon2DBuilder(points, null, DefaultRenderStyle, Drawable.DefaultShouldRender).build(); + } + + /** + * Creates a {@code Polygon2D} from the specified points. + * + * @param points {@code Pointf} array that defines the points for the {@code Polygon2D}. + * @return The resulting {@code Polygon2D}. + */ + public static Polygon2D fromPoints(Pointf[] points, Point[] altIndexes) { + return new Polygon2DBuilder(points, altIndexes, DefaultRenderStyle, Drawable.DefaultShouldRender).build(); } /** @@ -130,6 +161,16 @@ public Pointf[] getOriginalPoints() { return originalPoints; } + /** + * Gets the polygon's alternate indexes, which are associated with the + * {@link #getOriginalPoints() original point set.} + * + * @return The original set of points for this polygon, as a {@code Pointf[]}. + */ + public Point[] getAlternateIndexes() { + return alternateIndexes; + } + /** * Gets the polygon's fill paint. * @@ -245,7 +286,40 @@ public Pointf[] getPoints() { */ public void modifyPoints(Pointf[] points, boolean resetTranslation, boolean resetRotation, boolean resetScale) { originalPoints = points; + alternateIndexes = null; + + resetTransform(resetTranslation, resetRotation, resetScale); + setCollisionPath(DrawUtil.createPath(originalPoints)); + } + + /** + * Replaces the current point array with the parameter point array and alternate indexes. + *

+ * This does not reset the rotation, scale, or location of the original, unless specified with the third, fourth, + * and fifth parameters. + * + * @param points {@code Pointf} array that will replace the current points of the polygon. + * @param altIndexes {@code Point} array that will replace the current alternate indexes of the polygon. + * @param resetTranslation Boolean to determine if the translation should be reset. + * @param resetRotation Boolean to determine if the rotation should be reset. + * @param resetScale Boolean to determine if the scale should be reset. + */ + public void modifyPoints(Pointf[] points, Point[] altIndexes, boolean resetTranslation, boolean resetRotation, boolean resetScale) { + originalPoints = points; + alternateIndexes = altIndexes; + + resetTransform(resetTranslation, resetRotation, resetScale); + setCollisionPath(DrawUtil.createPath(originalPoints, altIndexes)); + } + /** + * Resets the {@code Polygon2D}'s transform. + * + * @param resetTranslation Boolean to determine if the translation should be reset. + * @param resetRotation Boolean to determine if the rotation should be reset. + * @param resetScale Boolean to determine if the scale should be reset. + */ + private void resetTransform(boolean resetTranslation, boolean resetRotation, boolean resetScale) { if (resetTranslation && resetRotation && resetScale) { transform.reset(); } else { @@ -259,8 +333,6 @@ public void modifyPoints(Pointf[] points, boolean resetTranslation, boolean rese transform.resetScale(); } } - - setCollisionPath(DrawUtil.createPath(originalPoints)); } @Override diff --git a/src/main/java/tech/fastj/graphics/game/Polygon2DBuilder.java b/src/main/java/tech/fastj/graphics/game/Polygon2DBuilder.java index 016cdcee..cb3929a8 100644 --- a/src/main/java/tech/fastj/graphics/game/Polygon2DBuilder.java +++ b/src/main/java/tech/fastj/graphics/game/Polygon2DBuilder.java @@ -1,5 +1,6 @@ package tech.fastj.graphics.game; +import tech.fastj.math.Point; import tech.fastj.math.Pointf; import tech.fastj.math.Transform2D; @@ -12,6 +13,7 @@ public class Polygon2DBuilder { private final Pointf[] points; + private final Point[] altIndexes; private final boolean shouldRender; private final RenderStyle renderStyle; @@ -31,8 +33,9 @@ public class Polygon2DBuilder { * @param renderStyle The {@code RenderStyle} to use for the resulting {@code Polygon2D}. * @param shouldRender The "should render" {@code boolean} to use for the resulting {@code Polygon2D}. */ - Polygon2DBuilder(Pointf[] points, RenderStyle renderStyle, boolean shouldRender) { + Polygon2DBuilder(Pointf[] points, Point[] altIndexes, RenderStyle renderStyle, boolean shouldRender) { this.points = Objects.requireNonNull(points, "The array of points must not be null."); + this.altIndexes = altIndexes; this.renderStyle = Objects.requireNonNull(renderStyle, "The render style must not be null."); this.shouldRender = shouldRender; } @@ -85,7 +88,7 @@ public Polygon2DBuilder withTransform(Pointf translation, float rotation, Pointf * @return The resulting {@code Polygon2D}. */ public Polygon2D build() { - return (Polygon2D) new Polygon2D(points) + return (Polygon2D) new Polygon2D(points, altIndexes) .setOutlineStroke(outlineStroke) .setOutlineColor(outlineColor) .setRenderStyle(renderStyle) diff --git a/src/main/java/tech/fastj/graphics/util/DrawUtil.java b/src/main/java/tech/fastj/graphics/util/DrawUtil.java index 64e8239f..201f6f77 100644 --- a/src/main/java/tech/fastj/graphics/util/DrawUtil.java +++ b/src/main/java/tech/fastj/graphics/util/DrawUtil.java @@ -1,11 +1,14 @@ package tech.fastj.graphics.util; import tech.fastj.math.Maths; +import tech.fastj.math.Point; import tech.fastj.math.Pointf; import tech.fastj.graphics.Drawable; import tech.fastj.graphics.game.Polygon2D; +import tech.fastj.systems.collections.Pair; + import java.awt.*; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; @@ -135,6 +138,52 @@ public static Path2D.Float createPath(Pointf[] pts) { return p; } + /** + * Creates a {@code Path2D.Float} based on the specified {@code Pointf} array. + * + * @param pts The {@code Pointf} array to create the {@code Path2D.Float} from. + * @return The resulting {@code Path2D.Float}. + */ + public static Path2D.Float createPath(Pointf[] pts, Point[] altIndexes) { + if (altIndexes == null) { + return createPath(pts); + } + + Path2D.Float p = new Path2D.Float(); + + p.moveTo(pts[0].x, pts[0].y); + for (int i = 1, ai = 0; i < pts.length; i++) { + if (altIndexes[ai].x == i) { + switch (altIndexes[ai++].y) { + case Polygon2D.MovePath: { + p.moveTo(pts[i].x, pts[i].y); + break; + } + case Polygon2D.QuadCurve: { + p.quadTo(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y); + i += 1; + break; + } + case Polygon2D.BezierCurve: { + p.curveTo(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, pts[i + 2].x, pts[i + 2].y); + i += 2; + break; + } + default: { + throw new UnsupportedOperationException( + "No known path option for " + altIndexes[ai - 1].y + " on index " + i + " of the path." + ); + } + } + } else { + p.lineTo(pts[i].x, pts[i].y); + } + } + p.closePath(); + + return p; + } + /** * Checks for equality in length and point values between two {@link Path2D} objects. *

@@ -254,6 +303,45 @@ private static boolean mGradientEquals(MultipleGradientPaint mGradientPaint1, Mu && Arrays.equals(mGradientPaint1.getFractions(), mGradientPaint2.getFractions()); } + public static Pair createCircle(float x, float y, float radius) { + return createCircle(new Pointf(x, y), radius); + } + + public static Pair createCircle(float xy, float radius) { + return createCircle(new Pointf(xy), radius); + } + + public static Pair createCircle(Pointf center, float radius) { + float curveOffset = (float) ((4f / 3f) * (Math.sqrt(2) - 1) * radius); + return Pair.of( + new Pointf[]{ + Pointf.subtract(center, radius, 0f), + + Pointf.subtract(center, radius, curveOffset), + Pointf.subtract(center, curveOffset, radius), + Pointf.subtract(center, 0f, radius), + + Pointf.subtract(center, -curveOffset, radius), + Pointf.add(center, radius, -curveOffset), + Pointf.add(center, radius, 0f), + + Pointf.add(center, radius, curveOffset), + Pointf.add(center, curveOffset, radius), + Pointf.add(center, 0f, radius), + + Pointf.add(center, -curveOffset, radius), + Pointf.subtract(center, radius, -curveOffset), + Pointf.subtract(center, radius, 0f) + }, + new Point[]{ + new Point(1, Polygon2D.BezierCurve), + new Point(4, Polygon2D.BezierCurve), + new Point(7, Polygon2D.BezierCurve), + new Point(10, Polygon2D.BezierCurve) + } + ); + } + /** * Creates a {@code Pointf} array of 4 points, based on the specified x, y, width, and height floats. *

diff --git a/src/main/java/tech/fastj/systems/collections/Pair.java b/src/main/java/tech/fastj/systems/collections/Pair.java new file mode 100644 index 00000000..7c4a22c7 --- /dev/null +++ b/src/main/java/tech/fastj/systems/collections/Pair.java @@ -0,0 +1,53 @@ +package tech.fastj.systems.collections; + +import java.util.Objects; + +/** + * 2-tuple generic class. + *

+ * This implementation does not provide comparisons or well-established, veritable solutions for tuples. It only + * provides the necessities to easily return two values at once. If you would like a well-made tuple system, go use + * Apache Commons' libraries. + * + * @param The type for the left parameter of the pair. + * @param The type for the right parameter of the pair. + */ +public class Pair { + + private final L left; + private final R right; + + public Pair(L left, R right) { + this.left = Objects.requireNonNull(left); + this.right = Objects.requireNonNull(right); + } + + public L getLeft() { + return left; + } + + public R getRight() { + return right; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + Pair pair = (Pair) other; + return left.equals(pair.left) && right.equals(pair.right); + } + + @Override + public int hashCode() { + return Objects.hash(left, right); + } + + public static Pair of(L left, R right) { + return new Pair<>(left, right); + } +}