Skip to content

Commit

Permalink
(#10, #109) curve path generation, .psdf support
Browse files Browse the repository at this point in the history
New Additions:
- Support for converting Path2D objects with curves and multiple subpaths to Pointf[] arrays (with optional `alternateIndexes` stored in paired `Point[]`)
- `DrawUtil#pointsOfPathWithAlt` specifically for getting both the points of the path and indicators of where curves/other path related functions are
- Support for writing/reading polygons with curves with .psdf files

Breaking Changes:
- `DrawUtil#pointsOfPath` and variants no longer throw exceptions on unclosed paths.

Bug Fixes:
- (minor) Fixed issue in `DrawUtil#createPath` where `altIndexes` was not checked for emptiness

Other Changes:
- Optimized usages of `Polygon2D#getPoints` to be less taxing for standard tasks
  • Loading branch information
lucasstarsz committed May 9, 2022
1 parent 8ae2bba commit 949d3af
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 32 deletions.
98 changes: 79 additions & 19 deletions src/main/java/tech/fastj/graphics/util/DrawUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import tech.fastj.graphics.Drawable;
import tech.fastj.graphics.game.Polygon2D;

import tech.fastj.logging.Log;

import tech.fastj.systems.collections.Pair;

import java.awt.*;
Expand Down Expand Up @@ -145,7 +147,7 @@ public static Path2D.Float createPath(Pointf[] pts) {
* @return The resulting {@code Path2D.Float}.
*/
public static Path2D.Float createPath(Pointf[] pts, Point[] altIndexes) {
if (altIndexes == null) {
if (altIndexes == null || altIndexes.length == 0) {
return createPath(pts);
}

Expand Down Expand Up @@ -436,8 +438,8 @@ public static Pointf[] createBox(float x, float y, Pointf size) {
}

/**
* Creates a {@code Pointf} array of 4 points, based on the specified location {@code Pointf} and size {@code
* Pointf}.
* Creates a {@code Pointf} array of 4 points, based on the specified location {@code Pointf} and size
* {@code Pointf}.
* <p>
* This creates an array with the following values:
* <pre>
Expand Down Expand Up @@ -478,8 +480,8 @@ public static Pointf[] createBox(Rectangle2D.Float r) {
}

/**
* Creates a {@code Pointf} array of 4 points, based on the specified {@code BufferedImage} and the location {@code
* Pointf}.
* Creates a {@code Pointf} array of 4 points, based on the specified {@code BufferedImage} and the location
* {@code Pointf}.
* <p>
* This creates an array with the following values:
* <pre>
Expand Down Expand Up @@ -541,8 +543,8 @@ public static Rectangle2D.Float createRect(Pointf[] pts) {
}

/**
* Creates a {@code Rectangle2D.Float} based on the specified {@code BufferedImage} and the location {@code
* Pointf}.
* Creates a {@code Rectangle2D.Float} based on the specified {@code BufferedImage} and the location
* {@code Pointf}.
*
* @param source The source for the width and height.
* @param translation The x and y locations.
Expand All @@ -568,40 +570,98 @@ public static Pointf centerOf(Pointf[] points) {

/**
* Gets a {@code Pointf} array that represents the points of the {@code Path2D.Float} parameter.
* <p>
* This method will record all curves and other points in the given path, without indication as to where they may
* be. Use {@link #pointsOfPathWithAlt(Path2D.Float)} to get an array of alternate indexes for the path which
* represent what actions were taken.
*
* @param path The path to get the points of.
* @return The resultant array of points.
*/
public static Pointf[] pointsOfPath(Path2D.Float path) {
List<Pointf> pointList = new ArrayList<>();
float[] coords = new float[2];
int numSubPaths = 0;
float[] coords = new float[6];

for (PathIterator pi = path.getPathIterator(null); !pi.isDone(); pi.next()) {
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO: {
pointList.add(new Pointf(coords[0], coords[1]));
break;
}
case PathIterator.SEG_CUBICTO: {
pointList.add(new Pointf(coords[0], coords[1]));
pointList.add(new Pointf(coords[2], coords[3]));
pointList.add(new Pointf(coords[4], coords[5]));
break;
}
case PathIterator.SEG_QUADTO: {
pointList.add(new Pointf(coords[0], coords[1]));
pointList.add(new Pointf(coords[2], coords[3]));
break;
}
case PathIterator.SEG_CLOSE: {
return pointList.toArray(new Pointf[0]);
}
}
}

return pointList.toArray(new Pointf[0]);
}

/**
* Gets a {@code Pointf} array that represents the points of the {@code Path2D.Float} parameter. as well as a
* {@code Point} array that indicates the location of curves and other {@link Path2D} iteration guides.
*
* @param path The path to get the points of.
* @return The resultant array of points.
*/
public static Pair<Pointf[], Point[]> pointsOfPathWithAlt(Path2D.Float path) {
List<Pointf> pointList = new ArrayList<>();
List<Point> alternateIndexes = new ArrayList<>();
float[] coords = new float[6];
int numSubPaths = 0;

PathIterator pi = path.getPathIterator(null);
while (!pi.isDone()) {
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO: {
pointList.add(new Pointf(coords[0], coords[1]));
numSubPaths++;
if (numSubPaths > 1) {
alternateIndexes.add(new Point(pointList.size() - 1, Polygon2D.MovePath));
}
break;
}
case PathIterator.SEG_LINETO: {
pointList.add(new Pointf(coords[0], coords[1]));
break;
}
case PathIterator.SEG_CUBICTO: {
alternateIndexes.add(new Point(pointList.size(), Polygon2D.BezierCurve));
pointList.add(new Pointf(coords[0], coords[1]));
pointList.add(new Pointf(coords[2], coords[3]));
pointList.add(new Pointf(coords[4], coords[5]));
break;
}
case PathIterator.SEG_QUADTO: {
alternateIndexes.add(new Point(pointList.size(), Polygon2D.QuadCurve));
pointList.add(new Pointf(coords[0], coords[1]));
pointList.add(new Pointf(coords[2], coords[3]));
break;
}
case PathIterator.SEG_CLOSE: {
if (numSubPaths > 1) {
throw new IllegalArgumentException("Path contains multiple sub-paths");
if (!pi.isDone()) {
Log.warn(DrawUtil.class, "tried to close path iterator before done");
break;
}

return pointList.toArray(new Pointf[0]);
}
default: {
throw new IllegalArgumentException("Path contains curves");
return Pair.of(pointList.toArray(new Pointf[0]), alternateIndexes.toArray(new Point[0]));
}
}
pi.next();
}

throw new IllegalArgumentException("Unclosed path");
return Pair.of(pointList.toArray(new Pointf[0]), alternateIndexes.toArray(new Point[0]));
}

/**
Expand Down Expand Up @@ -770,8 +830,8 @@ public static Color colorLerp(Color c, Color c1, float t1, float t2, float t3, f
* @param c The starting value.
* @param c1 The ending value.
* @param v The value representing the "result" of linear interpolation between the two {@code Color}s. This value
* is used to calculate inverse linear interpolation of the colors' {@code red}, {@code green}, {@code
* blue}, and {@code alpha} values.
* is used to calculate inverse linear interpolation of the colors' {@code red}, {@code green},
* {@code blue}, and {@code alpha} values.
* @return An array of floats containing the resulting inverse linear interpolations of the colors' {@code red},
* {@code green}, {@code blue}, and {@code alpha} values.
* @see Maths#inverseLerp(float, float, float)
Expand Down
22 changes: 13 additions & 9 deletions src/main/java/tech/fastj/resources/models/ObjUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,15 @@ public static Polygon2D[] parse(Path modelPath, List<String> lines) {
Pointf[] vertexesFromFaces = new Pointf[tokens.length - 1];
boolean isLastPolygonOutline = false;
int lastPolygonIndex = polygons.size() - 1;
Pointf[] lastPolygonPoints = polygons.get(lastPolygonIndex).getPoints();

for (int j = 0; j < tokens.length - 1; j++) {
int vertexesIndex = Integer.parseInt(tokens[j + 1].split("/")[0]);
vertexesFromFaces[j] = new Pointf(
vertexes.get(vertexesIndex - 1)[0],
vertexes.get(vertexesIndex - 1)[1]
);
isLastPolygonOutline = polygons.get(lastPolygonIndex).getPoints()[j].equals(vertexesFromFaces[j]);
isLastPolygonOutline = lastPolygonPoints[j].equals(vertexesFromFaces[j]);
}

if (isLastPolygonOutline) {
Expand Down Expand Up @@ -150,7 +151,7 @@ public static void write(Path destinationPath, Model2D model) {
writeObject(fileContents, i + 1);
writeMaterial(fileContents, polygon, i + 1, vertexCount);

vertexCount += polygon.getPoints().length;
vertexCount += polygon.getOriginalPoints().length;
}

try {
Expand All @@ -175,8 +176,8 @@ private static void writeMaterialLib(StringBuilder fileContents, Path materialPa

private static void writeVertexes(StringBuilder fileContents, Polygon2D polygon, int polygonIndex) {
float vertexSpace = polygonIndex / 1000f;
for (int j = 0; j < polygon.getPoints().length; j++) {
Pointf vertex = polygon.getPoints()[j];
Pointf[] polygonPoints = polygon.getPoints();
for (Pointf vertex : polygonPoints) {
fileContents.append(ParsingKeys.Vertex)
.append(' ')
.append(String.format("%4f", vertex.x))
Expand All @@ -192,9 +193,10 @@ private static void writeVertexTextures(StringBuilder fileContents, Polygon2D po
Pointf space = Pointf.subtract(polygon.getBound(Boundary.BottomRight), polygon.getBound(Boundary.TopLeft));
Pointf topLeft = polygon.getBound(Boundary.TopLeft);

for (int j = 0; j < polygon.getPoints().length; j++) {
float circleX = Maths.normalize(polygon.getPoints()[j].x - topLeft.x, 0f, space.x);
float circleY = Maths.normalize(polygon.getPoints()[j].y - topLeft.y, 0f, space.y);
Pointf[] polygonPoints = polygon.getPoints();
for (Pointf polygonPoint : polygonPoints) {
float circleX = Maths.normalize(polygonPoint.x - topLeft.x, 0f, space.x);
float circleY = Maths.normalize(polygonPoint.y - topLeft.y, 0f, space.y);
fileContents.append(ParsingKeys.VertexTexture)
.append(' ')
.append(String.format("%4f", circleX))
Expand Down Expand Up @@ -253,7 +255,8 @@ private static void writeOutlineMaterialUsage(StringBuilder fileContents, int po

private static void writeFaces(StringBuilder fileContents, Polygon2D polygon, int vertexCount) {
fileContents.append(ParsingKeys.ObjectFace);
for (int i = 1; i <= polygon.getPoints().length; i++) {
Pointf[] polygonPoints = polygon.getPoints();
for (int i = 1; i <= polygonPoints.length; i++) {
fileContents.append(' ')
.append(vertexCount + i)
.append('/')
Expand All @@ -264,7 +267,8 @@ private static void writeFaces(StringBuilder fileContents, Polygon2D polygon, in

private static void writeLines(StringBuilder fileContents, Polygon2D polygon, int vertexCount) {
fileContents.append(ParsingKeys.ObjectLine);
for (int i = 1; i <= polygon.getPoints().length; i++) {
Pointf[] polygonPoints = polygon.getPoints();
for (int i = 1; i <= polygonPoints.length; i++) {
fileContents.append(' ').append(vertexCount + i);
}
fileContents.append(LineSeparator);
Expand Down
38 changes: 34 additions & 4 deletions src/main/java/tech/fastj/resources/models/PsdfUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import tech.fastj.engine.CrashMessages;
import tech.fastj.engine.FastJEngine;

import tech.fastj.math.Point;
import tech.fastj.math.Pointf;
import tech.fastj.math.Transform2D;

Expand Down Expand Up @@ -51,6 +52,7 @@ public static Polygon2D[] parse(Path modelPath, List<String> lines) {
int polygonsIndex = 0;

List<Pointf> polygonPoints = new ArrayList<>();
List<Point> altIndexes = new ArrayList<>();
boolean shouldRender = Drawable.DefaultShouldRender;

RenderStyle renderStyle = Polygon2D.DefaultRenderStyle;
Expand Down Expand Up @@ -102,14 +104,18 @@ public static Polygon2D[] parse(Path modelPath, List<String> lines) {
shouldRender = parseShouldRender(tokens);
break;
}
case ParsingKeys.AlternateIndex: {
altIndexes.add(parseAltIndex(tokens));
break;
}
case ParsingKeys.MeshPoint: {
polygonPoints.add(parseMeshPoint(tokens));

// if end of polygon, add polygon to array
if (tokens.length == 4 && tokens[3].equals(";")) {
assert polygons != null;

polygons[polygonsIndex] = Polygon2D.create(polygonPoints.toArray(new Pointf[0]), null, shouldRender)
polygons[polygonsIndex] = Polygon2D.create(polygonPoints.toArray(new Pointf[0]), altIndexes.toArray(new Point[0]), shouldRender)
.withRenderStyle(renderStyle)
.withOutline(outlineStroke, outlineColor)
.withTransform(translation, rotation, scale)
Expand All @@ -123,6 +129,7 @@ public static Polygon2D[] parse(Path modelPath, List<String> lines) {

// reset values
polygonPoints.clear();
altIndexes.clear();
shouldRender = Drawable.DefaultShouldRender;

renderStyle = Polygon2D.DefaultRenderStyle;
Expand Down Expand Up @@ -320,6 +327,10 @@ private static boolean parseShouldRender(String[] tokens) {
return Boolean.parseBoolean(tokens[1]);
}

private static Point parseAltIndex(String[] tokens) {
return new Point(Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2]));
}

private static Pointf parseMeshPoint(String[] tokens) {
return new Pointf(Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]));
}
Expand All @@ -336,6 +347,7 @@ public static void write(Path destinationPath, Model2D model) {
writeOutline(fileContents, polygon.getOutlineStroke(), polygon.getOutlineColor());
writeTransform(fileContents, polygon.getTranslation(), polygon.getRotation(), polygon.getScale());
writeShouldRender(fileContents, polygon.shouldRender());
writeAltIndexes(fileContents, polygon);
writeMeshPoints(fileContents, polygon);

// if there are more objects after this object, then add a new line.
Expand Down Expand Up @@ -516,15 +528,32 @@ private static void writeShouldRender(StringBuilder fileContents, boolean should
.append(LineSeparator);
}

private static void writeAltIndexes(StringBuilder fileContents, Polygon2D polygon) {
if (polygon.getAlternateIndexes() == null) {
return;
}

for (int j = 0; j < polygon.getAlternateIndexes().length; j++) {
Point pt = polygon.getAlternateIndexes()[j];
fileContents.append(ParsingKeys.AlternateIndex)
.append(' ')
.append(pt.x)
.append(' ')
.append(pt.y)
.append(LineSeparator);
}
}

private static void writeMeshPoints(StringBuilder fileContents, Polygon2D polygon) {
for (int j = 0; j < polygon.getPoints().length; j++) {
Pointf pt = polygon.getPoints()[j];
Pointf[] pts = polygon.getPoints();
for (int j = 0; j < pts.length; j++) {
Pointf pt = pts[j];
fileContents.append(PsdfUtil.ParsingKeys.MeshPoint)
.append(' ')
.append(pt.x)
.append(' ')
.append(pt.y)
.append(j == polygon.getPoints().length - 1 ? " ;" : "")
.append(j == pts.length - 1 ? " ;" : "")
.append(LineSeparator);
}
}
Expand All @@ -550,5 +579,6 @@ private ParsingKeys() {
public static final String Transform = "tfm";
public static final String ShouldRender = "sr";
public static final String MeshPoint = "p";
public static final String AlternateIndex = "aip";
}
}

0 comments on commit 949d3af

Please sign in to comment.