kopia lustrzana https://github.com/jameshball/osci-render
Complete initial working implementation of SvgParser with argument passing
rodzic
ef63e47b94
commit
d80a7aadf8
Przed Szerokość: | Wysokość: | Rozmiar: 541 B Po Szerokość: | Wysokość: | Rozmiar: 541 B |
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue