Catmull–Clark subdivision surface: Difference between revisions
m
Rewritten code to deal with holes in the surface.
m (Minor edit.) |
m (Rewritten code to deal with holes in the surface.) |
||
Line 917:
<syntaxhighlight lang="java">
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
public final class CatmullClarkSurfaceSubdivision {
public static void main(String[] args) {
List<
new Point(-1.0, -1.0, 1.0),
new Point( 1.0, -1.0, 1.0),
new Point( 1.0, 1.0, 1.0) )),
new Face(List.of( new Point( 1.0, 1.0,
new Point(
new Point(
new Point( 1.0, 1.0, -1.0) )),
new Face(List.of( new Point( 1.0, 1.0, -1.0),
new Point( 1.0, -1.0, -1.0),
new Point(-1.0, -1.0, -1.0),
new Point(-1.0, 1.0, -1.0) )),
new Face(List.of( new Point(-1.0, 1.0, -1.0),
new Point(-1.0, 1.0, 1.0),
new Point( 1.0, 1.0, 1.0),
new Point( 1.0, 1.0, -1.0) )),
new Face(List.of( new Point(-1.0, 1.0, -1.0),
new Point(-1.0, -1.0, -1.0),
new Point(-1.0, -1.0, 1.0),
new Point(-1.0, 1.0, 1.0) )),
new Face(List.of( new Point(-1.0, -1.0, -1.0),
new Point(-1.0, -1.0, 1.0),
new Point( 1.0, -1.0, 1.0),
new Point( 1.0, -1.0, -1.0) )) );
displaySurface(faces);
final int iterations = 1;
for ( int i = 0; i < iterations; i++ ) {
}
displaySurface(faces);
}
// The Catmull-Clarke surface subdivision algorithm.
private static List<Face> catmullClarkSurfaceSubdivision(List<Face> faces) {
// Determine, for each edge, whether or not it is an edge of a hole, and set its edge point accordingly.
List<Edge> edges = faces.stream().map( face -> face.edges.stream() ).flatMap(Function.identity()).toList();
for ( Edge edge : edges ) {
List<Point> facePointsForEdge =
faces.stream().filter( face -> face.contains(edge) ).map( face -> face.facePoint ).toList();
if ( facePointsForEdge.size() == 2 ) {
edge.holeEdge = false;
edge.edgePoint = centroid(List.of( edge.midEdge, centroid(facePointsForEdge) ));
} else {
edge.holeEdge = true;
edge.edgePoint = edge.midEdge;
}
}
Map<Point, Point> nextVertices = nextVertices(edges, faces);
List<Face> nextFaces = new ArrayList<Face>();
for ( Face face : faces ) { // The face may contain any number of points
if ( face.vertices.size() >= 3 ) { // A face with 2 or fewer points does not contribute to the surface
Point facePoint = face.facePoint;
for ( int i = 0; i < face.vertices.size(); i++ ) {
nextFaces.addLast( new Face(List.of(
nextVertices.get(face.vertices.get(i)),
face.edges.get(i).edgePoint,
facePoint,
face.edges.get(Math.floorMod(i - 1, face.vertices.size())).edgePoint )) );
}
}
}
return nextFaces;
}
// Return a
//
private static
Map<Point, Point> nextVertices = new HashMap<Point, Point>();
List<Point> vertices =
faces.stream().map( face -> face.vertices.stream() ).flatMap(Function.identity()).distinct().toList();
for ( Point vertex : vertices ) {
List<Face> facesForVertex = faces.stream().filter( face -> face.contains(vertex) ).toList();
List<Edge> edgesForVertex = edges.stream().filter( edge -> edge.contains(vertex) ).distinct().toList();
if ( facesForVertex.size() != edgesForVertex.size() ) {
List<Point> midEdgeOfHoleEdges = edgesForVertex.stream().filter( edge -> edge.holeEdge )
.map( edge -> edge.midEdge ).collect(Collectors.toList());
midEdgeOfHoleEdges.add(vertex);
nextVertices.put(vertex, centroid(midEdgeOfHoleEdges));
} else {
final int faceCount = facesForVertex.size();
final double multipleOne = (double) ( faceCount - 3 ) / faceCount;
final double multipleTwo = 1.0 / faceCount;
final double multipleThree = 2.0 / faceCount;
Point nextVertexOne = vertex.multiply(multipleOne);
List<Point> facePoints = facesForVertex.stream().map( face -> face.facePoint ).toList();
Point nextVertexTwo = centroid(facePoints).multiply(multipleTwo);
List<Point> midEdges = edgesForVertex.stream().map( edge -> edge.midEdge ).toList();
Point nextVertexThree = centroid(midEdges).multiply(multipleThree);
Point nextVertexFour = nextVertexOne.add(nextVertexTwo);
nextVertices.put(vertex, nextVertexFour.add(nextVertexThree));
}
}
return nextVertices;
}
// Return
private static Point centroid(List<Point> points) {
return points.stream().reduce(Point.ZERO, (left, right) -> left.add(right) ).divide(points.size());
}
// Display the current Catmull-Clark surface on the console.
private static
System.out.println("Surface {");
faces.stream().forEach(System.out::println);
System.out.println("}" + System.lineSeparator());
}
// A point of the current Catmull-Clark surface.
private static
public Point(double aX, double aY, double aZ) {
x = aX; y = aY; z = aZ;
}
@Override
public int compareTo(Point other) {
if ( x == other.x ) {
if ( y == other.y ) {
return Double.compare(z, other.z);
}
return Double.compare(y, other.y);
}
return Double.compare(x, other.x);
}
@Override
public boolean equals(Object other) {
return switch ( other ) {
case Point point -> x == point.x && y == point.y && z == point.z;
default -> false;
};
}
@Override
public int hashCode() {
return Objects.hash(x, y, z);
}
Line 1,190 ⟶ 1,081:
}
public Point
return new Point(x * factor, y * factor, z * factor);
}
public Point
return
}
Line 1,209 ⟶ 1,096:
private String format(double value) {
return ( value >= 0 ) ? String.format(" %.
}
private final double x, y, z;
}
// An edge of the current Catmull-Clark surface.
private static final class Edge {
public Edge(
if ( aBegin.compareTo(aEnd) <= 0 ) {
begin = aBegin; end = aEnd;
} else {
begin = aEnd; end = aBegin;
}
midEdge = centroid(List.of( begin, end ));
}
@Override
public boolean equals(Object other) {
return switch ( other ) {
case Edge edge -> begin.equals(edge.begin) && end.equals(edge.end);
default -> false;
};
}
@Override
public int
return Objects.hash(begin, end);
}
public
return point.equals(begin) || point.equals(end);
}
public boolean holeEdge;
public Point edgePoint;
public final Point begin, end, midEdge;
}
// A face of the current Catmull-Clark surface.
private static
public Face(List<Point> aVertices) {
vertices = new ArrayList<Point>(aVertices);
facePoint = centroid(vertices);
edges = new ArrayList<Edge>();
for ( int i = 0; i < vertices.size() - 1; i++ ) {
edges.addLast( new Edge(vertices.get(i), vertices.get(i + 1)) );
}
edges.addLast( new Edge(vertices.getLast(), vertices.getFirst()) );;
}
public boolean contains(Point vertex) {
return vertices.contains(vertex);
}
public boolean contains(Edge edge) {
return contains(edge.begin) && contains(edge.end);
}
public String toString() {
return "Face: " + vertices.stream().map( point -> point.toString() ).collect(Collectors.joining("; "));
}
public final List<Point> vertices;
public final Point facePoint;
public final List<Edge> edges;
}
}
Line 1,264 ⟶ 1,176:
{{ out }}
<pre>
Surface {
Face: (-1.000, 1.000, 1.000); (-1.000, -1.000, 1.000); ( 1.000, -1.000, 1.000); ( 1.000, 1.000, 1.000)
Face: ( 1.000, 1.000, 1.000); ( 1.000, -1.000, 1.000); ( 1.000, -1.000, -1.000); ( 1.000, 1.000, -1.000)
Face: ( 1.000, 1.000, -1.000); ( 1.000, -1.000, -1.000); (-1.000, -1.000, -1.000); (-1.000, 1.000, -1.000)
Face: (-1.000, 1.000, -1.000); (-1.000, 1.000, 1.000); ( 1.000, 1.000, 1.000); ( 1.000, 1.000, -1.000)
Face: (-1.000, 1.000, -1.000); (-1.000, -1.000, -1.000); (-1.000, -1.000, 1.000); (-1.000, 1.000, 1.000)
Face: (-1.000, -1.000, -1.000); (-1.000, -1.000, 1.000); ( 1.000, -1.000, 1.000); ( 1.000, -1.000, -1.000)
}
Surface {
Face: (-0.556, 0.556, 0.556); (-0.750, 0.000, 0.750); ( 0.000, 0.000, 1.000); ( 0.000, 0.750, 0.750)
Face: (-0.556, -0.556, 0.556); ( 0.000, -0.750, 0.750); ( 0.000, 0.000, 1.000); (-0.750, 0.000, 0.750)
Face: ( 0.556, -0.556, 0.556); ( 0.750, 0.000, 0.750); ( 0.000, 0.000, 1.000); ( 0.000, -0.750, 0.750)
Face: ( 0.556, 0.556, 0.556); ( 0.000, 0.750, 0.750); ( 0.000, 0.000, 1.000); ( 0.750, 0.000, 0.750)
Face: ( 0.556, 0.556, 0.556); ( 0.750, 0.000, 0.750); ( 1.000, 0.000, 0.000); ( 0.750, 0.750, 0.000)
Face: ( 0.556, -0.556, 0.556); ( 0.750, -0.750, 0.000); ( 1.000, 0.000, 0.000); ( 0.750, 0.000, 0.750)
Face: ( 0.556, -0.556, -0.556); ( 0.750, 0.000, -0.750); ( 1.000, 0.000, 0.000); ( 0.750, -0.750, 0.000)
Face: ( 0.556, 0.556, -0.556); ( 0.750, 0.750, 0.000); ( 1.000, 0.000, 0.000); ( 0.750, 0.000, -0.750)
Face: ( 0.556, 0.556, -0.556); ( 0.750, 0.000, -0.750); ( 0.000, 0.000, -1.000); ( 0.000, 0.750, -0.750)
Face: ( 0.556, -0.556, -0.556); ( 0.000, -0.750, -0.750); ( 0.000, 0.000, -1.000); ( 0.750, 0.000, -0.750)
Face: (-0.556, -0.556, -0.556); (-0.750, 0.000, -0.750); ( 0.000, 0.000, -1.000); ( 0.000, -0.750, -0.750)
Face: (-0.556, 0.556, -0.556); ( 0.000, 0.750, -0.750); ( 0.000, 0.000, -1.000); (-0.750, 0.000, -0.750)
Face: (-0.556, 0.556, -0.556); (-0.750, 0.750, 0.000); ( 0.000, 1.000, 0.000); ( 0.000, 0.750, -0.750)
Face: (-0.556, 0.556, 0.556); ( 0.000, 0.750, 0.750); ( 0.000, 1.000, 0.000); (-0.750, 0.750, 0.000)
Face: ( 0.556, 0.556, 0.556); ( 0.750, 0.750, 0.000); ( 0.000, 1.000, 0.000); ( 0.000, 0.750, 0.750)
Face: ( 0.556, 0.556, -0.556); ( 0.000, 0.750, -0.750); ( 0.000, 1.000, 0.000); ( 0.750, 0.750, 0.000)
Face: (-0.556, 0.556, -0.556); (-0.750, 0.000, -0.750); (-1.000, 0.000, 0.000); (-0.750, 0.750, 0.000)
Face: (-0.556, -0.556, -0.556); (-0.750, -0.750, 0.000); (-1.000, 0.000, 0.000); (-0.750, 0.000, -0.750)
Face: (-0.556, -0.556, 0.556); (-0.750, 0.000, 0.750); (-1.000, 0.000, 0.000); (-0.750, -0.750, 0.000)
Face: (-0.556, 0.556, 0.556); (-0.750, 0.750, 0.000); (-1.000, 0.000, 0.000); (-0.750, 0.000, 0.750)
Face: (-0.556, -0.556, -0.556); (-0.750, -0.750, 0.000); ( 0.000, -1.000, 0.000); ( 0.000, -0.750, -0.750)
Face: (-0.556, -0.556, 0.556); ( 0.000, -0.750, 0.750); ( 0.000, -1.000, 0.000); (-0.750, -0.750, 0.000)
Face: ( 0.556, -0.556, 0.556); ( 0.750, -0.750, 0.000); ( 0.000, -1.000, 0.000); ( 0.000, -0.750, 0.750)
Face: ( 0.556, -0.556, -0.556); ( 0.000, -0.750, -0.750); ( 0.000, -1.000, 0.000); ( 0.750, -0.750, 0.000)
}
</pre>
|