diff --git a/src/audio/AudioClient.java b/src/audio/AudioClient.java index 7aa3be1b..83f187be 100644 --- a/src/audio/AudioClient.java +++ b/src/audio/AudioClient.java @@ -42,6 +42,9 @@ public class AudioClient { AudioPlayer player = new AudioPlayer(SAMPLE_RATE, frames, ROTATE_SPEED, TRANSLATION_SPEED, TRANSLATION, SCALE, WEIGHT); System.out.println("Starting audio stream"); - player.play(); + new Thread(player).start(); + while (true) { + + } } } \ No newline at end of file diff --git a/src/audio/AudioPlayer.java b/src/audio/AudioPlayer.java index b5a1ed44..880db842 100644 --- a/src/audio/AudioPlayer.java +++ b/src/audio/AudioPlayer.java @@ -14,7 +14,7 @@ import shapes.Vector2; import java.util.List; -public class AudioPlayer { +public class AudioPlayer implements Runnable { private final XtFormat FORMAT; @@ -141,7 +141,12 @@ public class AudioPlayer { return frames.get(currentFrame).get(currentShape); } - public void play() { + public void addFrame(List frame) { + frames.add(frame); + } + + @Override + public void run() { try (XtAudio audio = new XtAudio(null, null, null, null)) { XtService service = XtAudio.getServiceBySetup(XtSetup.CONSUMER_AUDIO); try (XtDevice device = service.openDefaultDevice(true)) { diff --git a/src/engine/Camera.java b/src/engine/Camera.java index c5f5279f..d2af2c39 100644 --- a/src/engine/Camera.java +++ b/src/engine/Camera.java @@ -1,5 +1,7 @@ package engine; +import java.util.HashMap; +import java.util.Map; import shapes.Line; import shapes.Vector2; @@ -40,17 +42,17 @@ public class Camera { } public List draw(WorldObject worldObject) { - return getFrame(getProjectedVertices(worldObject), worldObject.getEdgeData()); + return getFrame(getProjectedVertices(worldObject), worldObject.getVertexPath()); } - public List getProjectedVertices(WorldObject worldObject) { - List vertices = new ArrayList<>(); + public Map getProjectedVertices(WorldObject worldObject) { + Map projectionMap = new HashMap<>(); for (Vector3 vertex : worldObject.getVertices()) { - vertices.add(project(vertex)); + projectionMap.put(vertex, project(vertex)); } - return vertices; + return projectionMap; } // Automatically finds the correct Z position to use to view the world object properly. @@ -73,7 +75,7 @@ public class Camera { List vertices = new ArrayList<>(); for (int i = 0; i < SAMPLE_RENDER_SAMPLES - 1; i++) { - vertices.addAll(getProjectedVertices(clone)); + vertices.addAll(getProjectedVertices(clone).values()); clone.rotate(rotation); } @@ -111,18 +113,14 @@ public class Camera { ); } - public List getFrame(List vertices, List connections) { + public List getFrame(Map projectionMap, List vertexPath) { List lines = new ArrayList<>(); - for (int i = 0; i < connections.size(); i += 2) { - Vector2 start = vertices.get(Math.abs(connections.get(i))); - Vector2 end = vertices.get(Math.abs(connections.get(i + 1))); - double x1 = start.getX(); - double y1 = start.getY(); - double x2 = end.getX(); - double y2 = end.getY(); - - lines.add(new Line(x1, y1, x2, y2)); + for (int i = 0; i < vertexPath.size(); i += 2) { + lines.add(new Line( + projectionMap.get(vertexPath.get(i)), + projectionMap.get(vertexPath.get(i + 1)) + )); } return lines; diff --git a/src/engine/Vector3.java b/src/engine/Vector3.java index a71fe8c9..a99e68da 100644 --- a/src/engine/Vector3.java +++ b/src/engine/Vector3.java @@ -1,6 +1,8 @@ package engine; import java.util.List; +import java.util.Objects; +import shapes.Vector2; public final class Vector3 { @@ -78,6 +80,10 @@ public final class Vector3 { ); } + public double distance(Vector3 vector) { + return Math.sqrt(Math.pow(vector.x, 2) + Math.pow(vector.y, 2) + Math.pow(vector.z, 2)); + } + public static Vector3 meanPoint(List points) { Vector3 mean = new Vector3(); @@ -88,6 +94,34 @@ public final class Vector3 { return mean.scale(1f / (points.size())); } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Vector3 point = (Vector3) obj; + return x == point.x && y == point.y && z== point.z; + } + + @Override + public int hashCode() { + return Objects.hash(x, y, z); + } + + private static double round(double value, int places) { + if (places < 0) { + throw new IllegalArgumentException(); + } + + long factor = (long) Math.pow(10, places); + value *= factor; + + return (double) Math.round(value) / factor; + } + public Vector3 clone() { return new Vector3(x, y, z); } diff --git a/src/engine/WorldObject.java b/src/engine/WorldObject.java index 30e8412d..1129c6fc 100644 --- a/src/engine/WorldObject.java +++ b/src/engine/WorldObject.java @@ -3,36 +3,102 @@ package engine; import com.mokiat.data.front.parser.*; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Set; +import org.jgrapht.Graph; +import org.jgrapht.alg.connectivity.ConnectivityInspector; +import org.jgrapht.alg.cycle.ChinesePostman; +import org.jgrapht.graph.AsSubgraph; +import org.jgrapht.graph.DefaultUndirectedWeightedGraph; +import org.jgrapht.graph.DefaultWeightedEdge; public class WorldObject { private final List vertices; - private final List edgeData; + + // These should be a path of vertices from the above vertex list. + private List vertexPath; private Vector3 position; private Vector3 rotation; - public WorldObject(List vertices, List edgeData, Vector3 position, - Vector3 rotation) { + private WorldObject(List vertices, List vertexPath, Vector3 position, Vector3 rotation) { this.vertices = vertices; - this.edgeData = edgeData; this.position = position; this.rotation = rotation; + this.vertexPath = vertexPath; + } + + private WorldObject(List vertices, Vector3 position, Vector3 rotation) { + this(vertices, new ArrayList<>(), position, rotation); } public WorldObject(String filename, Vector3 position, Vector3 rotation) throws IOException { - this(new ArrayList<>(), new ArrayList<>(), position, rotation); - loadFromFile(filename); + this(new ArrayList<>(), position, rotation); + this.vertexPath = getDrawPath(loadFromFile(filename)); } public WorldObject(String filename) throws IOException { this(filename, new Vector3(), new Vector3()); } + public List getVertexPath() { + List newVertices = new ArrayList<>(); + + for (Vector3 vertex : vertexPath) { + newVertices.add(vertex.rotate(rotation).add(position)); + } + + return newVertices; + } + + public List getDrawPath(List edgeData) { + Graph graph = new DefaultUndirectedWeightedGraph<>( + DefaultWeightedEdge.class); + + List vertexPath = new ArrayList<>(); + + // Add all lines in frame to graph as vertices and edges. Edge weight is determined by the + // length of the line as this is directly proportional to draw time. + for (int i = 0; i < edgeData.size(); i += 2) { + Vector3 start = vertices.get(edgeData.get(i)); + Vector3 end = vertices.get(edgeData.get(i + 1)); + graph.addVertex(start); + graph.addVertex(end); + + DefaultWeightedEdge edge = new DefaultWeightedEdge(); + graph.addEdge(start, end, edge); + graph.setEdgeWeight(edge, start.distance(end)); + } + + ConnectivityInspector inspector = new ConnectivityInspector<>( + graph); + + // Chinese Postman can only be performed on connected graphs, so iterate over all connected + // sub-graphs. + for (Set vertices : inspector.connectedSets()) { + AsSubgraph subgraph = new AsSubgraph<>(graph, vertices); + ChinesePostman cp = new ChinesePostman<>(); + Collection path; + + try { + path = cp.getCPPSolution(subgraph).getEdgeList(); + } catch (Exception e) { + // Safety in case getCPPSolution fails. + path = subgraph.edgeSet(); + } + + for (DefaultWeightedEdge edge : path) { + vertexPath.add(subgraph.getEdgeSource(edge)); + vertexPath.add(subgraph.getEdgeTarget(edge)); + } + } + + return vertexPath; + } public void rotate(Vector3 theta) { rotation = rotation.add(theta); @@ -60,15 +126,13 @@ public class WorldObject { return newVertices; } - public List getEdgeData() { - return edgeData; - } - - private void loadFromFile(String filename) throws IOException { + private List loadFromFile(String filename) throws IOException { InputStream in = new FileInputStream(filename); final IOBJParser parser = new OBJParser(); final OBJModel model = parser.parse(in); + List edgeData = new ArrayList<>(); + for (OBJVertex vertex : model.getVertices()) { vertices.add(new Vector3(vertex.x, vertex.y, vertex.z)); } @@ -85,10 +149,11 @@ public class WorldObject { } } } + + return edgeData; } public WorldObject clone() { - return new WorldObject(new ArrayList<>(vertices), new ArrayList<>(edgeData), position, - rotation); + return new WorldObject(new ArrayList<>(vertices), new ArrayList<>(vertexPath), position, rotation); } }