kopia lustrzana https://github.com/jameshball/osci-render
Complete reduction of chinese postman solving, massively improving pre-rendering and cleaning up output
rodzic
c46afe24c9
commit
6d0e2f8217
|
@ -3,6 +3,7 @@ package engine;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import shapes.Line;
|
||||
import shapes.Shape;
|
||||
import shapes.Vector2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -41,7 +42,7 @@ public class Camera {
|
|||
this(DEFAULT_FOCAL_LENGTH, object);
|
||||
}
|
||||
|
||||
public List<Line> draw(WorldObject worldObject) {
|
||||
public List<Shape> draw(WorldObject worldObject) {
|
||||
return getFrame(getProjectedVertices(worldObject), worldObject.getVertexPath());
|
||||
}
|
||||
|
||||
|
@ -113,8 +114,8 @@ public class Camera {
|
|||
);
|
||||
}
|
||||
|
||||
public List<Line> getFrame(Map<Vector3, Vector2> projectionMap, List<Vector3> vertexPath) {
|
||||
List<Line> lines = new ArrayList<>();
|
||||
public List<Shape> getFrame(Map<Vector3, Vector2> projectionMap, List<Vector3> vertexPath) {
|
||||
List<Shape> lines = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < vertexPath.size(); i += 2) {
|
||||
lines.add(new Line(
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package engine;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Line3D {
|
||||
|
||||
private final Vector3 start;
|
||||
private final Vector3 end;
|
||||
|
||||
public Line3D(Vector3 start, Vector3 end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public Vector3 getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public Vector3 getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Line3D line3D = (Line3D) o;
|
||||
return Objects.equals(start, line3D.start) && Objects.equals(end, line3D.end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Line3D{" +
|
||||
"start=" + start +
|
||||
", end=" + end +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ package engine;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import shapes.Vector2;
|
||||
|
||||
public final class Vector3 {
|
||||
|
||||
|
@ -111,18 +110,16 @@ public final class Vector3 {
|
|||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vector3{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
", z=" + z +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import java.io.FileInputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.jgrapht.Graph;
|
||||
|
@ -18,27 +18,27 @@ import org.jgrapht.graph.DefaultWeightedEdge;
|
|||
|
||||
public class WorldObject {
|
||||
|
||||
private final List<Vector3> vertices;
|
||||
private final List<Vector3> objVertices;
|
||||
|
||||
// These should be a path of vertices from the above vertex list.
|
||||
private List<Vector3> vertexPath;
|
||||
private Vector3 position;
|
||||
private Vector3 rotation;
|
||||
|
||||
private WorldObject(List<Vector3> vertices, List<Vector3> vertexPath, Vector3 position, Vector3 rotation) {
|
||||
this.vertices = vertices;
|
||||
private WorldObject(List<Vector3> objVertices, List<Vector3> vertexPath, Vector3 position, Vector3 rotation) {
|
||||
this.objVertices = objVertices;
|
||||
this.position = position;
|
||||
this.rotation = rotation;
|
||||
this.vertexPath = vertexPath;
|
||||
}
|
||||
|
||||
private WorldObject(List<Vector3> vertices, Vector3 position, Vector3 rotation) {
|
||||
this(vertices, new ArrayList<>(), position, rotation);
|
||||
private WorldObject(List<Vector3> objVertices, Vector3 position, Vector3 rotation) {
|
||||
this(objVertices, new ArrayList<>(), position, rotation);
|
||||
}
|
||||
|
||||
public WorldObject(String filename, Vector3 position, Vector3 rotation) throws IOException {
|
||||
this(new ArrayList<>(), position, rotation);
|
||||
this.vertexPath = getDrawPath(loadFromFile(filename));
|
||||
getDrawPath(loadFromFile(filename));
|
||||
}
|
||||
|
||||
public WorldObject(String filename) throws IOException {
|
||||
|
@ -55,23 +55,22 @@ public class WorldObject {
|
|||
return newVertices;
|
||||
}
|
||||
|
||||
public List<Vector3> getDrawPath(List<Integer> edgeData) {
|
||||
public void getDrawPath(Set<Line3D> edges) {
|
||||
Graph<Vector3, DefaultWeightedEdge> graph = new DefaultUndirectedWeightedGraph<>(
|
||||
DefaultWeightedEdge.class);
|
||||
|
||||
List<Vector3> vertexPath = new ArrayList<>();
|
||||
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);
|
||||
for (Line3D edge : edges) {
|
||||
graph.addVertex(edge.getStart());
|
||||
graph.addVertex(edge.getEnd());
|
||||
|
||||
DefaultWeightedEdge edge = new DefaultWeightedEdge();
|
||||
graph.addEdge(start, end, edge);
|
||||
graph.setEdgeWeight(edge, start.distance(end));
|
||||
DefaultWeightedEdge weightedEdge = new DefaultWeightedEdge();
|
||||
graph.addEdge(edge.getStart(), edge.getEnd(), weightedEdge);
|
||||
graph.addEdge(edge.getStart(), edge.getEnd());
|
||||
graph.setEdgeWeight(weightedEdge, edge.getStart().distance(edge.getEnd()));
|
||||
}
|
||||
|
||||
ConnectivityInspector<Vector3, DefaultWeightedEdge> inspector = new ConnectivityInspector<>(
|
||||
|
@ -82,22 +81,13 @@ public class WorldObject {
|
|||
for (Set<Vector3> vertices : inspector.connectedSets()) {
|
||||
AsSubgraph<Vector3, DefaultWeightedEdge> subgraph = new AsSubgraph<>(graph, vertices);
|
||||
ChinesePostman<Vector3, DefaultWeightedEdge> cp = new ChinesePostman<>();
|
||||
Collection<DefaultWeightedEdge> path;
|
||||
List<Vector3> path = cp.getCPPSolution(subgraph).getVertexList();
|
||||
|
||||
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));
|
||||
for (int i = 1; i < path.size(); i++) {
|
||||
vertexPath.add(path.get(i - 1));
|
||||
vertexPath.add(path.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
return vertexPath;
|
||||
}
|
||||
|
||||
public void rotate(Vector3 theta) {
|
||||
|
@ -119,22 +109,22 @@ public class WorldObject {
|
|||
public List<Vector3> getVertices() {
|
||||
List<Vector3> newVertices = new ArrayList<>();
|
||||
|
||||
for (Vector3 vertex : vertices) {
|
||||
for (Vector3 vertex : objVertices) {
|
||||
newVertices.add(vertex.rotate(rotation).add(position));
|
||||
}
|
||||
|
||||
return newVertices;
|
||||
}
|
||||
|
||||
private List<Integer> loadFromFile(String filename) throws IOException {
|
||||
private Set<Line3D> loadFromFile(String filename) throws IOException {
|
||||
InputStream in = new FileInputStream(filename);
|
||||
final IOBJParser parser = new OBJParser();
|
||||
final OBJModel model = parser.parse(in);
|
||||
|
||||
List<Integer> edgeData = new ArrayList<>();
|
||||
Set<Line3D> edges = new HashSet<>();
|
||||
|
||||
for (OBJVertex vertex : model.getVertices()) {
|
||||
vertices.add(new Vector3(vertex.x, vertex.y, vertex.z));
|
||||
objVertices.add(new Vector3(vertex.x, vertex.y, vertex.z));
|
||||
}
|
||||
|
||||
for (OBJObject object : model.getObjects()) {
|
||||
|
@ -143,17 +133,19 @@ public class WorldObject {
|
|||
List<OBJDataReference> references = face.getReferences();
|
||||
|
||||
for (int i = 0; i < references.size(); i++) {
|
||||
edgeData.add(references.get(i).vertexIndex);
|
||||
edgeData.add(references.get((i + 1) % references.size()).vertexIndex);
|
||||
edges.add(new Line3D(
|
||||
objVertices.get(references.get(i).vertexIndex),
|
||||
objVertices.get(references.get((i + 1) % references.size()).vertexIndex)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return edgeData;
|
||||
return edges;
|
||||
}
|
||||
|
||||
public WorldObject clone() {
|
||||
return new WorldObject(new ArrayList<>(vertices), new ArrayList<>(vertexPath), position, rotation);
|
||||
return new WorldObject(new ArrayList<>(objVertices), new ArrayList<>(vertexPath), position, rotation);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,25 +71,10 @@ public class ObjParser extends FileParser {
|
|||
int numFrames = (int) (2 * Math.PI / OBJ_ROTATE_SPEED);
|
||||
|
||||
for (int i = 0; i < numFrames; i++) {
|
||||
preRenderedFrames.add(new ArrayList<>());
|
||||
object.rotate(rotation);
|
||||
preRenderedFrames.add(camera.draw(object));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,7 @@
|
|||
package shapes;
|
||||
|
||||
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;
|
||||
|
||||
// Helper functions for the Shape interface.
|
||||
public class Shapes {
|
||||
|
@ -98,49 +89,4 @@ public class Shapes {
|
|||
.reduce(Double::sum)
|
||||
.orElse(0d);
|
||||
}
|
||||
|
||||
// Performs chinese postman on the input lines to get a path that will render cleanly on the
|
||||
// oscilloscope.
|
||||
// TODO: Speed up.
|
||||
public static List<Shape> sortLines(List<Line> lines) {
|
||||
Graph<Vector2, DefaultWeightedEdge> graph = new DefaultUndirectedWeightedGraph<>(
|
||||
DefaultWeightedEdge.class);
|
||||
|
||||
// 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 (Line line : lines) {
|
||||
graph.addVertex(line.getA());
|
||||
graph.addVertex(line.getB());
|
||||
|
||||
DefaultWeightedEdge edge = new DefaultWeightedEdge();
|
||||
graph.addEdge(line.getA(), line.getB(), edge);
|
||||
graph.setEdgeWeight(edge, line.length);
|
||||
}
|
||||
|
||||
ConnectivityInspector<Vector2, DefaultWeightedEdge> inspector = new ConnectivityInspector<>(
|
||||
graph);
|
||||
|
||||
List<Shape> sortedLines = new ArrayList<>();
|
||||
|
||||
// Chinese Postman can only be performed on connected graphs, so iterate over all connected
|
||||
// sub-graphs.
|
||||
for (Set<Vector2> vertices : inspector.connectedSets()) {
|
||||
AsSubgraph<Vector2, DefaultWeightedEdge> subgraph = new AsSubgraph<>(graph, vertices);
|
||||
ChinesePostman<Vector2, DefaultWeightedEdge> cp = new ChinesePostman<>();
|
||||
Collection<DefaultWeightedEdge> path;
|
||||
|
||||
try {
|
||||
path = cp.getCPPSolution(subgraph).getEdgeList();
|
||||
} catch (Exception e) {
|
||||
// Safety in case getCPPSolution fails.
|
||||
path = subgraph.edgeSet();
|
||||
}
|
||||
|
||||
for (DefaultWeightedEdge edge : path) {
|
||||
sortedLines.add(new Line(subgraph.getEdgeSource(edge), subgraph.getEdgeTarget(edge)));
|
||||
}
|
||||
}
|
||||
|
||||
return sortedLines;
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue