@woosh/meep-engine 2.37.20 → 2.38.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/collection/array/array_get_index_in_range.js +16 -0
- package/{engine/navigation/grid → core/collection/heap}/BinaryHeap.js +6 -1
- package/{engine/navigation/grid → core/collection/heap}/FastBinaryHeap.js +3 -2
- package/{engine/navigation/grid → core/collection/heap}/FastBinaryHeap.spec.js +3 -3
- package/core/collection/heap/Uin32Heap.spec.js +59 -0
- package/core/collection/heap/Uint32Heap.js +385 -0
- package/core/geom/3d/topology/computeTopoMeshVertexDuplicates.js +9 -6
- package/core/geom/3d/topology/expandConnectivityByLocality.js +5 -5
- package/core/geom/3d/topology/query/query_edge_is_boundary.js +7 -0
- package/core/geom/3d/topology/query/query_edge_is_manifold.js +13 -0
- package/core/geom/3d/topology/query/query_edge_is_manifold_or_boundary.js +11 -0
- package/core/geom/3d/topology/query/query_edge_is_wire.js +13 -0
- package/core/geom/3d/topology/query/query_edge_other_vertex.js +20 -0
- package/core/geom/3d/topology/query/query_edge_share_vert.js +9 -0
- package/core/geom/3d/topology/query/query_face_get_other_edges.js +39 -0
- package/core/geom/3d/topology/query/query_face_next_vertex.js +19 -0
- package/core/geom/3d/topology/query/query_face_prev_vertex.js +18 -0
- package/core/geom/3d/topology/query/query_vertex_in_edge.js +14 -0
- package/core/geom/3d/topology/query/query_vertex_pair_share_face.js +24 -0
- package/core/geom/3d/topology/query/query_vertices_in_edge.js +19 -0
- package/core/geom/3d/topology/simplify/collapseEdge.spec.js +3 -5
- package/core/geom/3d/topology/simplify/collapse_all_degenerate_edges.js +8 -10
- package/core/geom/3d/topology/simplify/compute_face_normal_change_dot_product.js +12 -2
- package/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.js +277 -0
- package/core/geom/3d/topology/simplify/edge_collapse_quadratic.js +126 -0
- package/core/geom/3d/topology/simplify/prototypeMeshSimplification.js +502 -0
- package/core/geom/3d/topology/simplify/quadratic/Quadratic3.js +37 -5
- package/core/geom/3d/topology/simplify/quadratic/build_vertex_quadratics.js +86 -1
- package/core/geom/3d/topology/simplify/simplifyTopoMesh.js +4 -4
- package/core/geom/3d/topology/simplify/simplifyTopoMesh2.js +119 -0
- package/core/geom/3d/topology/simplify/tm_edge_collapse_is_degenerate_flip.js +81 -0
- package/core/geom/3d/topology/{TopoEdge.js → struct/TopoEdge.js} +47 -20
- package/core/geom/3d/topology/{TopoEdge.spec.js → struct/TopoEdge.spec.js} +0 -0
- package/core/geom/3d/topology/{TopoMesh.js → struct/TopoMesh.js} +20 -41
- package/core/geom/3d/topology/{TopoTriangle.js → struct/TopoTriangle.js} +15 -25
- package/core/geom/3d/topology/{TopoVertex.js → struct/TopoVertex.js} +21 -4
- package/core/geom/3d/topology/{TopoVertex.spec.js → struct/TopoVertex.spec.js} +0 -0
- package/core/geom/3d/topology/tm_edge_kill.js +24 -0
- package/core/geom/3d/topology/tm_edge_splice.js +42 -0
- package/core/geom/3d/topology/tm_face_area.js +18 -0
- package/core/geom/3d/topology/tm_face_kill.js +12 -0
- package/core/geom/3d/topology/tm_face_normal.js +14 -0
- package/core/geom/3d/topology/tm_kill_only_edge.js +35 -0
- package/core/geom/3d/topology/tm_kill_only_face.js +12 -0
- package/core/geom/3d/topology/tm_kill_only_vert.js +14 -0
- package/core/geom/3d/topology/tm_vert_kill.js +19 -0
- package/core/geom/3d/topology/tm_vert_splice.js +64 -0
- package/core/geom/3d/topology/tm_vertex_compute_normal.js +42 -0
- package/core/geom/3d/topology/topoMeshToBufferGeometry.js +18 -4
- package/core/geom/3d/topology/weld_duplicate_vertices.js +63 -0
- package/core/geom/packing/MaxRectangles.js +1 -1
- package/core/graph/cluster_mesh_metis.js +16 -0
- package/core/graph/coarsen_graph.js +1 -1
- package/core/graph/graph_k_means_cluster.js +1 -1
- package/core/math/random/seededRandom.js +2 -31
- package/core/math/random/seededRandom_Mulberry32.js +31 -0
- package/core/math/random/seededRandom_sine.js +33 -0
- package/editor/view/node-graph/NodeGraphView.js +2 -2
- package/editor/view/node-graph/NodeView.js +7 -9
- package/engine/ecs/parent/ParentEntitySystem.js +57 -0
- package/engine/ecs/terrain/util/tensionOptimizeUV.js +1 -1
- package/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.d.ts +4 -0
- package/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +31 -0
- package/engine/graphics/ecs/sprite/prototypeSpriteSystem.js +4 -0
- package/engine/graphics/micron/build/PatchRepresentation.js +7 -3
- package/engine/graphics/micron/build/buildMicronGeometryFromBufferGeometry.js +14 -8
- package/engine/graphics/micron/build/clustering/build_clustering_2.js +1 -1
- package/engine/graphics/micron/build/clustering/build_leaf_patches.js +2 -2
- package/engine/graphics/micron/build/clustering/build_leaf_patches_metis.js +1 -1
- package/engine/graphics/micron/build/hierarchy/buildAbstractPatchHierarchy.js +21 -3
- package/engine/graphics/micron/build/hierarchy/merge_patches.js +96 -43
- package/engine/graphics/micron/build/hierarchy/qvdr_build_simplified_clusters.js +11 -5
- package/engine/graphics/micron/format/VirtualGeometry.js +46 -3
- package/engine/graphics/micron/format/micron_build_proxy_geometry.js +4 -2
- package/engine/graphics/micron/prototypeVirtualGeometry.js +45 -8
- package/engine/graphics/micron/render/instanced/shader/shader_rewrite_standard.js +2 -2
- package/engine/graphics/micron/render/refinement/get_geometry_patch_cut.js +15 -3
- package/engine/graphics/micron/simplifyGeometry.js +1 -1
- package/engine/input/devices/PointerDevice.d.ts +1 -1
- package/engine/input/devices/PointerDevice.js +17 -2
- package/engine/navigation/grid/AStar.js +1 -1
- package/engine/navigation/grid/GridField.js +3 -2
- package/engine/ui/DraggableAspect.js +2 -2
- package/generation/grid/generation/discrete/GridTaskConnectRooms.js +1 -1
- package/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +1 -1
- package/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.js +1 -1
- package/generation/grid/generation/road/GridTaskGenerateRoads.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next vertex in the loop around the face
|
|
3
|
+
* @param {TopoTriangle} face
|
|
4
|
+
* @param {TopoVertex} vertex
|
|
5
|
+
* @returns {TopoVertex}
|
|
6
|
+
*/
|
|
7
|
+
export function query_face_next_vertex(face, vertex) {
|
|
8
|
+
const vertices = face.vertices;
|
|
9
|
+
const vertex_count = vertices.length;
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < vertex_count; i++) {
|
|
12
|
+
if (vertices[i] === vertex) {
|
|
13
|
+
return vertices[(i + 1) % vertex_count];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
throw new Error('Vertex is not part of this face');
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next vertex in the loop around the face
|
|
3
|
+
* @param {TopoTriangle} face
|
|
4
|
+
* @param {TopoVertex} vertex
|
|
5
|
+
* @returns {TopoVertex}
|
|
6
|
+
*/
|
|
7
|
+
export function query_face_prev_vertex(face, vertex) {
|
|
8
|
+
const vertices = face.vertices;
|
|
9
|
+
const vertex_count = vertices.length;
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < vertex_count; i++) {
|
|
12
|
+
if (vertices[i] === vertex) {
|
|
13
|
+
return vertices[(vertex_count + i - 1) % vertex_count];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
throw new Error('Vertex is not part of this face');
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { assert } from "../../../../assert.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {TopoEdge} edge
|
|
6
|
+
* @param {TopoVertex} vertex
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function query_vertex_in_edge(edge, vertex) {
|
|
10
|
+
assert.notNull(edge, 'edge');
|
|
11
|
+
|
|
12
|
+
return edge.v0 === vertex || edge.v1 === vertex;
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {TopoVertex} a
|
|
4
|
+
* @param {TopoVertex} b
|
|
5
|
+
* @returns {boolean}
|
|
6
|
+
*/
|
|
7
|
+
export function query_vertex_pair_share_face(a, b) {
|
|
8
|
+
const a_faces = a.faces;
|
|
9
|
+
const b_faces = b.faces;
|
|
10
|
+
|
|
11
|
+
const a_face_count = a_faces.length;
|
|
12
|
+
const b_face_count = b_faces.length;
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < a_face_count; i++) {
|
|
15
|
+
for (let j = 0; j < b_face_count; j++) {
|
|
16
|
+
|
|
17
|
+
if (a_faces[i] === b_faces[j]) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { assert } from "../../../../assert.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {TopoEdge} edge
|
|
6
|
+
* @param {TopoVertex} v2
|
|
7
|
+
* @param {TopoVertex} v1
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
export function query_vertices_in_edge(edge, v1, v2) {
|
|
11
|
+
assert.notNull(edge, 'edge');
|
|
12
|
+
assert.notNull(v1, 'v1');
|
|
13
|
+
assert.notNull(v2, 'v2');
|
|
14
|
+
|
|
15
|
+
const a = edge.v0;
|
|
16
|
+
const b = edge.v1;
|
|
17
|
+
|
|
18
|
+
return (a === v1 || a === v2) && (b === v1 || b === v2);
|
|
19
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { collapseEdge } from "./collapseEdge.js";
|
|
2
|
-
import { TopoEdge } from "../TopoEdge.js";
|
|
3
|
-
import { TopoMesh } from "../TopoMesh.js";
|
|
4
|
-
import { TopoVertex } from "../TopoVertex.js";
|
|
2
|
+
import { TopoEdge } from "../struct/TopoEdge.js";
|
|
3
|
+
import { TopoMesh } from "../struct/TopoMesh.js";
|
|
4
|
+
import { TopoVertex } from "../struct/TopoVertex.js";
|
|
5
5
|
|
|
6
6
|
test("Collapse empty edge not attached to anything else", () => {
|
|
7
7
|
const mesh = new TopoMesh();
|
|
@@ -21,6 +21,4 @@ test("Collapse empty edge not attached to anything else", () => {
|
|
|
21
21
|
collapseEdge(mesh, edge, v0, v1);
|
|
22
22
|
|
|
23
23
|
expect(mesh.containsEdge(edge)).toBe(false);
|
|
24
|
-
expect(mesh.containsVertex(v0)).toBe(false);
|
|
25
|
-
expect(mesh.containsVertex(v1)).toBe(true);
|
|
26
24
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tm_edge_kill } from "../tm_edge_kill.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
*
|
|
@@ -6,30 +6,28 @@ import { collapse_degenerate_edge } from "./collapse_degenerate_edge.js";
|
|
|
6
6
|
* @param {TopoMesh} mesh
|
|
7
7
|
*/
|
|
8
8
|
export function collapse_all_degenerate_edges(edges, mesh) {
|
|
9
|
-
let pass_edge_count;
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
let pass_edge_count;
|
|
12
11
|
|
|
13
12
|
do {
|
|
14
13
|
|
|
15
14
|
pass_edge_count = 0;
|
|
16
15
|
|
|
17
16
|
for (let edge of edges) {
|
|
18
|
-
if (dead_edges.has(edge)) {
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
17
|
|
|
22
18
|
if (!edge.isLinked()) {
|
|
23
|
-
dead_edges.add(edge);
|
|
24
19
|
continue;
|
|
25
20
|
}
|
|
26
21
|
|
|
27
22
|
if (edge.isDegenerateEdge()) {
|
|
28
|
-
collapse_degenerate_edge(edge, mesh);
|
|
23
|
+
// collapse_degenerate_edge(edge, mesh);
|
|
29
24
|
|
|
30
|
-
|
|
25
|
+
tm_edge_kill(mesh,edge);
|
|
26
|
+
|
|
27
|
+
// edge.unlink();
|
|
28
|
+
// tm_kill_only_edge(mesh, edge);
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
pass_edge_count++;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { v3_dot } from "../../../v3_dot.js";
|
|
2
|
+
import { compute_triangle_normal } from "../struct/TopoTriangle.js";
|
|
3
|
+
|
|
4
|
+
const scratch_normal = new Float32Array(3);
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Used to detect face normal flips for mesh optimization.
|
|
@@ -62,10 +65,17 @@ export function compute_face_normal_change_dot_product(face, vertex_old, vertex_
|
|
|
62
65
|
const crossY = vCBz * vABx - vCBx * vABz;
|
|
63
66
|
const crossZ = vCBx * vABy - vCBy * vABx;
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
// compute face normal
|
|
69
|
+
const vertices = face.vertices;
|
|
70
|
+
|
|
71
|
+
const vA = vertices[0];
|
|
72
|
+
const vB = vertices[1];
|
|
73
|
+
const vC = vertices[2];
|
|
74
|
+
|
|
75
|
+
compute_triangle_normal(scratch_normal, vA, vB, vC);
|
|
66
76
|
|
|
67
77
|
return v3_dot(
|
|
68
78
|
crossX, crossY, crossZ,
|
|
69
|
-
|
|
79
|
+
scratch_normal[0], scratch_normal[1], scratch_normal[2]
|
|
70
80
|
);
|
|
71
81
|
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { tm_edge_collapse } from "./edge_collapse_quadratic.js";
|
|
2
|
+
import { query_edge_other_vertex } from "../query/query_edge_other_vertex.js";
|
|
3
|
+
import { array_get_index_in_range } from "../../../../collection/array/array_get_index_in_range.js";
|
|
4
|
+
import { compute_edge_collapse_cost_quadratic } from "./quadratic/compute_edge_collapse_cost_quadratic.js";
|
|
5
|
+
import { tm_vertex_compute_normal } from "../tm_vertex_compute_normal.js";
|
|
6
|
+
import { min2 } from "../../../../math/min2.js";
|
|
7
|
+
import { vec3 } from "gl-matrix";
|
|
8
|
+
import { EPSILON } from "../../../../math/MathUtils.js";
|
|
9
|
+
import { tm_edge_collapse_is_degenerate_flip } from "./tm_edge_collapse_is_degenerate_flip.js";
|
|
10
|
+
import { query_edge_is_boundary } from "../query/query_edge_is_boundary.js";
|
|
11
|
+
import { tm_edge_kill } from "../tm_edge_kill.js";
|
|
12
|
+
import { squirrel3 } from "../../../../math/hash/squirrel3.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @type {TopoEdge[]}
|
|
17
|
+
*/
|
|
18
|
+
const scratch_edges = [];
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
const scratch_v3_0 = [];
|
|
22
|
+
const scratch_v3_1 = [];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param {TopoVertex} v
|
|
27
|
+
* @returns {number}
|
|
28
|
+
*/
|
|
29
|
+
function edge_collapse_vertex_removal_cost(v) {
|
|
30
|
+
let result = 0;
|
|
31
|
+
|
|
32
|
+
const edges = v.edges;
|
|
33
|
+
const edge_count = edges.length;
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < edge_count; i++) {
|
|
36
|
+
const edge = edges[i];
|
|
37
|
+
|
|
38
|
+
if (query_edge_is_boundary(edge)) {
|
|
39
|
+
result += 100;
|
|
40
|
+
} else {
|
|
41
|
+
// prefer not to remove vertices that have lots of faces attached. This is to prevent cases where more and more faces are aggregated around a single vertex
|
|
42
|
+
result += 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* @param {TopoEdge} edge
|
|
52
|
+
* @returns {TopoVertex}
|
|
53
|
+
*/
|
|
54
|
+
export function pick_lowest_cost_edge_collapse_vertex(edge) {
|
|
55
|
+
const v0 = edge.v0;
|
|
56
|
+
const v1 = edge.v1;
|
|
57
|
+
|
|
58
|
+
if (squirrel3(v0.index) > squirrel3(v1.index)) {
|
|
59
|
+
return v0;
|
|
60
|
+
} else {
|
|
61
|
+
return v1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (edge_collapse_vertex_removal_cost(v0) > edge_collapse_vertex_removal_cost(v1)) {
|
|
65
|
+
return v0;
|
|
66
|
+
} else {
|
|
67
|
+
return v1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param {TopoEdge} edge
|
|
74
|
+
* @param {Set<number>} restricted_vertices locked vertices
|
|
75
|
+
* @returns {TopoVertex|undefined}
|
|
76
|
+
*/
|
|
77
|
+
export function edge_collapse_pick_target_vertex(edge, restricted_vertices) {
|
|
78
|
+
|
|
79
|
+
const v0 = edge.v0;
|
|
80
|
+
const v1 = edge.v1;
|
|
81
|
+
|
|
82
|
+
const v0_restricted = restricted_vertices.has(v0.index);
|
|
83
|
+
const v1_restricted = restricted_vertices.has(v1.index);
|
|
84
|
+
|
|
85
|
+
let expected_target;
|
|
86
|
+
|
|
87
|
+
if (v0_restricted && v1_restricted) {
|
|
88
|
+
return;
|
|
89
|
+
} else if (v0_restricted) {
|
|
90
|
+
if (tm_edge_collapse_is_degenerate_flip(edge, v0)) {
|
|
91
|
+
return;
|
|
92
|
+
} else {
|
|
93
|
+
expected_target = v0;
|
|
94
|
+
}
|
|
95
|
+
} else if (v1_restricted) {
|
|
96
|
+
|
|
97
|
+
if (tm_edge_collapse_is_degenerate_flip(edge, v1)) {
|
|
98
|
+
return;
|
|
99
|
+
} else {
|
|
100
|
+
expected_target = v1;
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
const v0_degenerate = tm_edge_collapse_is_degenerate_flip(edge, v0);
|
|
104
|
+
const v1_degenerate = tm_edge_collapse_is_degenerate_flip(edge, v1);
|
|
105
|
+
if (v0_degenerate && v1_degenerate) {
|
|
106
|
+
return;
|
|
107
|
+
} else if (v1_degenerate) {
|
|
108
|
+
expected_target = v0;
|
|
109
|
+
} else if (v0_degenerate) {
|
|
110
|
+
expected_target = v1;
|
|
111
|
+
} else {
|
|
112
|
+
expected_target = pick_lowest_cost_edge_collapse_vertex(edge);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return expected_target;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
*
|
|
121
|
+
* @param {TopoEdge} edge
|
|
122
|
+
* @returns {number}
|
|
123
|
+
*/
|
|
124
|
+
function build_edge_cost_squared_topology(edge) {
|
|
125
|
+
tm_vertex_compute_normal(scratch_v3_0, 0, edge.v0);
|
|
126
|
+
tm_vertex_compute_normal(scratch_v3_1, 0, edge.v1);
|
|
127
|
+
|
|
128
|
+
const distance_sqr = vec3.squaredDistance(edge.v0, edge.v1);
|
|
129
|
+
|
|
130
|
+
let cost = Math.abs(vec3.dot(scratch_v3_0, scratch_v3_1))
|
|
131
|
+
/ min2(-distance_sqr, -EPSILON);
|
|
132
|
+
|
|
133
|
+
return cost;
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
*
|
|
140
|
+
* @param {TopoEdge} edge
|
|
141
|
+
* @param {Map<number,Quadratic3>} quadratics
|
|
142
|
+
* @param {Set<number>} restricted_vertices
|
|
143
|
+
*/
|
|
144
|
+
export function compute_edge_collapse_cost(edge, quadratics, restricted_vertices) {
|
|
145
|
+
const expected_target = edge_collapse_pick_target_vertex(edge, restricted_vertices);
|
|
146
|
+
|
|
147
|
+
if (expected_target === undefined) {
|
|
148
|
+
return Number.POSITIVE_INFINITY;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
const v0 = edge.v0;
|
|
153
|
+
const v1 = edge.v1;
|
|
154
|
+
|
|
155
|
+
let cost = compute_edge_collapse_cost_quadratic(v0, v1, expected_target, quadratics);
|
|
156
|
+
|
|
157
|
+
// fallback to topology is quadratics are noise
|
|
158
|
+
if (cost < 1e-5) {
|
|
159
|
+
// subtracting cost to introduce minor possible variation in cost, to produce stable order
|
|
160
|
+
cost = build_edge_cost_squared_topology(edge) - cost;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return cost;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
*
|
|
168
|
+
* @param {TopoEdge} edge
|
|
169
|
+
* @param {Map<number,Quadratic3>} quadratics
|
|
170
|
+
* @param {Uint32Heap} heap
|
|
171
|
+
* @param {Set<number>} restricted_vertices
|
|
172
|
+
*/
|
|
173
|
+
function tm_build_cost(edge, quadratics, heap, restricted_vertices) {
|
|
174
|
+
const index_in_heap = heap.find_index_by_id(edge.index);
|
|
175
|
+
if (index_in_heap === -1) {
|
|
176
|
+
// not in heap
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let cost = compute_edge_collapse_cost(edge, quadratics, restricted_vertices);
|
|
181
|
+
|
|
182
|
+
heap.__update_score_by_index(index_in_heap, cost);
|
|
183
|
+
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
*
|
|
188
|
+
* @param {TopoMesh} mesh
|
|
189
|
+
* @param {TopoEdge}edge
|
|
190
|
+
* @param {TopoVertex} target
|
|
191
|
+
* @param {Map<number,Quadratic3>} quadratics
|
|
192
|
+
* @param {Uint32Heap} heap
|
|
193
|
+
* @param {Set<number>} restricted_vertices
|
|
194
|
+
* @returns {boolean}
|
|
195
|
+
*/
|
|
196
|
+
export function decimate_edge_collapse_snap(
|
|
197
|
+
mesh,
|
|
198
|
+
edge,
|
|
199
|
+
target,
|
|
200
|
+
quadratics,
|
|
201
|
+
heap,
|
|
202
|
+
restricted_vertices
|
|
203
|
+
) {
|
|
204
|
+
|
|
205
|
+
if (!edge.isLinked()) {
|
|
206
|
+
// edge is not part of topology, this should never happen in practice
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (edge.v0 === edge.v1) {
|
|
211
|
+
// degenerate edge, don't touch
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (tm_edge_collapse_is_degenerate_flip(edge, target)) {
|
|
216
|
+
// re-insert into heap with very high cost
|
|
217
|
+
heap.insert(edge.index, Number.POSITIVE_INFINITY);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// update quadratics
|
|
222
|
+
const victim_vertex = query_edge_other_vertex(edge, target);
|
|
223
|
+
|
|
224
|
+
if (!tm_edge_collapse(mesh, edge, target)) {
|
|
225
|
+
// failed collapse
|
|
226
|
+
// re-insert into heap with very high cost
|
|
227
|
+
heap.insert(edge.index, Number.POSITIVE_INFINITY);
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
const q_a = quadratics.get(victim_vertex.index);
|
|
233
|
+
const q_b = quadratics.get(target.index);
|
|
234
|
+
q_b.add(q_a);
|
|
235
|
+
|
|
236
|
+
// TODO we can probably discount all original faces around the target vertex
|
|
237
|
+
// record affected edges
|
|
238
|
+
const attached_faces = target.faces;
|
|
239
|
+
const attached_face_count = attached_faces.length;
|
|
240
|
+
|
|
241
|
+
let dirty_edge_count = 0;
|
|
242
|
+
|
|
243
|
+
for (let i = 0; i < attached_face_count; i++) {
|
|
244
|
+
const face = attached_faces[i];
|
|
245
|
+
|
|
246
|
+
const edges = face.edges;
|
|
247
|
+
const edge_count = edges.length;
|
|
248
|
+
|
|
249
|
+
for (let j = 0; j < edge_count; j++) {
|
|
250
|
+
|
|
251
|
+
const topoEdge = edges[j];
|
|
252
|
+
|
|
253
|
+
if (array_get_index_in_range(scratch_edges, topoEdge, 0, dirty_edge_count) === -1) {
|
|
254
|
+
scratch_edges[dirty_edge_count++] = topoEdge;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// update costs
|
|
261
|
+
for (let i = 0; i < dirty_edge_count; i++) {
|
|
262
|
+
const dirty_edge = scratch_edges[i];
|
|
263
|
+
|
|
264
|
+
if (dirty_edge.isDegenerateEdge()) {
|
|
265
|
+
heap.remove(dirty_edge.index);
|
|
266
|
+
|
|
267
|
+
tm_edge_kill(mesh, dirty_edge);
|
|
268
|
+
|
|
269
|
+
} else {
|
|
270
|
+
|
|
271
|
+
tm_build_cost(dirty_edge, quadratics, heap, restricted_vertices);
|
|
272
|
+
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { assert } from "../../../../assert.js";
|
|
2
|
+
import { query_edge_other_vertex } from "../query/query_edge_other_vertex.js";
|
|
3
|
+
import { tm_edge_kill } from "../tm_edge_kill.js";
|
|
4
|
+
import { tm_vert_splice } from "../tm_vert_splice.js";
|
|
5
|
+
import { tm_edge_splice } from "../tm_edge_splice.js";
|
|
6
|
+
import { query_face_get_other_edges } from "../query/query_face_get_other_edges.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @type {TopoEdge[]}
|
|
11
|
+
*/
|
|
12
|
+
const scratch_edges_0 = [];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param {TopoMesh} mesh
|
|
17
|
+
* @param {TopoEdge} edge
|
|
18
|
+
* @param {TopoVertex} other
|
|
19
|
+
* @returns {boolean}
|
|
20
|
+
*/
|
|
21
|
+
export function tm_edge_collapse(mesh, edge, other) {
|
|
22
|
+
|
|
23
|
+
const victim = query_edge_other_vertex(edge, other);
|
|
24
|
+
|
|
25
|
+
assert.notNull(other, 'other');
|
|
26
|
+
assert.notEqual(victim, other, 'degenerate edge');
|
|
27
|
+
|
|
28
|
+
const edge_faces = edge.faces;
|
|
29
|
+
const attached_face_count = edge_faces.length;
|
|
30
|
+
|
|
31
|
+
let recorded_splice_edge_count = 0;
|
|
32
|
+
for (let i = 0; i < attached_face_count; i++) {
|
|
33
|
+
const edge_face = edge_faces[i];
|
|
34
|
+
|
|
35
|
+
const rec_address = recorded_splice_edge_count * 2;
|
|
36
|
+
const got_tri_edges = query_face_get_other_edges(scratch_edges_0, rec_address, edge_face, edge);
|
|
37
|
+
|
|
38
|
+
if (!got_tri_edges) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const a_v0 = scratch_edges_0[rec_address];
|
|
43
|
+
const a_v1 = scratch_edges_0[rec_address + 1];
|
|
44
|
+
|
|
45
|
+
for (let j = 0; j < rec_address; j++) {
|
|
46
|
+
const other_edge = scratch_edges_0[j];
|
|
47
|
+
|
|
48
|
+
if (other_edge === a_v0 || other_edge === a_v1) {
|
|
49
|
+
// rare case, triangles share edges, avoid
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
recorded_splice_edge_count++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// debugValidateMesh(mesh);
|
|
58
|
+
|
|
59
|
+
tm_edge_kill(mesh, edge);
|
|
60
|
+
|
|
61
|
+
tm_vert_splice(mesh, other, victim);
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < recorded_splice_edge_count; i++) {
|
|
64
|
+
const i2 = i * 2;
|
|
65
|
+
tm_edge_splice(mesh, scratch_edges_0[i2], scratch_edges_0[i2 + 1]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// debugValidateMesh(mesh);
|
|
69
|
+
|
|
70
|
+
return true;
|
|
71
|
+
|
|
72
|
+
// // if (query_edge_is_manifold(edge)) {
|
|
73
|
+
// if (attached_face_count === 2) {
|
|
74
|
+
// // two triangles are to be removed
|
|
75
|
+
//
|
|
76
|
+
//
|
|
77
|
+
// const a_face = edge_faces[0];
|
|
78
|
+
// const b_face = edge_faces[1];
|
|
79
|
+
//
|
|
80
|
+
//
|
|
81
|
+
// const a_other = scratch_edges_0;
|
|
82
|
+
// const b_other = scratch_edges_1;
|
|
83
|
+
//
|
|
84
|
+
// query_face_get_other_edges(a_other, 0, a_face, edge);
|
|
85
|
+
// query_face_get_other_edges(b_other, 0, b_face, edge);
|
|
86
|
+
//
|
|
87
|
+
// if (
|
|
88
|
+
// a_other[0] === b_other[0] || a_other[0] === b_other[1]
|
|
89
|
+
// || a_other[1] === b_other[0] || a_other[1] === b_other[1]
|
|
90
|
+
// ) {
|
|
91
|
+
// // triangles share edges, avoid
|
|
92
|
+
// return false;
|
|
93
|
+
// }
|
|
94
|
+
//
|
|
95
|
+
// tm_edge_kill(mesh, edge);
|
|
96
|
+
//
|
|
97
|
+
// tm_vert_splice(mesh, other, victim);
|
|
98
|
+
//
|
|
99
|
+
// tm_edge_splice(mesh, a_other[0], a_other[1]);
|
|
100
|
+
// tm_edge_splice(mesh, b_other[0], b_other[1]);
|
|
101
|
+
//
|
|
102
|
+
// return true;
|
|
103
|
+
// }
|
|
104
|
+
//
|
|
105
|
+
// // if (query_edge_is_boundary(edge)) {
|
|
106
|
+
// if (attached_face_count === 1) {
|
|
107
|
+
// // only one triangle is to be removed
|
|
108
|
+
//
|
|
109
|
+
// const a_face = edge_faces[0];
|
|
110
|
+
//
|
|
111
|
+
// const a_other = scratch_edges_0;
|
|
112
|
+
//
|
|
113
|
+
// const got_tri_edges = query_face_get_other_edges(a_other, 0, a_face, edge);
|
|
114
|
+
//
|
|
115
|
+
// tm_edge_kill(mesh, edge);
|
|
116
|
+
// tm_vert_splice(mesh, other, victim);
|
|
117
|
+
// if (got_tri_edges) {
|
|
118
|
+
// tm_edge_splice(mesh, a_other[1], a_other[0]);
|
|
119
|
+
// }
|
|
120
|
+
//
|
|
121
|
+
// return true;
|
|
122
|
+
// }
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
return false;
|
|
126
|
+
}
|