Skip to content

Commit

Permalink
(#10, #113) Added .obj and .mtl parsing
Browse files Browse the repository at this point in the history
Other Changes:
- The .obj parser handles multiple files where internal file paths are based on the starting file path.
  As a result, the file path is passed to the parsers as well as to the writers.
  • Loading branch information
lucasstarsz committed Sep 12, 2021
1 parent 8651abd commit 25dbd12
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 24 deletions.
29 changes: 9 additions & 20 deletions src/main/java/tech/fastj/resources/models/ModelUtil.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
package tech.fastj.resources.models;

import tech.fastj.engine.CrashMessages;
import tech.fastj.graphics.game.Model2D;
import tech.fastj.graphics.game.Polygon2D;

import tech.fastj.resources.files.FileUtil;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.BiFunction;

public class ModelUtil {

private ModelUtil() {
throw new java.lang.IllegalStateException();
}

private static final Map<String, Function<List<String>, Polygon2D[]>> ModelParser = Map.of(
private static final Map<String, BiFunction<Path, List<String>, Polygon2D[]>> ModelParser = Map.of(
SupportedModelFormats.Psdf, PsdfUtil::parse,
SupportedModelFormats.Obj, ObjUtil::parse
);
Expand All @@ -41,28 +39,19 @@ private ModelUtil() {
* 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.
* @param modelPath File location of the model.
* @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() + "\".");
public static Polygon2D[] loadModel(Path modelPath) {
if (!Files.exists(modelPath, LinkOption.NOFOLLOW_LINKS)) {
throw new IllegalArgumentException("A file was not found at \"" + modelPath.toAbsolutePath() + "\".");
}

String fileExtension = FileUtil.getFileExtension(fileLocation);
String fileExtension = FileUtil.getFileExtension(modelPath);
checkFileExtension(fileExtension);

List<String> 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);
List<String> lines = FileUtil.readFileLines(modelPath);
return ModelParser.get(fileExtension).apply(modelPath, lines);
}

/**
Expand Down
91 changes: 90 additions & 1 deletion src/main/java/tech/fastj/resources/models/MtlUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import tech.fastj.graphics.Boundary;
import tech.fastj.graphics.game.Model2D;
import tech.fastj.graphics.game.Polygon2D;
import tech.fastj.graphics.textures.Textures;
import tech.fastj.graphics.util.DrawUtil;

import tech.fastj.resources.files.FileUtil;
import tech.fastj.resources.images.ImageResource;
Expand All @@ -25,6 +27,7 @@
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

public class MtlUtil {

Expand All @@ -34,6 +37,93 @@ private MtlUtil() {
throw new java.lang.IllegalStateException();
}

public static void parse(Polygon2D polygon, Path materialPath, String materialName, boolean isFill) {
if (materialName.isBlank()) {
return;
}

List<String> lines = FileUtil.readFileLines(materialPath);
int materialIndex = lines.indexOf(
lines.stream()
.filter(line -> line.startsWith(ParsingKeys.NewMaterial + " " + materialName))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Couldn't find material \"" + materialName + "\" in file \"" + materialPath.toAbsolutePath() + "\"."))
);

for (int i = materialIndex + 1; i < lines.size(); i++) {
String[] tokens = lines.get(i).split("\\s+");
switch (tokens[0]) {
case ParsingKeys.AmbientColor: {
parseColor(
polygon,
Float.parseFloat(tokens[1]),
Float.parseFloat(tokens[2]),
Float.parseFloat(tokens[3]),
isFill
);
break;
}
case ParsingKeys.DiffuseColor:
case ParsingKeys.SpecularColor:
case ParsingKeys.SpecularExponent: {
break;
}
case ParsingKeys.Transparency: {
parseColorAlpha(polygon, Float.parseFloat(tokens[1]), isFill);
break;
}
case ParsingKeys.TextureImage: {
String materialPathString = materialPath.toString();
String materialFileName = materialPath.getFileName().toString();
Path imagePath = Path.of(materialPathString.substring(0, materialPathString.indexOf(materialFileName)) + tokens[1]);
parseImageTexture(polygon, imagePath);
break;
}
case ParsingKeys.NewMaterial: {
return;
}
}
}
}

public static void parseColor(Polygon2D polygon, float red, float green, float blue, boolean isFill) {
Color color = new Color((int) (red * 255), (int) (green * 255), (int) (blue * 255));
if (isFill) {
polygon.setFill(color);
} else {
polygon.setOutlineColor(color);
}
}

public static void parseColorAlpha(Polygon2D polygon, float alpha, boolean isFill) {
if (isFill) {
Color color = (Color) polygon.getFill();
polygon.setFill(
new Color(
color.getRed(),
color.getGreen(),
color.getBlue(),
(int) (alpha * 255)
)
);
} else {
Color color = polygon.getOutlineColor();
polygon.setOutlineColor(
new Color(
color.getRed(),
color.getGreen(),
color.getBlue(),
(int) (alpha * 255)
)
);
}
}

public static void parseImageTexture(Polygon2D polygon, Path imagePath) {
FastJEngine.getResourceManager(ImageResource.class).loadResource(imagePath);
polygon.setFill(Textures.create(imagePath, DrawUtil.createRect(polygon.getBounds())));
}

public static void write(Path destinationPath, Model2D model) {
Path destinationPathWithoutSpaces = Path.of(destinationPath.toString().replace(' ', '_'));
StringBuilder fileContents = new StringBuilder();
Expand Down Expand Up @@ -245,7 +335,6 @@ private ParsingKeys() {
public static final String SpecularExponent = "Ns";
public static final String Transparency = "d";
public static final String IlluminationMode = "illum";
public static final String TextureMap = "map_Ka";
public static final String TextureImage = "map_Kd";
}
}
92 changes: 90 additions & 2 deletions src/main/java/tech/fastj/resources/models/ObjUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import tech.fastj.graphics.Boundary;
import tech.fastj.graphics.game.Model2D;
import tech.fastj.graphics.game.Polygon2D;
import tech.fastj.graphics.game.RenderStyle;

import tech.fastj.resources.files.FileUtil;

Expand All @@ -14,6 +15,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

Expand All @@ -25,8 +27,94 @@ private ObjUtil() {
throw new java.lang.IllegalStateException();
}

public static Polygon2D[] parse(List<String> lines) {
return null;
public static Polygon2D[] parse(Path modelPath, List<String> lines) {
List<Polygon2D> polygons = new ArrayList<>();
List<float[]> vertexes = new ArrayList<>();


Path materialLibraryPath = null;
String currentMaterial = "";

for (String line : lines) {
String[] tokens = line.split("\\s+");
switch (tokens[0]) {
case ParsingKeys.Vertex: {
vertexes.add(parseVertex(tokens));
break;
}
case ParsingKeys.ObjectFace: {
Pointf[] vertexesFromFaces = parseVertexesFromFaces(vertexes, tokens);

Polygon2D polygonFromVertexes = Polygon2D.fromPoints(vertexesFromFaces);
MtlUtil.parse(polygonFromVertexes, materialLibraryPath, currentMaterial, true);
polygons.add(polygonFromVertexes);
break;
}
case ParsingKeys.ObjectLine: {
Pointf[] vertexesFromFaces = new Pointf[tokens.length - 1];
boolean isLastPolygonOutline = false;
int lastPolygonIndex = polygons.size() - 1;

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]);
}

if (isLastPolygonOutline) {
polygons.get(lastPolygonIndex).setRenderStyle(RenderStyle.FillAndOutline);
MtlUtil.parse(polygons.get(lastPolygonIndex), materialLibraryPath, currentMaterial, false);
} else {
Polygon2D polygonFromVertexes = Polygon2D.create(vertexesFromFaces, RenderStyle.Outline).build();
MtlUtil.parse(polygonFromVertexes, materialLibraryPath, currentMaterial, false);
polygons.add(polygonFromVertexes);
}
break;
}
case ParsingKeys.MaterialLib: {
materialLibraryPath = Path.of(
modelPath.toString().substring(
0,
modelPath.toString().indexOf(modelPath.getFileName().toString())
) + tokens[1]
// filenames and paths in .obj files cannot contain spaces, allowing us to use a non-robust
// solution for tokens.
);
break;
}
case ParsingKeys.UseMaterial: {
// material names in .obj files cannot contain spaces, allowing us to use a non-robust solution for
// tokens.
currentMaterial = tokens[1];
break;
}
}
}
return polygons.toArray(new Polygon2D[0]);
}

private static Pointf[] parseVertexesFromFaces(List<float[]> vertexes, String[] tokens) {
Pointf[] vertexesFromFaces = new Pointf[tokens.length - 1];
for (int j = 1; j < tokens.length; j++) {
int vertexesIndex = Integer.parseInt(tokens[Math.min(j, tokens.length - 1)].split("/")[0]);
vertexesFromFaces[j - 1] = new Pointf(
vertexes.get(vertexesIndex - 1)[0],
vertexes.get(vertexesIndex - 1)[1]
);
}

return vertexesFromFaces;
}

private static float[] parseVertex(String[] tokens) {
return new float[]{
Float.parseFloat(tokens[1]),
Float.parseFloat(tokens[2]),
Float.parseFloat(tokens[3])
};
}

public static void write(Path destinationPath, Model2D model) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/tech/fastj/resources/models/PsdfUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private PsdfUtil() {
* @param lines The .psdf file, split line by line.
* @return An array of {@code Polygon2D}s.
*/
public static Polygon2D[] parse(List<String> lines) {
public static Polygon2D[] parse(Path modelPath, List<String> lines) {
Polygon2D[] polygons = null;
int polygonsIndex = 0;

Expand Down

0 comments on commit 25dbd12

Please sign in to comment.