RosettaCodeData/Task/Catmull-Clark-subdivision-s.../JavaScript/catmull-clark-subdivision-s...

256 lines
7.2 KiB
JavaScript

// A point of the current Catmull-Clark surface.
class Point {
constructor(x = 0.0, y = 0.0, z = 0.0) {
this.x = x;
this.y = y;
this.z = z;
}
add(other) {
return new Point(this.x + other.x, this.y + other.y, this.z + other.z);
}
multiply(factor) {
return new Point(this.x * factor, this.y * factor, this.z * factor);
}
divide(factor) {
return this.multiply(1.0 / factor);
}
lessThan(other) {
return (this.x < other.x) ||
(this.x === other.x && this.y < other.y) ||
(this.x === other.x && this.y === other.y && this.z < other.z);
}
equals(other) {
return this.x === other.x && this.y === other.y && this.z === other.z;
}
format(value) {
const formattedValue = value.toFixed(3);
return (value >= 0) ? " " + formattedValue : formattedValue;
}
toString() {
return "(" + this.format(this.x) + ", " + this.format(this.y) + ", " + this.format(this.z) + ")";
}
}
// Return the centroid point of the given array of points.
function centroid(points) {
let sum = new Point();
for (const point of points) {
sum = sum.add(point);
}
return sum.divide(points.length);
}
// An edge of the current Catmull-Clark surface.
class Edge {
constructor(aBegin, aEnd) {
if (aEnd.lessThan(aBegin)) {
[aBegin, aEnd] = [aEnd, aBegin];
}
this.begin = aBegin;
this.end = aEnd;
this.mid_edge = centroid([this.begin, this.end]);
this.hole_edge = false;
}
contains(point) {
return point.equals(this.begin) || point.equals(this.end);
}
lessThan(other) {
return this.begin.lessThan(other.begin) ||
(this.begin.equals(other.begin) && this.end.lessThan(other.end));
}
equals(other) {
return this.contains(other.begin) && this.contains(other.end);
}
}
// A face of the current Catmull-Clark surface.
class Face {
constructor(aVertices) {
this.vertices = aVertices;
this.face_point = centroid(this.vertices);
this.edges = [];
for (let i = 0; i < this.vertices.length - 1; ++i) {
this.edges.push(new Edge(this.vertices[i], this.vertices[i + 1]));
}
this.edges.push(new Edge(this.vertices[this.vertices.length - 1], this.vertices[0]));
}
contains(vertex) {
if (vertex instanceof Point) {
return this.vertices.some(v => v.equals(vertex));
} else if (vertex instanceof Edge) {
return this.contains(vertex.begin) && this.contains(vertex.end);
}
return false;
}
toString() {
let result = "Face: ";
for (let i = 0; i < this.vertices.length - 1; ++i) {
result += this.vertices[i].toString() + ", ";
}
return result + this.vertices[this.vertices.length - 1].toString();
}
}
// Return a map containing, for each vertex,
// the new vertex created by the current iteration of the Catmull-Clark surface subdivision algorithm.
function next_vertices(edges, faces) {
const next_vertices_map = new Map();
const vertices = new Set();
// Collect all unique vertices
for (const face of faces) {
for (const vertex of face.vertices) {
vertices.add(vertex);
}
}
// Convert Set to Array for iteration
const verticesArray = Array.from(vertices);
for (const vertex of verticesArray) {
const faces_for_vertex = faces.filter(face => face.contains(vertex));
const edges_for_vertex = edges.filter(edge => edge.contains(vertex));
if (faces_for_vertex.length !== edges_for_vertex.length) {
const mid_edge_of_hole_edges = edges_for_vertex
.filter(edge => edge.hole_edge)
.map(edge => edge.mid_edge);
mid_edge_of_hole_edges.push(vertex);
next_vertices_map.set(vertex, centroid(mid_edge_of_hole_edges));
} else {
const face_count = faces_for_vertex.length;
const multiple_1 = (face_count - 3) / face_count;
const multiple_2 = 1.0 / face_count;
const multiple_3 = 2.0 / face_count;
const next_vertex_1 = vertex.multiply(multiple_1);
const face_points = faces_for_vertex.map(face => face.face_point);
const next_vertex_2 = centroid(face_points).multiply(multiple_2);
const mid_edges = edges_for_vertex.map(edge => edge.mid_edge);
const next_vertex_3 = centroid(mid_edges).multiply(multiple_3);
const next_vertex_4 = next_vertex_1.add(next_vertex_2);
next_vertices_map.set(vertex, next_vertex_4.add(next_vertex_3));
}
}
return next_vertices_map;
}
// The Catmull-Clarke surface subdivision algorithm.
function catmull_clark_surface_subdivision(faces) {
// Determine, for each edge, whether or not it is an edge of a hole,
// and set its edge point accordingly.
for (const face of faces) {
for (const edge of face.edges) {
const face_points_for_edge = [];
for (const search_face of faces) {
if (search_face.contains(edge)) {
face_points_for_edge.push(search_face.face_point);
}
}
if (face_points_for_edge.length === 2) {
edge.hole_edge = false;
edge.edge_point = centroid([edge.mid_edge, centroid(face_points_for_edge)]);
} else {
edge.hole_edge = true;
edge.edge_point = edge.mid_edge;
}
}
}
// Collect all edges
const edges = [];
for (const face of faces) {
for (const edge of face.edges) {
edges.push(edge);
}
}
const next_vertex_map = next_vertices(edges, faces);
const next_faces = [];
for (const face of faces) {
if (face.vertices.length >= 3) { // A face with 2 or fewer points does not contribute to the surface
const face_point = face.face_point;
for (let i = 0; i < face.vertices.length; ++i) {
const prevIndex = (i - 1 + face.vertices.length) % face.vertices.length;
next_faces.push(new Face([
next_vertex_map.get(face.vertices[i]),
face.edges[i].edge_point,
face_point,
face.edges[prevIndex].edge_point
]));
}
}
}
return next_faces;
}
// Display the current Catmull-Clark surface on the console.
function displaySurface(faces) {
console.log("Surface {");
for (const face of faces) {
console.log(face.toString());
}
console.log("}");
console.log();
}
// Main function
function main() {
const faces = [
new Face([
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([
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([
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([
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([
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([
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);
const iterations = 1;
let result = faces;
for (let i = 0; i < iterations; ++i) {
result = catmull_clark_surface_subdivision(result);
}
displaySurface(result);
}
// Run the main function
main();