Complete initial working implementation of SvgParser with argument passing

pull/35/head
James Ball 2020-11-06 21:35:00 +00:00
rodzic ef63e47b94
commit d80a7aadf8
9 zmienionych plików z 157 dodań i 112 usunięć

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 541 B

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 541 B

Wyświetl plik

@ -1,11 +1,20 @@
package audio; package audio;
import engine.Camera; import engine.Camera;
import java.io.IOException;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import parser.FileParser;
import parser.ObjParser;
import parser.SvgParser;
import shapes.Shape;
import shapes.Shapes;
// Helper class for AudioClient that deals with optional program arguments. // Helper class for AudioClient that deals with optional program arguments.
final class AudioArgs { final class AudioArgs {
final String objFilePath; final String filePath;
final float[] optionalArgs; final float[] optionalArgs;
AudioArgs(String[] args) throws IllegalAudioArgumentException { AudioArgs(String[] args) throws IllegalAudioArgumentException {
@ -13,7 +22,7 @@ final class AudioArgs {
throw new IllegalAudioArgumentException(); throw new IllegalAudioArgumentException();
} }
objFilePath = args[0]; filePath = args[0];
optionalArgs = new float[args.length - 1]; optionalArgs = new float[args.length - 1];
for (int i = 0; i < optionalArgs.length; i++) { for (int i = 0; i < optionalArgs.length; i++) {
@ -21,8 +30,16 @@ final class AudioArgs {
} }
} }
String objFilePath() { List<List<Shape>> getFramesFromFile() throws IOException, ParserConfigurationException, SAXException {
return objFilePath; if (filePath.matches(".*\\.obj")) {
return new ObjParser(filePath, rotateSpeed(), cameraX(), cameraY(), cameraZ(), focalLength(),
isDefaultPosition()).getShapes();
} else if (filePath.matches(".*\\.svg")) {
return Shapes.normalize(new SvgParser(filePath).getShapes());
} else {
throw new IllegalArgumentException(
"Provided file extension in file " + filePath + " not supported.");
}
} }
float rotateSpeed() { float rotateSpeed() {

Wyświetl plik

@ -1,24 +1,15 @@
package audio; package audio;
import engine.Camera;
import engine.Vector3;
import engine.WorldObject;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import parser.SvgParser;
import shapes.Shape; import shapes.Shape;
import shapes.Shapes;
import shapes.Vector2; import shapes.Vector2;
public class AudioClient { public class AudioClient {
private static final int SAMPLE_RATE = 192000; private static final int SAMPLE_RATE = 192000;
private static double OBJ_ROTATE_SPEED = Math.PI / 1000;
private static final float ROTATE_SPEED = 0; private static final float ROTATE_SPEED = 0;
private static final float TRANSLATION_SPEED = 0; private static final float TRANSLATION_SPEED = 0;
private static final Vector2 TRANSLATION = new Vector2(0.3, 0.3); private static final Vector2 TRANSLATION = new Vector2(0.3, 0.3);
@ -26,7 +17,7 @@ public class AudioClient {
private static final float WEIGHT = Shape.DEFAULT_WEIGHT; private static final float WEIGHT = Shape.DEFAULT_WEIGHT;
// args: // args:
// args[0] - path of .obj file // args[0] - path of .obj or .svg file
// args[1] - rotation speed of object // args[1] - rotation speed of object
// args[2] - focal length of camera // args[2] - focal length of camera
// args[3] - x position of camera // args[3] - x position of camera
@ -44,23 +35,8 @@ public class AudioClient {
AudioArgs args = new AudioArgs(programArgs); AudioArgs args = new AudioArgs(programArgs);
OBJ_ROTATE_SPEED *= args.rotateSpeed();
Vector3 cameraPos = new Vector3(args.cameraX(), args.cameraY(), args.cameraZ());
WorldObject object = new WorldObject(args.objFilePath());
// If camera position arguments haven't been specified, automatically work out the position of
// the camera based on the size of the object in the camera's view.
Camera camera = args.isDefaultPosition() ? new Camera(args.focalLength(), object)
: new Camera(args.focalLength(), cameraPos);
Vector3 rotation = new Vector3(0, OBJ_ROTATE_SPEED, OBJ_ROTATE_SPEED);
System.out.println("Begin pre-render..."); System.out.println("Begin pre-render...");
//List<List<? extends Shape>> frames = preRender(object, rotation, camera); List<List<Shape>> frames = args.getFramesFromFile();
List<List<? extends Shape>> frames = new ArrayList<>();
List<Shape> frame = Shapes.normalizeShapes(new SvgParser("test/images/sine-wave.svg").getShapes());
frames.add(frame);
System.out.println("Finish pre-render"); System.out.println("Finish pre-render");
System.out.println("Connecting to audio player"); System.out.println("Connecting to audio player");
AudioPlayer player = new AudioPlayer(SAMPLE_RATE, frames, ROTATE_SPEED, TRANSLATION_SPEED, AudioPlayer player = new AudioPlayer(SAMPLE_RATE, frames, ROTATE_SPEED, TRANSLATION_SPEED,
@ -68,33 +44,4 @@ public class AudioClient {
System.out.println("Starting audio stream"); System.out.println("Starting audio stream");
player.play(); player.play();
} }
private static List<List<? extends Shape>> preRender(WorldObject object, Vector3 rotation,
Camera camera) {
List<List<? extends Shape>> preRenderedFrames = new ArrayList<>();
// Number of frames it will take to render a full rotation of the object.
int numFrames = (int) (2 * Math.PI / OBJ_ROTATE_SPEED);
for (int i = 0; i < numFrames; i++) {
preRenderedFrames.add(new ArrayList<>());
}
AtomicInteger renderedFrames = new AtomicInteger();
// pre-renders the WorldObject in parallel
IntStream.range(0, numFrames).parallel().forEach((frameNum) -> {
WorldObject clone = object.clone();
clone.rotate(rotation.scale(frameNum));
// Finds all lines to draw the object from the camera's view and then 'sorts' them by finding
// a hamiltonian path, which dramatically helps with rendering a clean image.
preRenderedFrames.set(frameNum, Shapes.sortLines(camera.draw(clone)));
int numRendered = renderedFrames.getAndIncrement();
if (numRendered % 50 == 0) {
System.out.println("Rendered " + numRendered + " frames of " + (numFrames + 1) + " total");
}
});
return preRenderedFrames;
}
} }

Wyświetl plik

@ -18,7 +18,7 @@ public class AudioPlayer {
private final XtFormat FORMAT; private final XtFormat FORMAT;
private final List<List<? extends Shape>> frames; private final List<List<Shape>> frames;
private int currentFrame = 0; private int currentFrame = 0;
private int currentShape = 0; private int currentShape = 0;
private int audioFramesDrawn = 0; private int audioFramesDrawn = 0;
@ -33,12 +33,12 @@ public class AudioPlayer {
private volatile boolean stopped; private volatile boolean stopped;
public AudioPlayer(int sampleRate, List<List<? extends Shape>> frames) { public AudioPlayer(int sampleRate, List<List<Shape>> frames) {
this.FORMAT = new XtFormat(new XtMix(sampleRate, XtSample.FLOAT32), 0, 0, 2, 0); this.FORMAT = new XtFormat(new XtMix(sampleRate, XtSample.FLOAT32), 0, 0, 2, 0);
this.frames = frames; this.frames = frames;
} }
public AudioPlayer(int sampleRate, List<List<? extends Shape>> frames, float rotateSpeed, public AudioPlayer(int sampleRate, List<List<Shape>> frames, float rotateSpeed,
float translateSpeed, Vector2 translateVector, float scale, float weight) { float translateSpeed, Vector2 translateVector, float scale, float weight) {
this(sampleRate, frames); this(sampleRate, frames);
setRotateSpeed(rotateSpeed); setRotateSpeed(rotateSpeed);

Wyświetl plik

@ -2,25 +2,27 @@ package parser;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.regex.Pattern;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import shapes.Shape; import shapes.Shape;
public abstract class FileParser { public abstract class FileParser {
public abstract String getFileExtension(); protected abstract String getFileExtension();
protected void checkFileExtension(String path) throws IllegalArgumentException { protected void checkFileExtension(String path) throws IllegalArgumentException {
Pattern pattern = Pattern.compile("\\." + getFileExtension() + "$"); if (!hasCorrectFileExtension(path)) {
if (!pattern.matcher(path).find()) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"File to parse is not a ." + getFileExtension() + " file."); "File to parse is not a ." + getFileExtension() + " file.");
} }
} }
public boolean hasCorrectFileExtension(String path) {
return path.matches(".*\\." + getFileExtension());
}
protected abstract void parseFile(String path) protected abstract void parseFile(String path)
throws ParserConfigurationException, IOException, SAXException, IllegalArgumentException; throws ParserConfigurationException, IOException, SAXException, IllegalArgumentException;
public abstract List<Shape> getShapes(); public abstract List<List<Shape>> getShapes();
} }

Wyświetl plik

@ -0,0 +1,95 @@
package parser;
import engine.Camera;
import engine.Vector3;
import engine.WorldObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
import shapes.Shape;
import shapes.Shapes;
public class ObjParser extends FileParser {
private static double OBJ_ROTATE_SPEED = Math.PI / 1000;
private List<List<Shape>> shapes;
private final float rotateSpeed;
private final float cameraX;
private final float cameraY;
private final float cameraZ;
private final float focalLength;
private final boolean isDefaultPosition;
public ObjParser(String path, float rotateSpeed, float cameraX, float cameraY, float cameraZ,
float focalLength, boolean isDefaultPosition) throws IOException {
checkFileExtension(path);
shapes = new ArrayList<>();
this.rotateSpeed = rotateSpeed;
this.cameraX = cameraX;
this.cameraY = cameraY;
this.cameraZ = cameraZ;
this.focalLength = focalLength;
this.isDefaultPosition = isDefaultPosition;
parseFile(path);
}
@Override
protected String getFileExtension() {
return "obj";
}
@Override
protected void parseFile(String path) throws IllegalArgumentException, IOException {
OBJ_ROTATE_SPEED *= rotateSpeed;
Vector3 cameraPos = new Vector3(cameraX, cameraY, cameraZ);
WorldObject object = new WorldObject(path);
// If camera position arguments haven't been specified, automatically work out the position of
// the camera based on the size of the object in the camera's view.
Camera camera = isDefaultPosition ? new Camera(focalLength, object)
: new Camera(focalLength, cameraPos);
Vector3 rotation = new Vector3(0, OBJ_ROTATE_SPEED, OBJ_ROTATE_SPEED);
shapes = preRender(object, rotation, camera);
}
@Override
public List<List<Shape>> getShapes() {
return shapes;
}
private static List<List<Shape>> preRender(WorldObject object, Vector3 rotation,
Camera camera) {
List<List<Shape>> preRenderedFrames = new ArrayList<>();
// Number of frames it will take to render a full rotation of the object.
int numFrames = (int) (2 * Math.PI / OBJ_ROTATE_SPEED);
for (int i = 0; i < numFrames; i++) {
preRenderedFrames.add(new ArrayList<>());
}
AtomicInteger renderedFrames = new AtomicInteger();
// pre-renders the WorldObject in parallel
IntStream.range(0, numFrames).parallel().forEach((frameNum) -> {
WorldObject clone = object.clone();
clone.rotate(rotation.scale(frameNum));
// Finds all lines to draw the object from the camera's view and then 'sorts' them by finding
// a hamiltonian path, which dramatically helps with rendering a clean image.
preRenderedFrames.set(frameNum, Shapes.sortLines(camera.draw(clone)));
int numRendered = renderedFrames.getAndIncrement();
if (numRendered % 50 == 0) {
System.out.println("Rendered " + numRendered + " frames of " + (numFrames + 1) + " total");
}
});
return preRenderedFrames;
}
}

Wyświetl plik

@ -13,7 +13,6 @@ import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
@ -31,11 +30,6 @@ public class SvgParser extends FileParser {
private final List<Shape> shapes; private final List<Shape> shapes;
private final Map<Character, Function<List<Float>, List<? extends Shape>>> commandMap; private final Map<Character, Function<List<Float>, List<? extends Shape>>> commandMap;
private float viewBoxWidth;
private float viewBoxHeight;
private float width;
private float height;
private Vector2 currPoint; private Vector2 currPoint;
private Vector2 initialPoint; private Vector2 initialPoint;
private Vector2 prevCubicControlPoint; private Vector2 prevCubicControlPoint;
@ -119,20 +113,6 @@ public class SvgParser extends FileParser {
return paths; return paths;
} }
// Returns the width and height of the viewBox attribute
private void getSvgDimensions(Node svgElem) {
List<Float> viewBox = Arrays.stream(getNodeValue(svgElem, "viewBox").split(" "))
.map(Float::parseFloat)
.skip(2)
.collect(Collectors.toList());
viewBoxWidth = viewBox.get(0);
viewBoxHeight = viewBox.get(1);
width = Float.parseFloat(getNodeValue(svgElem, "width"));
height = Float.parseFloat(getNodeValue(svgElem, "height"));
}
private static List<Float> splitCommand(String command) { private static List<Float> splitCommand(String command) {
List<Float> nums = new ArrayList<>(); List<Float> nums = new ArrayList<>();
String[] decimalSplit = command.split("\\."); String[] decimalSplit = command.split("\\.");
@ -156,7 +136,7 @@ public class SvgParser extends FileParser {
} }
@Override @Override
public String getFileExtension() { protected String getFileExtension() {
return "svg"; return "svg";
} }
@ -170,8 +150,6 @@ public class SvgParser extends FileParser {
throw new IllegalArgumentException("SVG has either zero or more than one svg element."); throw new IllegalArgumentException("SVG has either zero or more than one svg element.");
} }
getSvgDimensions(svgElem.get(0));
// Get all d attributes within path elements in the SVG file. // Get all d attributes within path elements in the SVG file.
for (String path : getSvgPathAttributes(svg)) { for (String path : getSvgPathAttributes(svg)) {
currPoint = new Vector2(); currPoint = new Vector2();
@ -206,8 +184,10 @@ public class SvgParser extends FileParser {
} }
@Override @Override
public List<Shape> getShapes() { public List<List<Shape>> getShapes() {
return shapes; List<List<Shape>> frames = new ArrayList<>();
frames.add(shapes);
return frames;
} }
// Parses moveto commands (M and m commands) // Parses moveto commands (M and m commands)

Wyświetl plik

@ -17,28 +17,32 @@ public class Shapes {
// Normalises shapes between the coords -1 and 1 for proper scaling on an oscilloscope. May not // Normalises shapes between the coords -1 and 1 for proper scaling on an oscilloscope. May not
// work perfectly with curves that heavily deviate from their start and end points. // work perfectly with curves that heavily deviate from their start and end points.
public static List<Shape> normalizeShapes(List<Shape> shapes) { public static List<List<Shape>> normalize(List<List<Shape>> shapeLists) {
double maxVertex = 0; double maxVertex = 0;
for (Shape shape : shapes) { for (List<Shape> shapes : shapeLists) {
Vector2 startVector = shape.nextVector(0); for (Shape shape : shapes) {
Vector2 endVector = shape.nextVector(1); Vector2 startVector = shape.nextVector(0);
Vector2 endVector = shape.nextVector(1);
double maxX = Math.max(Math.abs(startVector.getX()), Math.abs(endVector.getX())); double maxX = Math.max(Math.abs(startVector.getX()), Math.abs(endVector.getX()));
double maxY = Math.max(Math.abs(startVector.getY()), Math.abs(endVector.getY())); double maxY = Math.max(Math.abs(startVector.getY()), Math.abs(endVector.getY()));
maxVertex = Math.max(Math.max(maxX, maxY), maxVertex); maxVertex = Math.max(Math.max(maxX, maxY), maxVertex);
}
} }
double factor = 2 / maxVertex; double factor = 2 / maxVertex;
for (int i = 0; i < shapes.size(); i++) { for (List<Shape> shapes : shapeLists) {
shapes.set(i, shapes.get(i) for (int i = 0; i < shapes.size(); i++) {
.scale(new Vector2(factor, -factor)) shapes.set(i, shapes.get(i)
.translate(new Vector2(-1, 1))); .scale(new Vector2(factor, -factor))
.translate(new Vector2(-1, 1)));
}
} }
return shapes; return shapeLists;
} }
public static List<Shape> generatePolygram(int sides, int angleJump, Vector2 start, public static List<Shape> generatePolygram(int sides, int angleJump, Vector2 start,

Wyświetl plik

@ -11,35 +11,35 @@ import shapes.Shape;
public class SvgParserTest { public class SvgParserTest {
private List<? extends Shape> getShapes(SvgParser parser) {
return parser.getShapes().get(0);
}
@Test @Test
public void lineToGeneratesALineShape() public void lineToGeneratesALineShape()
throws ParserConfigurationException, SAXException, IOException { throws ParserConfigurationException, SAXException, IOException {
SvgParser svgParser = new SvgParser("test/images/line-to.svg"); SvgParser svgParser = new SvgParser("test/images/line-to.svg");
List<Shape> shapes = svgParser.getShapes(); assertEquals(getShapes(svgParser), Line.pathToLines(0.5, 0.5, 0.75, 1, 0, 0, 0.5, 0.5));
assertEquals(shapes, Line.pathToLines(0.5, 0.5, 0.75, 1, 0, 0, 0.5, 0.5));
} }
@Test @Test
public void horizontalLineToGeneratesAHorizontalLineShape() public void horizontalLineToGeneratesAHorizontalLineShape()
throws ParserConfigurationException, SAXException, IOException { throws ParserConfigurationException, SAXException, IOException {
SvgParser svgParser = new SvgParser("test/images/horizontal-line-to.svg"); SvgParser svgParser = new SvgParser("test/images/horizontal-line-to.svg");
List<Shape> shapes = svgParser.getShapes(); assertEquals(getShapes(svgParser), Line.pathToLines(0.5, 0.5, 0.75, 0.5, 0, 0.5, 0.5, 0.5));
assertEquals(shapes, Line.pathToLines(0.5, 0.5, 0.75, 0.5, 0, 0.5, 0.5, 0.5));
} }
@Test @Test
public void verticalLineToGeneratesAVerticalLineShape() public void verticalLineToGeneratesAVerticalLineShape()
throws ParserConfigurationException, SAXException, IOException { throws ParserConfigurationException, SAXException, IOException {
SvgParser svgParser = new SvgParser("test/images/vertical-line-to.svg"); SvgParser svgParser = new SvgParser("test/images/vertical-line-to.svg");
List<Shape> shapes = svgParser.getShapes(); assertEquals(getShapes(svgParser), Line.pathToLines(0.5, 0.5, 0.5, 0.75, 0.5, 0, 0.5, 0.5));
assertEquals(shapes, Line.pathToLines(0.5, 0.5, 0.5, 0.75, 0.5, 0, 0.5, 0.5));
} }
@Test @Test
public void closingASubPathDrawsLineToInitialPoint() public void closingASubPathDrawsLineToInitialPoint()
throws ParserConfigurationException, SAXException, IOException { throws ParserConfigurationException, SAXException, IOException {
SvgParser svgParser = new SvgParser("test/images/closing-subpath.svg"); SvgParser svgParser = new SvgParser("test/images/closing-subpath.svg");
List<Shape> shapes = svgParser.getShapes(); assertEquals(getShapes(svgParser), Line.pathToLines(0.5, 0.5, 0.75, 0.5, 0.75, 0.75, 0.5, 0.75, 0.5, 0.5));
assertEquals(shapes, Line.pathToLines(0.5, 0.5, 0.75, 0.5, 0.75, 0.75, 0.5, 0.75, 0.5, 0.5));
} }
} }