From 6521afe5e9e7d9f384eb335ba22dd9f4352b628f Mon Sep 17 00:00:00 2001 From: lucasstarsz Date: Fri, 2 Jul 2021 16:41:12 -0400 Subject: [PATCH] (#10, #32) Added outline support, polygon builder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Additions: - Polygon2D now properly supports outlines (BasicStroke only) - revamped ModelUtil (PsdfUtil → ModelUtil) - Added outline, render style, and transform support - Refractored code to allow for later support for more file formats Breaking Changes: - Removed public access to all `Polygon2D` constructors. `Polygon2D`s must now be created using `Polygon2D#create`, which corresponds to a `Polygon2DBuilder` instance. - Renamed `PsdfUtil#loadPsdf` → `ModelUtil#loadModel` & `PsdfUtil#writePsdf` → `ModelUtil#writeModel` to better represent what the methods do. --- src/main/java/module-info.java | 1 + .../tech/fastj/graphics/game/Polygon2D.java | 10 +- .../fastj/graphics/game/Polygon2DBuilder.java | 6 +- .../tech/fastj/graphics/util/ModelUtil.java | 87 ++++ .../tech/fastj/graphics/util/PsdfUtil.java | 285 ----------- .../tech/fastj/graphics/util/io/PsdfUtil.java | 479 ++++++++++++++++++ .../util/io/SupportedFileFormats.java | 15 + 7 files changed, 590 insertions(+), 293 deletions(-) create mode 100644 src/main/java/tech/fastj/graphics/util/ModelUtil.java delete mode 100644 src/main/java/tech/fastj/graphics/util/PsdfUtil.java create mode 100644 src/main/java/tech/fastj/graphics/util/io/PsdfUtil.java create mode 100644 src/main/java/tech/fastj/graphics/util/io/SupportedFileFormats.java diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 532f6f2a..a31231aa 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -34,4 +34,5 @@ exports tech.fastj.systems.control; exports tech.fastj.systems.tags; exports tech.fastj.graphics.util.gradients; + exports tech.fastj.graphics.util.io; } diff --git a/src/main/java/tech/fastj/graphics/game/Polygon2D.java b/src/main/java/tech/fastj/graphics/game/Polygon2D.java index 82691556..154474b0 100644 --- a/src/main/java/tech/fastj/graphics/game/Polygon2D.java +++ b/src/main/java/tech/fastj/graphics/game/Polygon2D.java @@ -29,7 +29,7 @@ public class Polygon2D extends GameObject { /** {@link Paint} representing the default fill paint value as the color black. */ public static final Paint DefaultPaint = Color.black; /** {@link Stroke} representing the default outline stroke value as a 1px outline with sharp edges. */ - public static final Stroke DefaultOutlineStroke = new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 1.0f); + public static final BasicStroke DefaultOutlineStroke = new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 1.0f); /** {@link Color} representing the default outline color value as the color black. */ public static final Color DefaultOutlineColor = Color.black; @@ -38,7 +38,7 @@ public class Polygon2D extends GameObject { private RenderStyle renderStyle; private Paint fillPaint; private Color outlineColor; - private Stroke outlineStroke; + private BasicStroke outlineStroke; /** * {@code Polygon2D} constructor that takes in an array of points. @@ -134,7 +134,7 @@ public Color getOutlineColor() { return outlineColor; } - public Stroke getOutlineStroke() { + public BasicStroke getOutlineStroke() { return outlineStroke; } @@ -158,12 +158,12 @@ public Polygon2D setOutlineColor(Color newOutlineColor) { return this; } - public Polygon2D setOutlineStroke(Stroke newStroke) { + public Polygon2D setOutlineStroke(BasicStroke newStroke) { outlineStroke = newStroke; return this; } - public Polygon2D setOutline(Stroke newStroke, Color newOutlineColor) { + public Polygon2D setOutline(BasicStroke newStroke, Color newOutlineColor) { outlineStroke = newStroke; outlineColor = newOutlineColor; return this; diff --git a/src/main/java/tech/fastj/graphics/game/Polygon2DBuilder.java b/src/main/java/tech/fastj/graphics/game/Polygon2DBuilder.java index 361ec951..c7f8dfd8 100644 --- a/src/main/java/tech/fastj/graphics/game/Polygon2DBuilder.java +++ b/src/main/java/tech/fastj/graphics/game/Polygon2DBuilder.java @@ -4,9 +4,9 @@ import tech.fastj.graphics.RenderStyle; import tech.fastj.graphics.Transform2D; +import java.awt.BasicStroke; import java.awt.Color; import java.awt.Paint; -import java.awt.Stroke; import java.util.Objects; public class Polygon2DBuilder { @@ -16,7 +16,7 @@ public class Polygon2DBuilder { private final RenderStyle renderStyle; private Paint fillPaint = Polygon2D.DefaultPaint; - private Stroke outlineStroke = Polygon2D.DefaultOutlineStroke; + private BasicStroke outlineStroke = Polygon2D.DefaultOutlineStroke; private Color outlineColor = Polygon2D.DefaultOutlineColor; private Pointf translation = Transform2D.DefaultTranslation.copy(); @@ -34,7 +34,7 @@ public Polygon2DBuilder withFill(Paint fillPaint) { return this; } - public Polygon2DBuilder withOutline(Stroke outlineStroke, Color outlineColor) { + public Polygon2DBuilder withOutline(BasicStroke outlineStroke, Color outlineColor) { this.outlineStroke = Objects.requireNonNull(outlineStroke, "The outline stroke must not be null."); this.outlineColor = Objects.requireNonNull(outlineColor, "The outline color must not be null."); return this; diff --git a/src/main/java/tech/fastj/graphics/util/ModelUtil.java b/src/main/java/tech/fastj/graphics/util/ModelUtil.java new file mode 100644 index 00000000..3aaa0f87 --- /dev/null +++ b/src/main/java/tech/fastj/graphics/util/ModelUtil.java @@ -0,0 +1,87 @@ +package tech.fastj.graphics.util; + +import tech.fastj.engine.CrashMessages; +import tech.fastj.graphics.game.Model2D; +import tech.fastj.graphics.game.Polygon2D; +import tech.fastj.graphics.util.io.PsdfUtil; +import tech.fastj.graphics.util.io.SupportedFileFormats; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class ModelUtil { + + private static final Map, Polygon2D[]>> ModelParser = Map.of( + SupportedFileFormats.Psdf, PsdfUtil::parse + ); + + private static final Map> ModelWriter = Map.of( + SupportedFileFormats.Psdf, PsdfUtil::write + ); + + /** + * Gets a {@code Polygon2D} array, loaded from a {@code .psdf} file. + *

+ * This method allows the user to load an array of {@code Polygon2D}s from a single file, decreasing the amount of + * models that have to be programmed in. + *

+ * Furthermore, this allows for easy use of the {@code Model2D} class, allowing you to directly use the resulting + * array from this method to create a {@code Model2D} object. + * + * @param fileLocation Location of the file. + * @return An array of {@code Polygon2D}s. + */ + public static Polygon2D[] loadModel(Path fileLocation) { + if (!Files.exists(fileLocation, LinkOption.NOFOLLOW_LINKS)) { + throw new IllegalArgumentException("A file was not found at \"" + fileLocation.toAbsolutePath() + "\"."); + } + + String fileExtension = getFileExtension(fileLocation); + checkFileExtension(fileExtension); + + List lines; + try { + lines = Files.readAllLines(fileLocation); + } catch (IOException exception) { + throw new IllegalStateException( + CrashMessages.theGameCrashed("an issue while trying to parse file \"" + fileLocation.toAbsolutePath() + "\"."), + exception + ); + } + + return ModelParser.get(fileExtension).apply(lines); + } + + /** + * Writes a model file containing the current state of the {@code Polygon2D}s that make up the specified {@code + * Model2D}. + * + * @param destinationPath The destination path of the model file that will be written. + * @param model The {@code Model2D} that will be written to the file. + */ + public static void writeModel(Path destinationPath, Model2D model) { + String fileExtension = getFileExtension(destinationPath); + checkFileExtension(fileExtension); + ModelWriter.get(fileExtension).accept(destinationPath, model); + } + + private static String getFileExtension(Path filePath) { + return filePath.toString().substring(filePath.toString().lastIndexOf(".") + 1); + } + + private static void checkFileExtension(String fileExtension) { + if (SupportedFileFormats.valuesStream.noneMatch(fileFormat -> fileFormat.equalsIgnoreCase(fileExtension))) { + throw new IllegalArgumentException( + "Unsupported file extension \"" + fileExtension + "\"." + + System.lineSeparator() + + "This engine only supports files of the following extensions: " + SupportedFileFormats.valuesString + ); + } + } +} diff --git a/src/main/java/tech/fastj/graphics/util/PsdfUtil.java b/src/main/java/tech/fastj/graphics/util/PsdfUtil.java deleted file mode 100644 index 66342d98..00000000 --- a/src/main/java/tech/fastj/graphics/util/PsdfUtil.java +++ /dev/null @@ -1,285 +0,0 @@ -package tech.fastj.graphics.util; - -import tech.fastj.engine.CrashMessages; -import tech.fastj.engine.FastJEngine; -import tech.fastj.math.Pointf; -import tech.fastj.graphics.game.Model2D; -import tech.fastj.graphics.game.Polygon2D; -import tech.fastj.graphics.util.gradients.Gradients; -import tech.fastj.graphics.util.gradients.LinearGradientBuilder; -import tech.fastj.graphics.util.gradients.RadialGradientBuilder; - -import java.awt.Color; -import java.awt.LinearGradientPaint; -import java.awt.Paint; -import java.awt.RadialGradientPaint; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; - -public class PsdfUtil { - - private static final String PsdfReadErrorMessage = CrashMessages.theGameCrashed("a .psdf file reading error."); - private static final String PsdfWriteErrorMessage = CrashMessages.theGameCrashed("a .psdf file writing error."); - - /** - * Gets a {@code Polygon2D} array, loaded from a {@code .psdf} file. - *

- * This method allows the user to load an array of {@code Polygon2D}s from a single file, decreasing the amount of - * models that have to be programmed in. - *

- * Furthermore, this allows for easy use of the {@code Model2D} class, allowing you to directly use the resulting - * array from this method to create a {@code Model2D} object. - * - * @param fileLocation Location of the file. - * @return An array of {@code Polygon2D}s. - */ - public static Polygon2D[] loadPsdf(String fileLocation) { - // check for correct file extension - if (!fileLocation.substring(fileLocation.lastIndexOf(".") + 1).equalsIgnoreCase("psdf")) { - throw new IllegalArgumentException( - "Unsupported file type." - + System.lineSeparator() - + "This engine currently only supports files of the extension \".psdf\"." - ); - } - - return parsePsdf(fileLocation); - } - - /** - * Parses the content of the file at the location of the string into a {@code Polygon2D} array. - * - * @param fileLocation The location of the .psdf file. - * @return An array of {@code Polygon2D}s. - */ - private static Polygon2D[] parsePsdf(String fileLocation) { - try { - Polygon2D[] result = null; - List lines = Files.readAllLines(Path.of(fileLocation)); - List polygonPoints = new ArrayList<>(); - Paint paint = null; - boolean fillPolygon = false; - boolean renderPolygon = false; - int arrayLoc = 0; - - /* Checks through each line in the list, and sets variables based on the - * first word of each line. */ - for (String words : lines) { - String[] tokens = words.split("\\s+"); - switch (tokens[0]) { - case "amt": { // sets the total amount of polygons in the array - result = new Polygon2D[Integer.parseInt(tokens[1])]; - break; - } - case "c": { // set paint as Color - paint = new Color(Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2]), Integer.parseInt(tokens[3]), Integer.parseInt(tokens[4])); - break; - } - case "lg": { // set paint as LinearGradientPaint - LinearGradientBuilder linearGradientBuilder = Gradients.linearGradient( - new Pointf( - Float.parseFloat(tokens[1]), - Float.parseFloat(tokens[2]) - ), - new Pointf( - Float.parseFloat(tokens[3]), - Float.parseFloat(tokens[4]) - ) - ); - - int colorTokens = (tokens.length - 5); - for (int i = 5; i <= colorTokens + 1; i += 4) { - linearGradientBuilder.withColor( - new Color( - Integer.parseInt(tokens[i]), - Integer.parseInt(tokens[i + 1]), - Integer.parseInt(tokens[i + 2]), - Integer.parseInt(tokens[i + 3]) - ) - ); - } - - paint = linearGradientBuilder.build(); - break; - } - case "rg": { // set paint as RadialGradientPaint - RadialGradientBuilder radialGradientBuilder = Gradients.radialGradient( - new Pointf( - Float.parseFloat(tokens[1]), - Float.parseFloat(tokens[2]) - ), - Float.parseFloat(tokens[3]) - ); - - int colorCount = (tokens.length - 4); - for (int i = 4; i <= colorCount + 1; i += 4) { - radialGradientBuilder.withColor( - new Color( - Integer.parseInt(tokens[i]), - Integer.parseInt(tokens[i + 1]), - Integer.parseInt(tokens[i + 2]), - Integer.parseInt(tokens[i + 3]) - ) - ); - } - - paint = radialGradientBuilder.build(); - break; - } - case "f": { // set fill value - fillPolygon = Boolean.parseBoolean(tokens[1]); - break; - } - case "s": { // set show value - renderPolygon = Boolean.parseBoolean(tokens[1]); - break; - } - case "p": { // add point to point list - polygonPoints.add(new Pointf(Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]))); - - // if end of polygon, add polygon to array - if (tokens.length == 4 && tokens[3].equals(";")) { - assert result != null; - result[arrayLoc] = new Polygon2D(polygonPoints.toArray(new Pointf[0]), paint, fillPolygon, renderPolygon); - - // reset values - paint = null; - fillPolygon = false; - renderPolygon = false; - polygonPoints.clear(); - - // increase array location - arrayLoc++; - } - - break; - } - } - } - return result; - } catch (IOException e) { - throw new IllegalStateException("An issue occurred while trying to parse file \"" + fileLocation + "\".", e); - } - } - - /** - * Writes a {@code .psdf} file containing the current state of the {@code Polygon2D}s that make up the specified - * {@code Model2D}. - * - * @param destPath The destination path of the {@code .psdf} file that will be written. - * @param model The {@code Model2D} that will be written to the file. - */ - public static void writePsdf(String destPath, Model2D model) { - try { - String sep = System.lineSeparator(); - StringBuilder fileContents = new StringBuilder(); - - // write object count - fileContents.append("amt ").append(model.getPolygons().length).append(sep); - - for (int i = 0; i < model.getPolygons().length; i++) { - Polygon2D obj = model.getPolygons()[i]; - Paint paint = obj.getPaint(); - - // Write obj paint, fill, show - if (paint instanceof LinearGradientPaint) { - LinearGradientPaint linearGradientPaint = (LinearGradientPaint) paint; - fileContents.append("lg ") - .append(linearGradientPaint.getStartPoint().getX()) - .append(' ') - .append(linearGradientPaint.getStartPoint().getY()) - .append(' ') - .append(linearGradientPaint.getEndPoint().getX()) - .append(' ') - .append(linearGradientPaint.getEndPoint().getY()); - - for (Color color : linearGradientPaint.getColors()) { - if (color == null) { - continue; - } - - fileContents.append(' ') - .append(color.getRed()) - .append(' ') - .append(color.getGreen()) - .append(' ') - .append(color.getBlue()) - .append(' ') - .append(color.getAlpha()); - } - } else if (paint instanceof RadialGradientPaint) { - RadialGradientPaint radialGradientPaint = (RadialGradientPaint) paint; - fileContents.append("rg ") - .append(radialGradientPaint.getCenterPoint().getX()) - .append(' ') - .append(radialGradientPaint.getCenterPoint().getY()) - .append(' ') - .append(radialGradientPaint.getRadius()); - - for (Color color : radialGradientPaint.getColors()) { - if (color == null) { - continue; - } - - fileContents.append(' ') - .append(color.getRed()) - .append(' ') - .append(color.getGreen()) - .append(' ') - .append(color.getBlue()) - .append(' ') - .append(color.getAlpha()); - } - } else if (paint instanceof Color) { - Color color = (Color) paint; - fileContents.append("c ") - .append(color.getRed()) - .append(' ') - .append(color.getGreen()) - .append(' ') - .append(color.getBlue()) - .append(' ') - .append(color.getAlpha()); - } else { - FastJEngine.error( - CrashMessages.UnimplementedMethodError.errorMessage, - new UnsupportedOperationException( - "Writing paints other than LinearGradientPaint, RadialGradientPaint, or Color is not supported." - + System.lineSeparator() - + "Check the github to confirm you are on the latest version, as that version may have more implemented features." - ) - ); - } - - fileContents.append(sep) - .append("f ").append(obj.isFilled()) - .append(sep) - .append("s ").append(obj.shouldRender()) - .append(sep); - - // Write each point in object - for (int j = 0; j < obj.getPoints().length; j++) { - Pointf pt = obj.getPoints()[j]; - fileContents.append("p ") - .append((int) pt.x == pt.x ? Integer.toString((int) pt.x) : pt.x) - .append(' ') - .append((int) pt.y == pt.y ? Integer.toString((int) pt.y) : pt.y) - .append(j == obj.getPoints().length - 1 ? " ;" : "") - .append(sep); - } - - // if there are more objects after this object, then add a new line. - if (i != model.getPolygons().length - 1) fileContents.append(sep); - } - - Files.writeString(Paths.get(destPath), fileContents, StandardCharsets.UTF_8); - } catch (IOException e) { - FastJEngine.error(PsdfWriteErrorMessage, e); - } - } -} diff --git a/src/main/java/tech/fastj/graphics/util/io/PsdfUtil.java b/src/main/java/tech/fastj/graphics/util/io/PsdfUtil.java new file mode 100644 index 00000000..7f5da063 --- /dev/null +++ b/src/main/java/tech/fastj/graphics/util/io/PsdfUtil.java @@ -0,0 +1,479 @@ +package tech.fastj.graphics.util.io; + +import tech.fastj.engine.CrashMessages; +import tech.fastj.engine.FastJEngine; +import tech.fastj.math.Pointf; +import tech.fastj.graphics.Drawable; +import tech.fastj.graphics.RenderStyle; +import tech.fastj.graphics.Transform2D; +import tech.fastj.graphics.game.Model2D; +import tech.fastj.graphics.game.Polygon2D; +import tech.fastj.graphics.util.gradients.Gradients; +import tech.fastj.graphics.util.gradients.LinearGradientBuilder; +import tech.fastj.graphics.util.gradients.RadialGradientBuilder; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.LinearGradientPaint; +import java.awt.Paint; +import java.awt.RadialGradientPaint; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class PsdfUtil { + + private static final String LineSeparator = System.lineSeparator(); + + /** + * Parses the specified file contents into a {@code Polygon2D} array. + * + * @param lines The .psdf file, split line by line. + * @return An array of {@code Polygon2D}s. + */ + public static Polygon2D[] parse(List lines) { + Polygon2D[] polygons = null; + int polygonsIndex = 0; + + List polygonPoints = new ArrayList<>(); + boolean shouldRender = Drawable.DefaultShouldRender; + + RenderStyle renderStyle = Polygon2D.DefaultRenderStyle; + Paint fillPaint = Polygon2D.DefaultPaint; + BasicStroke outlineStroke = Polygon2D.DefaultOutlineStroke; + Color outlineColor = Polygon2D.DefaultOutlineColor; + + Pointf translation = Transform2D.DefaultTranslation.copy(); + float rotation = Transform2D.DefaultRotation; + Pointf scale = Transform2D.DefaultScale.copy(); + + for (String words : lines) { + String[] tokens = words.split("\\s+"); + switch (tokens[0]) { + case ParsingKeys.Amount: { + polygons = parsePolygonCount(tokens); + break; + } + case ParsingKeys.FillPaint_Color: + case ParsingKeys.FillPaint_LinearGradient: + case ParsingKeys.FillPaint_RadialGradient: { + fillPaint = parsePaint(tokens); + break; + } + case ParsingKeys.Outline_Stroke: { + outlineStroke = parseOutlineStroke(tokens); + break; + } + case ParsingKeys.Outline_Color: { + outlineColor = parseOutlineColor(tokens); + break; + } + case ParsingKeys.RenderStyle: { + renderStyle = parseRenderStyle(tokens); + break; + } + case ParsingKeys.Transform: { + translation = parseTranslation(tokens); + rotation = parseRotation(tokens); + scale = parseScale(tokens); + break; + } + case ParsingKeys.ShouldRender: { + shouldRender = parseShouldRender(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]), renderStyle, shouldRender) + .withFill(fillPaint) + .withOutline(outlineStroke, outlineColor) + .withTransform(translation, rotation, scale) + .build(); + + // reset values + polygonPoints.clear(); + shouldRender = Drawable.DefaultShouldRender; + + renderStyle = Polygon2D.DefaultRenderStyle; + fillPaint = Polygon2D.DefaultPaint; + outlineStroke = Polygon2D.DefaultOutlineStroke; + outlineColor = Polygon2D.DefaultOutlineColor; + + translation = Transform2D.DefaultTranslation.copy(); + rotation = Transform2D.DefaultRotation; + scale = Transform2D.DefaultScale.copy(); + + // increase array location + polygonsIndex++; + } + + break; + } + } + } + return polygons; + } + + private static Polygon2D[] parsePolygonCount(String[] tokens) { + return new Polygon2D[Integer.parseInt(tokens[1])]; + } + + private static Paint parsePaint(String[] tokens) { + switch (tokens[0]) { + case ParsingKeys.FillPaint_Color: { + return new Color(Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2]), Integer.parseInt(tokens[3]), Integer.parseInt(tokens[4])); + } + case ParsingKeys.FillPaint_LinearGradient: { + LinearGradientBuilder linearGradientBuilder = Gradients.linearGradient( + new Pointf( + Float.parseFloat(tokens[1]), + Float.parseFloat(tokens[2]) + ), + new Pointf( + Float.parseFloat(tokens[3]), + Float.parseFloat(tokens[4]) + ) + ); + + int colorTokens = (tokens.length - 5); + for (int i = 5; i <= colorTokens + 1; i += 4) { + linearGradientBuilder.withColor( + new Color( + Integer.parseInt(tokens[i]), + Integer.parseInt(tokens[i + 1]), + Integer.parseInt(tokens[i + 2]), + Integer.parseInt(tokens[i + 3]) + ) + ); + } + + return linearGradientBuilder.build(); + } + case ParsingKeys.FillPaint_RadialGradient: { + RadialGradientBuilder radialGradientBuilder = Gradients.radialGradient( + new Pointf( + Float.parseFloat(tokens[1]), + Float.parseFloat(tokens[2]) + ), + Float.parseFloat(tokens[3]) + ); + + int colorCount = (tokens.length - 4); + for (int i = 4; i <= colorCount + 1; i += 4) { + radialGradientBuilder.withColor( + new Color( + Integer.parseInt(tokens[i]), + Integer.parseInt(tokens[i + 1]), + Integer.parseInt(tokens[i + 2]), + Integer.parseInt(tokens[i + 3]) + ) + ); + } + + return radialGradientBuilder.build(); + } + default: { + throw new IllegalStateException("Invalid fill paint type: " + tokens[0]); + } + } + } + + private static BasicStroke parseOutlineStroke(String[] tokens) { + int basicStrokeCap; + switch (Integer.parseInt(tokens[2])) { + case BasicStroke.CAP_BUTT: { + basicStrokeCap = BasicStroke.CAP_BUTT; + break; + } + case BasicStroke.CAP_ROUND: { + basicStrokeCap = BasicStroke.CAP_ROUND; + break; + } + case BasicStroke.CAP_SQUARE: { + basicStrokeCap = BasicStroke.CAP_SQUARE; + break; + } + default: { + throw new IllegalStateException("Invalid BasicStroke Cap value: " + Integer.parseInt(tokens[2])); + } + } + + int basicStrokeJoinStyle; + switch (Integer.parseInt(tokens[3])) { + case BasicStroke.JOIN_MITER: { + basicStrokeJoinStyle = BasicStroke.JOIN_MITER; + break; + } + case BasicStroke.JOIN_ROUND: { + basicStrokeJoinStyle = BasicStroke.JOIN_ROUND; + break; + } + case BasicStroke.JOIN_BEVEL: { + basicStrokeJoinStyle = BasicStroke.JOIN_BEVEL; + break; + } + default: { + throw new IllegalStateException("Invalid BasicStroke Join value: " + Integer.parseInt(tokens[3])); + } + } + + return new BasicStroke(Integer.parseInt(tokens[1]), basicStrokeCap, basicStrokeJoinStyle, Integer.parseInt(tokens[4]), null, 0.0f); + } + + private static Color parseOutlineColor(String[] tokens) { + return new Color(Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2]), Integer.parseInt(tokens[3]), Integer.parseInt(tokens[4])); + } + + private static RenderStyle parseRenderStyle(String[] tokens) { + switch (tokens[1]) { + case ParsingKeys.RenderStyle_Fill: { + return RenderStyle.Fill; + } + case ParsingKeys.RenderStyle_Outline: { + return RenderStyle.Outline; + } + case ParsingKeys.RenderStyle_FillAndOutline: { + return RenderStyle.FillAndOutline; + } + default: { + throw new IllegalStateException("Invalid render style: " + tokens[1]); + } + } + } + + private static Pointf parseTranslation(String[] tokens) { + return new Pointf(Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])); + } + + private static float parseRotation(String[] tokens) { + return Float.parseFloat(tokens[3]); + } + + private static Pointf parseScale(String[] tokens) { + return new Pointf(Float.parseFloat(tokens[4]), Float.parseFloat(tokens[5])); + } + + private static boolean parseShouldRender(String[] tokens) { + return Boolean.parseBoolean(tokens[1]); + } + + private static Pointf parseMeshPoint(String[] tokens) { + return new Pointf(Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])); + } + + public static void write(Path destinationPath, Model2D model) { + StringBuilder fileContents = new StringBuilder(); + writePolygonAmount(fileContents, model.getPolygons()); + + for (int i = 0; i < model.getPolygons().length; i++) { + Polygon2D polygon = model.getPolygons()[i]; + + writeRenderStyle(fileContents, polygon.getRenderStyle()); + writeFill(fileContents, polygon.getFill()); + writeOutline(fileContents, polygon.getOutlineStroke(), polygon.getOutlineColor()); + writeTransform(fileContents, polygon.getTranslation(), polygon.getRotation(), polygon.getScale()); + writeShouldRender(fileContents, polygon.shouldRender()); + writeMeshPoints(fileContents, polygon); + + // if there are more objects after this object, then add a new line. + if (i != model.getPolygons().length - 1) { + fileContents.append(LineSeparator); + } + } + + try { + Files.writeString(destinationPath, fileContents, StandardCharsets.UTF_8); + } catch (IOException exception) { + throw new IllegalStateException(CrashMessages.theGameCrashed("a .psdf file writing error."), exception); + } + } + + private static void writePolygonAmount(StringBuilder fileContents, Polygon2D[] polygons) { + fileContents.append(PsdfUtil.ParsingKeys.Amount) + .append(' ') + .append(polygons.length) + .append(LineSeparator); + } + + private static void writeRenderStyle(StringBuilder fileContents, RenderStyle renderStyle) { + fileContents.append(PsdfUtil.ParsingKeys.RenderStyle).append(' '); + switch (renderStyle) { + case Fill: { + fileContents.append(PsdfUtil.ParsingKeys.RenderStyle_Fill); + } + case Outline: { + fileContents.append(PsdfUtil.ParsingKeys.RenderStyle_Outline); + } + case FillAndOutline: { + fileContents.append(PsdfUtil.ParsingKeys.RenderStyle_FillAndOutline); + } + } + fileContents.append(LineSeparator); + } + + private static void writeFill(StringBuilder fileContents, Paint paint) { + if (paint instanceof LinearGradientPaint) { + writeFill_LinearGradient(fileContents, (LinearGradientPaint) paint); + } else if (paint instanceof RadialGradientPaint) { + writeFill_RadialGradient(fileContents, (RadialGradientPaint) paint); + } else if (paint instanceof Color) { + writeFill_Color(fileContents, (Color) paint); + } else { + FastJEngine.error( + CrashMessages.UnimplementedMethodError.errorMessage, + new UnsupportedOperationException( + "Writing paints other than LinearGradientPaint, RadialGradientPaint, or Color is not supported." + + System.lineSeparator() + + "Check the github to confirm you are on the latest version, as that version may have more implemented features." + ) + ); + } + } + + private static void writeFill_LinearGradient(StringBuilder fileContents, LinearGradientPaint linearGradientPaint) { + fileContents.append(PsdfUtil.ParsingKeys.FillPaint_LinearGradient) + .append(' ') + .append(linearGradientPaint.getStartPoint().getX()) + .append(' ') + .append(linearGradientPaint.getStartPoint().getY()) + .append(' ') + .append(linearGradientPaint.getEndPoint().getX()) + .append(' ') + .append(linearGradientPaint.getEndPoint().getY()); + + for (Color color : linearGradientPaint.getColors()) { + if (color == null) { + continue; + } + + fileContents.append(' ') + .append(color.getRed()) + .append(' ') + .append(color.getGreen()) + .append(' ') + .append(color.getBlue()) + .append(' ') + .append(color.getAlpha()); + } + } + + private static void writeFill_RadialGradient(StringBuilder fileContents, RadialGradientPaint radialGradientPaint) { + fileContents.append(PsdfUtil.ParsingKeys.FillPaint_RadialGradient) + .append(' ') + .append(radialGradientPaint.getCenterPoint().getX()) + .append(' ') + .append(radialGradientPaint.getCenterPoint().getY()) + .append(' ') + .append(radialGradientPaint.getRadius()); + + for (Color color : radialGradientPaint.getColors()) { + if (color == null) { + continue; + } + + fileContents.append(' ') + .append(color.getRed()) + .append(' ') + .append(color.getGreen()) + .append(' ') + .append(color.getBlue()) + .append(' ') + .append(color.getAlpha()); + } + } + + private static void writeFill_Color(StringBuilder fileContents, Color color) { + fileContents.append(PsdfUtil.ParsingKeys.FillPaint_Color) + .append(' ') + .append(color.getRed()) + .append(' ') + .append(color.getGreen()) + .append(' ') + .append(color.getBlue()) + .append(' ') + .append(color.getAlpha()); + } + + private static void writeOutline(StringBuilder fileContents, BasicStroke outlineStroke, Color outlineColor) { + fileContents.append(LineSeparator) + .append(PsdfUtil.ParsingKeys.Outline_Stroke) + .append(' ') + .append(outlineStroke.getLineWidth()) + .append(' ') + .append(outlineStroke.getEndCap()) + .append(' ') + .append(outlineStroke.getLineJoin()) + .append(' ') + .append(outlineStroke.getMiterLimit()) + .append(LineSeparator) + .append(PsdfUtil.ParsingKeys.Outline_Color) + .append(' ') + .append(outlineColor.getRed()) + .append(' ') + .append(outlineColor.getGreen()) + .append(' ') + .append(outlineColor.getBlue()) + .append(' ') + .append(outlineColor.getAlpha()) + .append(LineSeparator); + } + + private static void writeTransform(StringBuilder fileContents, Pointf translation, float rotation, Pointf scale) { + fileContents.append(PsdfUtil.ParsingKeys.Transform) + .append(' ') + .append(translation.x) + .append(' ') + .append(translation.y) + .append(' ') + .append(rotation) + .append(' ') + .append(scale.x) + .append(' ') + .append(scale.y) + .append(LineSeparator); + } + + private static void writeShouldRender(StringBuilder fileContents, boolean shouldRender) { + fileContents.append(PsdfUtil.ParsingKeys.ShouldRender) + .append(' ') + .append(shouldRender) + .append(LineSeparator); + } + + private static void writeMeshPoints(StringBuilder fileContents, Polygon2D polygon) { + for (int j = 0; j < polygon.getPoints().length; j++) { + Pointf pt = polygon.getPoints()[j]; + fileContents.append(PsdfUtil.ParsingKeys.MeshPoint) + .append(' ') + .append(pt.x) + .append(' ') + .append(pt.y) + .append(j == polygon.getPoints().length - 1 ? " ;" : "") + .append(LineSeparator); + } + } + + /** Keys for parsing .psdf files. */ + public static class ParsingKeys { + public static final String Amount = "amt"; + public static final String RenderStyle = "rs"; + public static final String RenderStyle_Fill = "fill"; + public static final String RenderStyle_Outline = "outline"; + public static final String RenderStyle_FillAndOutline = "filloutline"; + public static final String FillPaint_Color = "c"; + public static final String FillPaint_LinearGradient = "lg"; + public static final String FillPaint_RadialGradient = "rg"; + public static final String Outline_Stroke = "otls"; + public static final String Outline_Color = "otlc"; + public static final String Transform = "tfm"; + public static final String ShouldRender = "sr"; + public static final String MeshPoint = "p"; + } +} diff --git a/src/main/java/tech/fastj/graphics/util/io/SupportedFileFormats.java b/src/main/java/tech/fastj/graphics/util/io/SupportedFileFormats.java new file mode 100644 index 00000000..b33adfd5 --- /dev/null +++ b/src/main/java/tech/fastj/graphics/util/io/SupportedFileFormats.java @@ -0,0 +1,15 @@ +package tech.fastj.graphics.util.io; + +import java.util.Arrays; +import java.util.stream.Stream; + +public class SupportedFileFormats { + public static final String Psdf = "psdf"; + + public static final String[] values = { + Psdf + }; + + public static final Stream valuesStream = Arrays.stream(values); + public static final String valuesString = Arrays.toString(values); +}