@woosh/meep-engine 2.43.21 → 2.43.23
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/HashMap.d.ts +2 -2
- package/core/collection/HashMap.js +22 -2
- package/core/geom/3d/morton/mortonEncode_magicbits.js +18 -0
- package/core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js +2 -2
- package/core/geom/3d/topology/struct/TopoEdge.js +4 -0
- package/core/geom/3d/topology/struct/TopoMesh.js +25 -0
- package/core/geom/3d/topology/struct/TopoTriangle.js +4 -0
- package/core/geom/3d/topology/struct/TopoVertex.js +8 -0
- package/core/geom/3d/topology/struct/binary/BinaryElementPool.js +288 -0
- package/core/geom/3d/topology/struct/binary/BinaryElementPool.spec.js +36 -0
- package/core/geom/3d/topology/struct/binary/BinaryTopology.js +617 -0
- package/core/geom/3d/topology/struct/binary/BinaryTopology.spec.js +16 -0
- package/core/geom/3d/topology/struct/binary/io/OrderedEdge.js +66 -0
- package/core/geom/3d/topology/struct/binary/io/bt_index_geometry_to_topology.js +188 -0
- package/core/geom/3d/topology/struct/binary/io/bt_index_geometry_to_topology.spec.js +84 -0
- package/core/geom/3d/topology/struct/binary/io/bt_mesh_calc_edges.js +51 -0
- package/core/geom/3d/topology/struct/binary/io/create_edge.js +106 -0
- package/core/geom/3d/topology/struct/binary/io/get_or_create_edge_map.js +26 -0
- package/core/geom/3d/topology/struct/binary/query/bt_mesh_edge_has_vertex.js +16 -0
- package/core/geom/3d/topology/struct/binary/query/bt_mesh_edge_has_vertex.spec.js +15 -0
- package/core/geom/3d/topology/struct/binary/query/bt_mesh_edge_other_vertex.js +21 -0
- package/core/geom/3d/topology/struct/binary/query/bt_mesh_edge_other_vertex.spec.js +16 -0
- package/core/geom/3d/topology/struct/prototypeBinaryTopology.js +55 -0
- package/core/path/PATH_SEPARATOR.js +1 -0
- package/core/path/computeFileExtension.js +24 -0
- package/core/path/computeFileExtension.spec.js +13 -0
- package/core/path/computePathBase.js +21 -0
- package/core/path/computePathBase.spec.js +13 -0
- package/core/path/computePathDirectory.js +25 -0
- package/editor/view/library/MeshLibraryView.js +1 -1
- package/engine/asset/guessAssetType.js +1 -1
- package/engine/asset/loaders/image/ImageRGBADataLoader.js +1 -1
- package/engine/asset/loaders/texture/TextureAssetLoader.js +1 -1
- package/engine/ecs/storage/BinaryBufferSerializer.js +7 -0
- package/engine/graphics/material/composeCompile.js +3 -5
- package/engine/graphics/micron/plugin/GLTFAssetTransformer.js +90 -65
- package/engine/graphics/sh3/README.md +2 -0
- package/engine/graphics/sh3/path_tracer/PathTracer.js +2 -15
- package/engine/graphics/sh3/path_tracer/getBiasedNormalSample.js +1 -1
- package/engine/graphics/sh3/path_tracer/prototypePathTracer.js +14 -9
- package/engine/graphics/texture/cubemap/load_environment_map.js +1 -1
- package/engine/graphics/util/makeMeshPreviewScene.js +38 -26
- package/engine/network/RemoteController.js +98 -0
- package/engine/network/remoteEditor.js +33 -0
- package/engine/save/storage/IndexedDBStorage.js +1 -0
- package/package.json +1 -1
- package/view/elements/MeshPreview.js +5 -1
- package/core/FilePath.js +0 -73
- package/core/FilePath.spec.js +0 -25
- package/core/geom/3d/topology/struct/BinaryTopology.js +0 -112
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { NULL_POINTER } from "../BinaryTopology.js";
|
|
2
|
+
import { create_edge } from "./create_edge.js";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Populates supplied topology with data from supplied indexed geometry
|
|
7
|
+
* @see https://github.com/blender/blender/blob/9cb061f4f0119e647173e7d354e1457e97632333/source/blender/blenkernel/intern/mesh_calc_edges.cc
|
|
8
|
+
* @see https://github.com/blender/blender/blob/9cb061f4f0119e647173e7d354e1457e97632333/source/blender/io/stl/importer/stl_import_mesh.cc#L63
|
|
9
|
+
* @param {BinaryTopology} out
|
|
10
|
+
* @param {number[]} indices
|
|
11
|
+
* @param {number[]} vertex_positions
|
|
12
|
+
* @param {number[]} [vertex_normals]
|
|
13
|
+
*/
|
|
14
|
+
export function bt_index_geometry_to_topology(
|
|
15
|
+
out,
|
|
16
|
+
indices,
|
|
17
|
+
vertex_positions,
|
|
18
|
+
vertex_normals
|
|
19
|
+
) {
|
|
20
|
+
|
|
21
|
+
// remove any existing data
|
|
22
|
+
out.clear();
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
NOTES:
|
|
26
|
+
- we use {@link BinaryElementPool#allocate_continuous} to avoid having to allocate additional mapping structure
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// ingest all vertices
|
|
30
|
+
const vertex_count = vertex_positions.length / 3;
|
|
31
|
+
|
|
32
|
+
const vertex_allocation_offset = out.vertices.allocate_continuous(vertex_count);
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < vertex_count; i++) {
|
|
35
|
+
const i3 = i * 3;
|
|
36
|
+
|
|
37
|
+
const vertex_id = i + vertex_allocation_offset;
|
|
38
|
+
|
|
39
|
+
out.vertex_write_coordinate(vertex_id, vertex_positions, i3);
|
|
40
|
+
|
|
41
|
+
if (vertex_normals !== undefined) {
|
|
42
|
+
out.vertex_write_normal(vertex_id, vertex_normals, i3);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// initialize edge to NULL
|
|
46
|
+
out.vertex_write_edge(vertex_id, NULL_POINTER);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const face_count = indices.length / 3;
|
|
50
|
+
|
|
51
|
+
// create faces
|
|
52
|
+
const face_allocation_offset = out.faces.allocate_continuous(face_count);
|
|
53
|
+
|
|
54
|
+
// reserve some memory to avoid extra allocations
|
|
55
|
+
out.loops.ensure_capacity(face_count * 3);
|
|
56
|
+
out.edges.ensure_capacity(face_count * 1.6);
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < face_count; i++) {
|
|
59
|
+
const i3 = i * 3;
|
|
60
|
+
|
|
61
|
+
const a = indices[i3];
|
|
62
|
+
const b = indices[i3 + 1];
|
|
63
|
+
const c = indices[i3 + 2];
|
|
64
|
+
|
|
65
|
+
const vertex_a = a + vertex_allocation_offset;
|
|
66
|
+
const vertex_b = b + vertex_allocation_offset;
|
|
67
|
+
const vertex_c = c + vertex_allocation_offset;
|
|
68
|
+
|
|
69
|
+
const face_id = i + face_allocation_offset;
|
|
70
|
+
|
|
71
|
+
// create edges
|
|
72
|
+
const edge_ab = get_or_create_edge_2(vertex_a, vertex_b, out);
|
|
73
|
+
const edge_bc = get_or_create_edge_2(vertex_b, vertex_c, out);
|
|
74
|
+
const edge_ca = get_or_create_edge_2(vertex_c, vertex_a, out);
|
|
75
|
+
|
|
76
|
+
// allocate loops
|
|
77
|
+
const loop_a = out.loop_create();
|
|
78
|
+
const loop_b = out.loop_create();
|
|
79
|
+
const loop_c = out.loop_create();
|
|
80
|
+
|
|
81
|
+
// write loops
|
|
82
|
+
out.loop_write_vertex(loop_a, vertex_a);
|
|
83
|
+
out.loop_write_vertex(loop_b, vertex_b);
|
|
84
|
+
out.loop_write_vertex(loop_c, vertex_c);
|
|
85
|
+
|
|
86
|
+
// link loops to edges
|
|
87
|
+
link_loop_to_edge(loop_a, edge_ab, out);
|
|
88
|
+
link_loop_to_edge(loop_b, edge_bc, out);
|
|
89
|
+
link_loop_to_edge(loop_c, edge_ca, out);
|
|
90
|
+
|
|
91
|
+
out.loop_write_face(loop_a, face_id);
|
|
92
|
+
out.loop_write_face(loop_b, face_id);
|
|
93
|
+
out.loop_write_face(loop_c, face_id);
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
NOTE: Vertices (#BMLoop.v & #BMLoop.next.v) always contain vertices from (#BMEdge.v1 & #BMEdge.v2).
|
|
97
|
+
*/
|
|
98
|
+
// link up loops around the face
|
|
99
|
+
out.loop_write_next(loop_a, loop_b);
|
|
100
|
+
out.loop_write_prev(loop_a, loop_c);
|
|
101
|
+
|
|
102
|
+
out.loop_write_next(loop_b, loop_c);
|
|
103
|
+
out.loop_write_prev(loop_b, loop_a);
|
|
104
|
+
|
|
105
|
+
out.loop_write_next(loop_c, loop_a);
|
|
106
|
+
out.loop_write_prev(loop_c, loop_b);
|
|
107
|
+
|
|
108
|
+
// update face
|
|
109
|
+
out.face_write_loop(face_id, loop_a);
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
*
|
|
116
|
+
* @param {number} loop_id
|
|
117
|
+
* @param {number} edge_id
|
|
118
|
+
* @param {BinaryTopology} mesh
|
|
119
|
+
*/
|
|
120
|
+
function link_loop_to_edge(loop_id, edge_id, mesh) {
|
|
121
|
+
mesh.loop_write_edge(loop_id, edge_id);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* First loop attached to the edge
|
|
125
|
+
* @type {number}
|
|
126
|
+
*/
|
|
127
|
+
const existing_loop = mesh.edge_read_loop(edge_id);
|
|
128
|
+
|
|
129
|
+
if (existing_loop === NULL_POINTER) {
|
|
130
|
+
// no loops connected yet
|
|
131
|
+
mesh.edge_write_loop(edge_id, loop_id);
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
} else {
|
|
135
|
+
// insert loop between existing loop and the next one
|
|
136
|
+
const next = mesh.loop_read_radial_next(existing_loop);
|
|
137
|
+
|
|
138
|
+
mesh.loop_write_radial_next(existing_loop, loop_id);
|
|
139
|
+
mesh.loop_write_radial_prev(next, loop_id);
|
|
140
|
+
|
|
141
|
+
mesh.loop_write_radial_next(loop_id, next);
|
|
142
|
+
mesh.loop_write_radial_prev(loop_id, existing_loop);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Ensure an edge between two vertices
|
|
150
|
+
* NOTE: works without having to use a separate hash map, resulting in faster execution and smaller RAM requirement
|
|
151
|
+
* @param {number} v0
|
|
152
|
+
* @param {number} v1
|
|
153
|
+
* @param {BinaryTopology} mesh
|
|
154
|
+
*/
|
|
155
|
+
function get_or_create_edge_2(v0, v1, mesh) {
|
|
156
|
+
const first_edge = mesh.vertex_read_edge(v0);
|
|
157
|
+
|
|
158
|
+
if (first_edge !== NULL_POINTER) {
|
|
159
|
+
|
|
160
|
+
// iterate over all edges around the vertex
|
|
161
|
+
let edge = first_edge;
|
|
162
|
+
|
|
163
|
+
do {
|
|
164
|
+
const edge_v1 = mesh.edge_read_vertex1(edge);
|
|
165
|
+
const edge_v2 = mesh.edge_read_vertex2(edge);
|
|
166
|
+
|
|
167
|
+
if (edge_v1 === v1 || edge_v2 === v1) {
|
|
168
|
+
// found edge that connects between v0 and v1
|
|
169
|
+
return edge;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (edge_v1 === v0) {
|
|
173
|
+
edge = mesh.edge_read_v1_disk_next(edge);
|
|
174
|
+
} else if (edge_v2 === v0) {
|
|
175
|
+
edge = mesh.edge_read_v2_disk_next(edge);
|
|
176
|
+
} else {
|
|
177
|
+
// invalid edge connection
|
|
178
|
+
throw new Error('Invalid edge, not connected to first vertex');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
} while (edge !== first_edge);
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// if match is found - return the match, else create a new edge and assign
|
|
186
|
+
|
|
187
|
+
return create_edge(mesh, v0, v1);
|
|
188
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { bt_index_geometry_to_topology } from "./bt_index_geometry_to_topology.js";
|
|
2
|
+
import { BinaryTopology } from "../BinaryTopology.js";
|
|
3
|
+
import { bt_mesh_edge_has_vertex } from "../query/bt_mesh_edge_has_vertex.js";
|
|
4
|
+
|
|
5
|
+
test('Empty geometry work as expected', () => {
|
|
6
|
+
const mesh = new BinaryTopology();
|
|
7
|
+
bt_index_geometry_to_topology(mesh, [], []);
|
|
8
|
+
|
|
9
|
+
expect(mesh.faces.size).toBe(0);
|
|
10
|
+
expect(mesh.vertices.size).toBe(0);
|
|
11
|
+
expect(mesh.edges.size).toBe(0);
|
|
12
|
+
expect(mesh.loops.size).toBe(0);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('Single triangle', () => {
|
|
16
|
+
const mesh = new BinaryTopology();
|
|
17
|
+
bt_index_geometry_to_topology(mesh, [0, 1, 2], [
|
|
18
|
+
1, 3, 7,
|
|
19
|
+
11, 13, 17,
|
|
20
|
+
23, 27, -1
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
expect(mesh.faces.size).toBe(1);
|
|
24
|
+
expect(mesh.loops.size).toBe(3);
|
|
25
|
+
expect(mesh.edges.size).toBe(3);
|
|
26
|
+
expect(mesh.vertices.size).toBe(3);
|
|
27
|
+
|
|
28
|
+
// validate loops
|
|
29
|
+
for (let loop_id = 0; loop_id < 3; loop_id++) {
|
|
30
|
+
const face = mesh.loop_read_face(loop_id);
|
|
31
|
+
expect(face).toEqual(0);
|
|
32
|
+
|
|
33
|
+
const loop_next = mesh.loop_read_next(loop_id);
|
|
34
|
+
expect(loop_next).not.toEqual(loop_id);
|
|
35
|
+
expect(mesh.loop_read_prev(loop_next)).toEqual(loop_id);
|
|
36
|
+
|
|
37
|
+
const loop_prev = mesh.loop_read_prev(loop_id);
|
|
38
|
+
expect(loop_prev).not.toEqual(loop_id);
|
|
39
|
+
expect(mesh.loop_read_next(loop_prev)).toEqual(loop_id);
|
|
40
|
+
|
|
41
|
+
expect(loop_prev).not.toEqual(loop_next);
|
|
42
|
+
|
|
43
|
+
// should loop around
|
|
44
|
+
expect(mesh.loop_read_next(loop_next)).toEqual(loop_prev);
|
|
45
|
+
expect(mesh.loop_read_prev(loop_prev)).toEqual(loop_next);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
// validate edges
|
|
50
|
+
for (let edge_id = 0; edge_id < 3; edge_id++) {
|
|
51
|
+
const v1 = mesh.edge_read_vertex1(edge_id);
|
|
52
|
+
const v2 = mesh.edge_read_vertex2(edge_id);
|
|
53
|
+
|
|
54
|
+
// different vertices
|
|
55
|
+
expect(v1).not.toEqual(v2);
|
|
56
|
+
|
|
57
|
+
// validate disk links
|
|
58
|
+
const disk_v1_next = mesh.edge_read_v1_disk_next(edge_id);
|
|
59
|
+
|
|
60
|
+
expect(disk_v1_next).not.toEqual(edge_id);
|
|
61
|
+
|
|
62
|
+
expect(bt_mesh_edge_has_vertex(mesh, disk_v1_next, v1)).toBe(true);
|
|
63
|
+
|
|
64
|
+
const disk_v1_prev = mesh.edge_read_v1_disk_prev(edge_id);
|
|
65
|
+
|
|
66
|
+
expect(disk_v1_prev).not.toEqual(edge_id);
|
|
67
|
+
|
|
68
|
+
expect(bt_mesh_edge_has_vertex(mesh, disk_v1_prev, v1)).toBe(true);
|
|
69
|
+
|
|
70
|
+
const disk_v2_next = mesh.edge_read_v2_disk_next(edge_id);
|
|
71
|
+
|
|
72
|
+
expect(disk_v2_next).not.toEqual(edge_id);
|
|
73
|
+
|
|
74
|
+
expect(bt_mesh_edge_has_vertex(mesh, disk_v2_next, v2)).toBe(true);
|
|
75
|
+
|
|
76
|
+
const disk_v2_prev = mesh.edge_read_v2_disk_prev(edge_id);
|
|
77
|
+
|
|
78
|
+
expect(disk_v2_prev).not.toEqual(edge_id);
|
|
79
|
+
|
|
80
|
+
expect(bt_mesh_edge_has_vertex(mesh, disk_v2_prev, v2)).toBe(true);
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { OrderedEdge } from "./OrderedEdge.js";
|
|
2
|
+
import { HashMap } from "../../../../../../collection/HashMap.d.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @see https://github.com/blender/blender/blob/9cb061f4f0119e647173e7d354e1457e97632333/source/blender/blenkernel/intern/mesh_calc_edges.cc#L209
|
|
6
|
+
* @param {BinaryTopology} mesh
|
|
7
|
+
*/
|
|
8
|
+
function bt_mesh_calc_edges(mesh) {
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @type {HashMap<OrderedEdge, unknown>}
|
|
12
|
+
*/
|
|
13
|
+
const map = new HashMap();
|
|
14
|
+
|
|
15
|
+
add_polygon_edges_to_map(mesh, map);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @see https://github.com/blender/blender/blob/9cb061f4f0119e647173e7d354e1457e97632333/source/blender/blenkernel/intern/mesh_calc_edges.cc#L96
|
|
20
|
+
* @param {BinaryTopology} mesh
|
|
21
|
+
* @param {HashMap<OrderedEdge,*>} edge_map
|
|
22
|
+
*/
|
|
23
|
+
function add_polygon_edges_to_map(mesh, edge_map) {
|
|
24
|
+
|
|
25
|
+
const faces = mesh.faces;
|
|
26
|
+
const face_count = faces.size;
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < face_count; i++) {
|
|
29
|
+
if (!faces.is_allocated(i)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const first_loop = mesh.face_read_loop(i);
|
|
34
|
+
let prev_loop = first_loop + j;
|
|
35
|
+
|
|
36
|
+
for (let j = 0; j < 3; j++) {
|
|
37
|
+
const loop = first_loop + j;
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
const prev_v = mesh.loop_read_vertex(prev_loop);
|
|
41
|
+
const next_v = mesh.loop_read_vertex(loop);
|
|
42
|
+
|
|
43
|
+
const edge = new OrderedEdge(prev_v, next_v);
|
|
44
|
+
|
|
45
|
+
edge_map.getOrSet(edge, null);
|
|
46
|
+
|
|
47
|
+
prev_loop = loop;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { NULL_POINTER } from "../BinaryTopology.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {BinaryTopology} mesh
|
|
6
|
+
* @param {number} edge_from
|
|
7
|
+
* @param {number} edge_to
|
|
8
|
+
* @param {number} vertex
|
|
9
|
+
*/
|
|
10
|
+
function edge_link_disk_forward(mesh, edge_from, edge_to, vertex) {
|
|
11
|
+
const from_v1 = mesh.edge_read_vertex1(edge_from);
|
|
12
|
+
|
|
13
|
+
let displaced_edge;
|
|
14
|
+
|
|
15
|
+
if (from_v1 === vertex) {
|
|
16
|
+
displaced_edge = mesh.edge_read_v1_disk_next(edge_from);
|
|
17
|
+
|
|
18
|
+
mesh.edge_write_v1_disk_next(edge_from, edge_to);
|
|
19
|
+
|
|
20
|
+
} else {
|
|
21
|
+
displaced_edge = mesh.edge_read_v2_disk_next(edge_from);
|
|
22
|
+
|
|
23
|
+
mesh.edge_write_v2_disk_next(edge_from, edge_to);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return displaced_edge;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function edge_link_disk_back(mesh, edge_from, edge_to, vertex) {
|
|
30
|
+
const from_v1 = mesh.edge_read_vertex1(edge_from);
|
|
31
|
+
|
|
32
|
+
let displaced_edge;
|
|
33
|
+
|
|
34
|
+
if (from_v1 === vertex) {
|
|
35
|
+
displaced_edge = mesh.edge_read_v1_disk_prev(edge_from);
|
|
36
|
+
|
|
37
|
+
mesh.edge_write_v1_disk_prev(edge_from, edge_to);
|
|
38
|
+
|
|
39
|
+
} else {
|
|
40
|
+
displaced_edge = mesh.edge_read_v2_disk_prev(edge_from);
|
|
41
|
+
|
|
42
|
+
mesh.edge_write_v2_disk_prev(edge_from, edge_to);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return displaced_edge;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
* @param {BinaryTopology} mesh
|
|
51
|
+
* @param {number} edge_source anchor edge, new edge is inserted after this one
|
|
52
|
+
* @param {number} edge_new edge that is being introduced
|
|
53
|
+
* @param {number} vertex
|
|
54
|
+
*/
|
|
55
|
+
function edge_link_disk(mesh, edge_source, edge_new, vertex) {
|
|
56
|
+
const forward_link = edge_link_disk_forward(mesh, edge_source, edge_new, vertex);
|
|
57
|
+
edge_link_disk_forward(mesh, edge_new, forward_link, vertex);
|
|
58
|
+
|
|
59
|
+
edge_link_disk_back(mesh, forward_link, edge_new, vertex);
|
|
60
|
+
edge_link_disk_back(mesh, edge_new, edge_source, vertex);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
*
|
|
65
|
+
* @param {BinaryTopology} mesh
|
|
66
|
+
* @param {number} v0
|
|
67
|
+
* @param {number} v1
|
|
68
|
+
* @return {number}
|
|
69
|
+
*/
|
|
70
|
+
export function create_edge(mesh, v0, v1) {
|
|
71
|
+
const edge_id = mesh.edges.allocate();
|
|
72
|
+
|
|
73
|
+
mesh.edge_write_vertex1(edge_id, v0);
|
|
74
|
+
mesh.edge_write_vertex2(edge_id, v1);
|
|
75
|
+
|
|
76
|
+
// initialize rest of the edge
|
|
77
|
+
mesh.edge_write_loop(edge_id, NULL_POINTER);
|
|
78
|
+
|
|
79
|
+
// process disk links
|
|
80
|
+
const first_v0_edge = mesh.vertex_read_edge(v0);
|
|
81
|
+
|
|
82
|
+
if (first_v0_edge !== NULL_POINTER) {
|
|
83
|
+
edge_link_disk(mesh, first_v0_edge, edge_id, v0);
|
|
84
|
+
} else {
|
|
85
|
+
// no edges attached yet, attach this one as the first
|
|
86
|
+
mesh.vertex_write_edge(v0, edge_id);
|
|
87
|
+
// link to self
|
|
88
|
+
mesh.edge_write_v1_disk_next(edge_id, edge_id);
|
|
89
|
+
mesh.edge_write_v1_disk_prev(edge_id, edge_id);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const first_v1_edge = mesh.vertex_read_edge(v1);
|
|
93
|
+
|
|
94
|
+
if (first_v1_edge !== NULL_POINTER) {
|
|
95
|
+
|
|
96
|
+
edge_link_disk(mesh, first_v1_edge, edge_id, v1);
|
|
97
|
+
|
|
98
|
+
} else {
|
|
99
|
+
// no edges attached yet, attach this one as the first
|
|
100
|
+
mesh.vertex_write_edge(v1, edge_id);
|
|
101
|
+
// link to self
|
|
102
|
+
mesh.edge_write_v2_disk_next(edge_id, edge_id);
|
|
103
|
+
mesh.edge_write_v2_disk_prev(edge_id, edge_id);
|
|
104
|
+
}
|
|
105
|
+
return edge_id;
|
|
106
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OrderedEdge } from "./OrderedEdge.js";
|
|
2
|
+
import { create_edge } from "./create_edge.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param {number} v0
|
|
7
|
+
* @param {number} v1
|
|
8
|
+
* @param {HashMap<OrderedEdge,number>} map
|
|
9
|
+
* @param {BinaryTopology} mesh
|
|
10
|
+
*/
|
|
11
|
+
function get_or_create_edge_map(v0, v1, map, mesh) {
|
|
12
|
+
const scratch_ordered_edge = new OrderedEdge(v0, v1);
|
|
13
|
+
|
|
14
|
+
const existing = map.get(scratch_ordered_edge);
|
|
15
|
+
|
|
16
|
+
if (existing !== undefined) {
|
|
17
|
+
return existing;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// doesn't exist - create
|
|
21
|
+
const edge_id = create_edge(mesh, v0, v1);
|
|
22
|
+
|
|
23
|
+
map.set(scratch_ordered_edge, edge_id);
|
|
24
|
+
|
|
25
|
+
return edge_id;
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {BinaryTopology} mesh
|
|
4
|
+
* @param {number} edge_id
|
|
5
|
+
* @param {number} vertex_id
|
|
6
|
+
* @returns {boolean}
|
|
7
|
+
*/
|
|
8
|
+
export function bt_mesh_edge_has_vertex(mesh, edge_id, vertex_id) {
|
|
9
|
+
const v1 = mesh.edge_read_vertex1(edge_id);
|
|
10
|
+
|
|
11
|
+
if (v1 === vertex_id) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return mesh.edge_read_vertex2(edge_id) === vertex_id;
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BinaryTopology } from "../BinaryTopology.js";
|
|
2
|
+
import { bt_mesh_edge_has_vertex } from "./bt_mesh_edge_has_vertex.js";
|
|
3
|
+
|
|
4
|
+
test('main', () => {
|
|
5
|
+
const mesh = new BinaryTopology();
|
|
6
|
+
|
|
7
|
+
const edge = mesh.edges.allocate();
|
|
8
|
+
|
|
9
|
+
mesh.edge_write_vertex1(edge, 7);
|
|
10
|
+
mesh.edge_write_vertex2(edge, 3);
|
|
11
|
+
|
|
12
|
+
expect(bt_mesh_edge_has_vertex(mesh, edge, 7)).toBe(true);
|
|
13
|
+
expect(bt_mesh_edge_has_vertex(mesh, edge, 3)).toBe(true);
|
|
14
|
+
expect(bt_mesh_edge_has_vertex(mesh, edge, 1)).toBe(false);
|
|
15
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns other vertex ID
|
|
3
|
+
* Assumes that given vertex belongs to this edge
|
|
4
|
+
* Assumes that given edge exists
|
|
5
|
+
* @param {BinaryTopology} mesh
|
|
6
|
+
* @param {number} edge_id edge ID
|
|
7
|
+
* @param {number} vertex_id
|
|
8
|
+
* @returns {number}
|
|
9
|
+
*/
|
|
10
|
+
export function bt_mesh_edge_other_vertex(mesh, edge_id, vertex_id) {
|
|
11
|
+
|
|
12
|
+
const v1 = mesh.edge_read_vertex1(edge_id);
|
|
13
|
+
const v2 = mesh.edge_read_vertex2(edge_id);
|
|
14
|
+
|
|
15
|
+
if (v1 === vertex_id) {
|
|
16
|
+
return v2;
|
|
17
|
+
} else {
|
|
18
|
+
return v1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BinaryTopology } from "../BinaryTopology.js";
|
|
2
|
+
import { bt_mesh_edge_other_vertex } from "./bt_mesh_edge_other_vertex.js";
|
|
3
|
+
|
|
4
|
+
test('main', () => {
|
|
5
|
+
|
|
6
|
+
const mesh = new BinaryTopology();
|
|
7
|
+
|
|
8
|
+
const edge = mesh.edges.allocate();
|
|
9
|
+
|
|
10
|
+
mesh.edge_write_vertex1(edge, 7);
|
|
11
|
+
mesh.edge_write_vertex2(edge, 3);
|
|
12
|
+
|
|
13
|
+
expect(bt_mesh_edge_other_vertex(mesh, edge, 7)).toBe(3);
|
|
14
|
+
expect(bt_mesh_edge_other_vertex(mesh, edge, 3)).toBe(7);
|
|
15
|
+
|
|
16
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { PLYLoader } from "three/examples/jsm/loaders/PLYLoader.js";
|
|
2
|
+
import { noop } from "../../../../function/Functions.js";
|
|
3
|
+
import { bt_index_geometry_to_topology } from "./binary/io/bt_index_geometry_to_topology.js";
|
|
4
|
+
import { BinaryTopology } from "./binary/BinaryTopology.js";
|
|
5
|
+
import {
|
|
6
|
+
compute_buffer_geometry_byte_size
|
|
7
|
+
} from "../../../../../engine/graphics/geometry/buffered/compute_buffer_geometry_byte_size.js";
|
|
8
|
+
import { prettyPrint } from "../../../../NumberFormat.js";
|
|
9
|
+
import { TopoMesh } from "./TopoMesh.js";
|
|
10
|
+
|
|
11
|
+
function promise_ply(url) {
|
|
12
|
+
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
new PLYLoader().load(url, resolve, noop, reject);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
|
|
21
|
+
const url = "data/models/stanford/Lucy100k.ply";
|
|
22
|
+
// const url = "data/models/stanford/dragon_recon/dragon_vrip.ply";
|
|
23
|
+
const lucy_geom = await promise_ply(url);
|
|
24
|
+
|
|
25
|
+
const mesh = new BinaryTopology();
|
|
26
|
+
const topoMesh = new TopoMesh();
|
|
27
|
+
|
|
28
|
+
const positions_array = lucy_geom.getAttribute('position').array;
|
|
29
|
+
const index_array = lucy_geom.getIndex().array;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
console.time('topo build - bin');
|
|
33
|
+
// console.profile('topo build - bin');
|
|
34
|
+
bt_index_geometry_to_topology(mesh, index_array, positions_array);
|
|
35
|
+
// console.profileEnd('topo build - bin');
|
|
36
|
+
console.timeEnd('topo build - bin');
|
|
37
|
+
|
|
38
|
+
//
|
|
39
|
+
// console.time('topo build - obj');
|
|
40
|
+
// topoMesh.build(positions_array, index_array);
|
|
41
|
+
// console.timeEnd('topo build - obj');
|
|
42
|
+
|
|
43
|
+
mesh.trim();
|
|
44
|
+
|
|
45
|
+
console.log(mesh, lucy_geom);
|
|
46
|
+
|
|
47
|
+
console.log(`source mesh size: ${prettyPrint(compute_buffer_geometry_byte_size(lucy_geom))} bytes`);
|
|
48
|
+
console.log(`binary topology size: ${prettyPrint(mesh.byteSize)} bytes`);
|
|
49
|
+
console.log(`object topology size: ${prettyPrint(topoMesh.byteSize)} bytes`);
|
|
50
|
+
|
|
51
|
+
window.topo_obj = topoMesh;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main();
|
|
55
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PATH_SEPARATOR = '/';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { computePathBase } from "./computePathBase.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {string} path
|
|
6
|
+
* @returns {String|null}
|
|
7
|
+
*/
|
|
8
|
+
export function computeFileExtension(path) {
|
|
9
|
+
if (typeof path !== "string") {
|
|
10
|
+
throw new Error('path is not a string');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//get base
|
|
14
|
+
const pathBase = computePathBase(path);
|
|
15
|
+
|
|
16
|
+
const lastDotIndex = pathBase.lastIndexOf('.');
|
|
17
|
+
|
|
18
|
+
if (lastDotIndex !== -1) {
|
|
19
|
+
return pathBase.substring(lastDotIndex + 1);
|
|
20
|
+
} else {
|
|
21
|
+
//no extension
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { computeFileExtension } from "./computeFileExtension.js";
|
|
2
|
+
|
|
3
|
+
test('extract extension of cat.txt', () => {
|
|
4
|
+
expect(computeFileExtension('cat.txt')).toEqual('txt');
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
test('extract extension of ../hmm/cat.txt', () => {
|
|
8
|
+
expect(computeFileExtension('../hmm/cat.txt')).toEqual('txt');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('extract extension of empty string', () => {
|
|
12
|
+
expect(computeFileExtension('')).toBeNull();
|
|
13
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PATH_SEPARATOR } from "./PATH_SEPARATOR.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Strips all directories from the path.
|
|
5
|
+
* @example 'a/b/c' -> 'c'
|
|
6
|
+
* @param {string} path
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function computePathBase(path) {
|
|
10
|
+
if (typeof path !== "string") {
|
|
11
|
+
throw new Error('path is not a string');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const lastSlashIndex = path.lastIndexOf(PATH_SEPARATOR);
|
|
15
|
+
|
|
16
|
+
if (lastSlashIndex !== -1) {
|
|
17
|
+
return path.substring(lastSlashIndex + 1);
|
|
18
|
+
} else {
|
|
19
|
+
return path;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { computePathBase } from "./computePathBase.js";
|
|
2
|
+
|
|
3
|
+
test('extract base path of empty string', () => {
|
|
4
|
+
expect(computePathBase('')).toBe('');
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
test('extract base path of hello', () => {
|
|
8
|
+
expect(computePathBase('hello')).toBe('hello');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('extract base path of hello/there/world', () => {
|
|
12
|
+
expect(computePathBase('hello/there/world')).toBe('world');
|
|
13
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { PATH_SEPARATOR } from "./PATH_SEPARATOR.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the directory name of a path, similar to the Unix dirname command.
|
|
5
|
+
* @param {string} path
|
|
6
|
+
* @return {string}
|
|
7
|
+
*/
|
|
8
|
+
export function computePathDirectory(path) {
|
|
9
|
+
if (typeof path !== "string") {
|
|
10
|
+
throw new Error('path is not a string');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let lastSlashIndex = path.lastIndexOf(PATH_SEPARATOR);
|
|
14
|
+
|
|
15
|
+
//rewind in case of trailing slashes
|
|
16
|
+
while (lastSlashIndex > 0 && path.charAt(lastSlashIndex - 1) === PATH_SEPARATOR) {
|
|
17
|
+
lastSlashIndex--;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (lastSlashIndex !== -1) {
|
|
21
|
+
return path.substring(0, lastSlashIndex);
|
|
22
|
+
} else {
|
|
23
|
+
return path;
|
|
24
|
+
}
|
|
25
|
+
}
|