@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
264 lines (205 loc) • 8.12 kB
JavaScript
import { array_copy_unique } from "../../../../collection/array/array_copy_unique.js";
import BinaryHeap from "../../../../collection/heap/BinaryHeap.js";
import { update_topo_face_normals } from "../update_topo_face_normals.js";
import { build_edge_collapse_candidates } from "./build_edge_collapse_candidates.js";
import { collapse_all_degenerate_edges } from "./collapse_all_degenerate_edges.js";
import { collapse_degenerate_edge } from "./collapse_degenerate_edge.js";
import { EdgeCollapseCandidate } from "./EdgeCollapseCandidate.js";
import { build_vertex_quadratics } from "./quadratic/build_vertex_quadratics.js";
const scratch_array_1 = [];
const scratch_array_2 = [];
/**
*
* @param {EdgeCollapseCandidate} edge
*/
export function extract_edge_cost(edge) {
return edge.cost;
}
/**
* @param {number} number_faces_to_remove minimum number of faces to removed
* @param {BinaryHeap<EdgeCollapseCandidate>} heap
* @param {TopoMesh} mesh
* @param {Map<TopoEdge, EdgeCollapseCandidate>} edge_to_collapse_map
* @param {Map<number, Quadratic3>} vertex_quadratics
* @param {Set<number>} restricted_vertices
*/
export function collapse_edges(
number_faces_to_remove,
heap,
mesh,
edge_to_collapse_map,
vertex_quadratics,
restricted_vertices
) {
const mesh_faces = mesh.getFaces();
const initial_face_count = mesh_faces.size;
const target_face_count = initial_face_count - number_faces_to_remove;
while (mesh_faces.size > target_face_count && !heap.isEmpty()) {
const collapse_candidate = heap.pop();
if (!collapse_candidate.edge.isLinked()) {
// no longer connected
continue;
}
// if (!collapse_candidate.validate()) {
// console.log('!');
//
// // edge is out of date, update it
// collapse_candidate.update();
//
// // push back onto the heap
// open_set.push(collapse_candidate);
// continue;
// }
// find edges that need to be updated
const victim_vertex = collapse_candidate.victim_vertex;
// record edges who's cost that might have been affected by collapse
const neighbour_count = victim_vertex.computeNeighbourVertices(scratch_array_2, 0);
// debugValidateMesh(mesh);
// perform edge collapse
collapse_candidate.collapse(mesh, vertex_quadratics);
// debugValidateMesh(mesh);
const victim_vertex_attached_faces = victim_vertex.faces;
// const victim_vertex_attached_faces = collapse_candidate.target_vertex.faces;
const victim_vertex_attached_face_count = victim_vertex_attached_faces.length;
// update face normals around collapsed vertex
update_topo_face_normals(victim_vertex_attached_faces, victim_vertex_attached_face_count);
let update_edge_count = 0;
// for (let i = 0; i < victim_vertex_attached_face_count; i++) {
// const victim_face = victim_vertex_attached_faces[i];
//
// if (!victim_face.isLinked()) {
// // disconnected face
// continue;
// }
//
// const edges = victim_face.edges;
//
// const addition_count = array_copy_unique(edges, 0, scratch_array_1, update_edge_count, edges.length);
//
// update_edge_count += addition_count;
// }
update_edge_count += array_copy_unique(collapse_candidate.target_vertex.edges, 0, scratch_array_1, update_edge_count, victim_vertex.edges.length);
// const copy_of_update_edges = scratch_array_1.slice(0, update_edge_count);
//
for (let i = 0; i < neighbour_count; i++) {
/**
*
* @type {TopoVertex}
*/
const v = scratch_array_2[i];
const edges = v.edges;
const addition_count = array_copy_unique(edges, 0, scratch_array_1, update_edge_count, edges.length);
update_edge_count += addition_count;
}
// update edge costs
for (let i = 0; i < update_edge_count; i++) {
/**
*
* @type {TopoEdge}
*/
const edge = scratch_array_1[i];
const edge_faces = edge.faces;
const edge_face_count = edge_faces.length;
if (edge_face_count <= 0) {
//disconnected edge, skip
continue;
}
const collapseEdge = edge_to_collapse_map.get(edge);
const heap_index = heap.data.indexOf(collapseEdge);
if (heap_index === -1) {
// edge is not in the heap, meaning it was already processed
continue;
}
if (edge.isDegenerateEdge()) {
if (edge.isLinked()) {
// edge is degenerate, it can be safely removed
collapse_degenerate_edge(edge, mesh);
}
// removed_edges.push(collapseEdge);
// exclude edge
heap.deleteByIndex(heap_index);
continue;
}
// update length
edge.computeSquaredLength();
// recompute cost
const is_valid = collapseEdge.update(vertex_quadratics, restricted_vertices);
if (is_valid) {
// update the heap, re-scoring element
heap.bubbleDown(heap_index);
} else {
// edge is no longer valid for collapse, remove from heap
heap.deleteByIndex(heap_index);
}
}
// DEBUG validate cost
// for (let i = 0; i < open_set.length; i++) {
// const datum = open_set.data[i];
//
// if (!datum.edge.isLinked()) {
// continue;
// }
//
// if (!datum.validate()) {
// debugger;
// }
//
// const delta = datum.validateUpdate();
// if (delta !== 0) {
// debugger;
// }
// }
}
}
/**
* Simplifies a given topology mesh by reducing number of faces. Preserves original vertices
* NOTE: preserves outline of the mesh, that is the open edge
* NOTE: assumes that face normals are set
* @param {TopoMesh} mesh
* @param {number} num_faces_to_remove desired number of faces to remove
* @param {Set<number>} [restricted_vertices] vertices that are not allowed to be removed
*/
export function simplifyTopoMesh(mesh, num_faces_to_remove, restricted_vertices = new Set()) {
const faces = mesh.getFaces();
const face_count = faces.size;
if (num_faces_to_remove <= 0 || face_count <= 0) {
// we're already at the target
return;
}
/**
*
* @type {BinaryHeap<EdgeCollapseCandidate>}
*/
const open_set = new BinaryHeap(extract_edge_cost);
/**
*
* @type {Map<TopoEdge, EdgeCollapseCandidate>}
*/
const edge_to_collapse_map = new Map();
/**
*
* @type {Map<number, Quadratic3>}
*/
const vertex_quadratics = new Map();
build_vertex_quadratics({ mesh: mesh, quadratics: vertex_quadratics });
// compute collapse cost of each vertex/edge pair
build_edge_collapse_candidates(
mesh,
open_set,
edge_to_collapse_map,
vertex_quadratics,
restricted_vertices
);
collapse_edges(
num_faces_to_remove,
open_set,
mesh,
edge_to_collapse_map,
vertex_quadratics,
restricted_vertices
);
// debugValidateMesh(mesh);
// get rid of degenerate edges
collapse_all_degenerate_edges(mesh.getEdges(), mesh);
// debugValidateMesh(mesh);
}