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;
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.
final class AudioArgs {
final String objFilePath;
final String filePath;
final float[] optionalArgs;
AudioArgs(String[] args) throws IllegalAudioArgumentException {
@ -13,7 +22,7 @@ final class AudioArgs {
throw new IllegalAudioArgumentException();
}
objFilePath = args[0];
filePath = args[0];
optionalArgs = new float[args.length - 1];
for (int i = 0; i < optionalArgs.length; i++) {
@ -21,8 +30,16 @@ final class AudioArgs {
}
}
String objFilePath() {
return objFilePath;
List<List<Shape>> getFramesFromFile() throws IOException, ParserConfigurationException, SAXException {
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() {

Wyświetl plik

@ -1,24 +1,15 @@
package audio;
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 javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import parser.SvgParser;
import shapes.Shape;
import shapes.Shapes;
import shapes.Vector2;
public class AudioClient {
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 TRANSLATION_SPEED = 0;
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;
// args:
// args[0] - path of .obj file
// args[0] - path of .obj or .svg file
// args[1] - rotation speed of object
// args[2] - focal length of camera
// args[3] - x position of camera
@ -44,23 +35,8 @@ public class AudioClient {
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...");
//List<List<? extends Shape>> frames = preRender(object, rotation, camera);
List<List<? extends Shape>> frames = new ArrayList<>();
List<Shape> frame = Shapes.normalizeShapes(new SvgParser("test/images/sine-wave.svg").getShapes());
frames.add(frame);
List<List<Shape>> frames = args.getFramesFromFile();
System.out.println("Finish pre-render");
System.out.println("Connecting to audio player");
AudioPlayer player = new AudioPlayer(SAMPLE_RATE, frames, ROTATE_SPEED, TRANSLATION_SPEED,
@ -68,33 +44,4 @@ public class AudioClient {
System.out.println("Starting audio stream");
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 List<List<? extends Shape>> frames;
private final List<List<Shape>> frames;
private int currentFrame = 0;
private int currentShape = 0;
private int audioFramesDrawn = 0;
@ -33,12 +33,12 @@ public class AudioPlayer {
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.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) {
this(sampleRate, frames);
setRotateSpeed(rotateSpeed);

Wyświetl plik

@ -2,25 +2,27 @@ package parser;
import java.io.IOException;
import java.util.List;
import java.util.regex.Pattern;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import shapes.Shape;
public abstract class FileParser {
public abstract String getFileExtension();
protected abstract String getFileExtension();
protected void checkFileExtension(String path) throws IllegalArgumentException {
Pattern pattern = Pattern.compile("\\." + getFileExtension() + "$");
if (!pattern.matcher(path).find()) {
if (!hasCorrectFileExtension(path)) {
throw new IllegalArgumentException(
"File to parse is not a ." + getFileExtension() + " file.");
}
}
public boolean hasCorrectFileExtension(String path) {
return path.matches(".*\\." + getFileExtension());
}
protected abstract void parseFile(String path)
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.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@ -31,11 +30,6 @@ public class SvgParser extends FileParser {
private final List<Shape> shapes;
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 initialPoint;
private Vector2 prevCubicControlPoint;
@ -119,20 +113,6 @@ public class SvgParser extends FileParser {
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) {
List<Float> nums = new ArrayList<>();
String[] decimalSplit = command.split("\\.");
@ -156,7 +136,7 @@ public class SvgParser extends FileParser {
}
@Override
public String getFileExtension() {
protected String getFileExtension() {
return "svg";
}
@ -170,8 +150,6 @@ public class SvgParser extends FileParser {
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.
for (String path : getSvgPathAttributes(svg)) {
currPoint = new Vector2();
@ -206,8 +184,10 @@ public class SvgParser extends FileParser {
}
@Override
public List<Shape> getShapes() {
return shapes;
public List<List<Shape>> getShapes() {
List<List<Shape>> frames = new ArrayList<>();
frames.add(shapes);
return frames;
}
// 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
// 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;
for (Shape shape : shapes) {
Vector2 startVector = shape.nextVector(0);
Vector2 endVector = shape.nextVector(1);
for (List<Shape> shapes : shapeLists) {
for (Shape shape : shapes) {
Vector2 startVector = shape.nextVector(0);
Vector2 endVector = shape.nextVector(1);
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 maxX = Math.max(Math.abs(startVector.getX()), Math.abs(endVector.getX()));
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;
for (int i = 0; i < shapes.size(); i++) {
shapes.set(i, shapes.get(i)
.scale(new Vector2(factor, -factor))
.translate(new Vector2(-1, 1)));
for (List<Shape> shapes : shapeLists) {
for (int i = 0; i < shapes.size(); i++) {
shapes.set(i, shapes.get(i)
.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,

Wyświetl plik

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