Browse Source

flattened structures;

fixed building bug of mimalloc, and turn it to static library
V2
Zhicheng Wang 1 week ago
parent
commit
7c7e2fb310
  1. 23
      network_process/include/connect_by_topo/patch_connectivity.hpp
  2. 6
      network_process/include/connect_by_topo/topology_ray_shooting.hpp
  3. 18
      network_process/include/post_topo/filter_polygon_faces.hpp
  4. 18
      network_process/include/post_topo/patch_propagation.hpp
  5. 35
      network_process/interface/fwd_types.hpp
  6. 132
      network_process/src/connect_by_topo/patch_connectivity.cpp
  7. 13
      network_process/src/connect_by_topo/topo_ray_shooting/find_extermal_edges.hpp
  8. 6
      network_process/src/connect_by_topo/topology_ray_shooting.cpp
  9. 41
      network_process/src/post_topo/filter_polygon_faces.cpp
  10. 32
      network_process/src/post_topo/patch_propagation.cpp
  11. 46
      network_process/src/process.cpp
  12. 4
      shared_module/xmake.lua

23
network_process/include/connect_by_topo/patch_connectivity.hpp

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstdint>
#include <fwd_types.hpp> #include <fwd_types.hpp>
/** /**
@ -18,35 +19,39 @@ void compute_patch_edges(const stl_vector_mp<polygon_face_t> &patch_faces,
* @param[in] patch_edges the list of edges on the patch. * @param[in] patch_edges the list of edges on the patch.
* @param[in] patch_faces the list of faces on the patch. Each face contains an implicit function label. * @param[in] patch_faces the list of faces on the patch. Each face contains an implicit function label.
* @param[out] patches the list of patches which contain a list of faces' indices. * @param[out] patches the list of patches which contain a list of faces' indices.
* @param[out] patch_of_face_mapping the mapping from face index to patch index.
*/ */
void compute_patches(const stl_vector_mp<stl_vector_mp<uint32_t>> &edges_of_face, void compute_patches(const stl_vector_mp<stl_vector_mp<uint32_t>> &edges_of_face,
const stl_vector_mp<iso_edge_t> &patch_edges, const stl_vector_mp<iso_edge_t> &patch_edges,
const stl_vector_mp<polygon_face_t> &patch_faces, const stl_vector_mp<polygon_face_t> &patch_faces,
stl_vector_mp<stl_vector_mp<uint32_t>> &patches, flat_index_group_t &patches,
stl_vector_mp<uint32_t> &patch_of_face_mapping); stl_vector_mp<uint32_t> &patch_of_face_mapping);
/** /**
* @brief Group non-manifold iso-edges into chains * @brief Group non-manifold iso-edges into chains
* @param[in] iso_vert_count the count of iso-vertices * @param[in] iso_vert_count the count of iso-vertices.
* @param[in] patch_edges the list of edges on the patch. * @param[in] patch_edges the list of edges on the patch.
* @param[out] chains Chains of non-manifold edges; encoded by a vector of edge indices. * @param[out] chains non-manifold edges.
* @param[out] chain_representative_headers the headers of edges in chains.
*/ */
void compute_chains(size_t iso_vert_count, void compute_chains(size_t iso_vert_count,
stl_vector_mp<iso_edge_t> &patch_edges, stl_vector_mp<iso_edge_t> &patch_edges,
stl_vector_mp<stl_list_mp<iso_edge_t>> &chains); flat_index_group_t &chains,
stl_vector_mp<iso_edge_t> &chain_representative_headers);
/** /**
* @brief compute shells and connected components of isosurfaces, and build maps: half-patch --> shell, patch --> component * @brief compute shells and connected components of isosurfaces, and build maps: half-patch --> shell, patch --> component
* @param[in] half_patch_adj_list The adjacency list of half-patches * @param[in] half_patch_adj_list The adjacency list of half-patches
* @param[out] shells List of shells, each shell is a list of half-patches
* @param[out] shell_of_patch Map: half-patch --> shell * @param[out] shell_of_patch Map: half-patch --> shell
* @param[out] component Connected componeent represented as a list of patches * @param[out] component Connected componeent represented as a list of patches
* @param[out] component_of_patch Map: patch --> component * @param[out] component_of_patch Map: patch --> component
* @note each shell is a list of half-patches, each component is a list of patches * @note each shell is a list of half-patches, each component is a list of patches
*/ */
void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>> &half_patch_adj_list, void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>> &half_patch_adj_list,
stl_vector_mp<stl_vector_mp<uint32_t>> &shells, flat_index_group_t &shells,
stl_vector_mp<uint32_t> &shell_of_half_patch, stl_vector_mp<uint32_t> &shell_of_half_patch,
stl_vector_mp<stl_vector_mp<uint32_t>> &components, flat_index_group_t &components,
stl_vector_mp<uint32_t> &component_of_patch); stl_vector_mp<uint32_t> &component_of_patch);
/** /**
@ -57,4 +62,4 @@ void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>>
*/ */
void compute_arrangement_cells(uint32_t num_shell, void compute_arrangement_cells(uint32_t num_shell,
const stl_vector_mp<std::pair<uint32_t, uint32_t>> &shell_links, const stl_vector_mp<std::pair<uint32_t, uint32_t>> &shell_links,
stl_vector_mp<stl_vector_mp<uint32_t>> &arrangement_cells); flat_index_group_t &arrangement_cells);

6
network_process/include/connect_by_topo/topology_ray_shooting.hpp

@ -15,10 +15,10 @@ void topo_ray_shooting(const scene_bg_mesh_info_t
const stl_vector_mp<arrangement_t> &tetrahedron_arrangements, const stl_vector_mp<arrangement_t> &tetrahedron_arrangements,
const stl_vector_mp<iso_vertex_t> &iso_verts, const stl_vector_mp<iso_vertex_t> &iso_verts,
const stl_vector_mp<polygon_face_t> &iso_faces, const stl_vector_mp<polygon_face_t> &iso_faces,
const stl_vector_mp<stl_vector_mp<uint32_t>> &patches, const flat_index_group_t &patches,
const stl_vector_mp<uint32_t> &patch_of_face, const stl_vector_mp<uint32_t> &patch_of_face,
const stl_vector_mp<stl_vector_mp<uint32_t>> &shells, const flat_index_group_t &shells,
const stl_vector_mp<uint32_t> &shell_of_half_patch, const stl_vector_mp<uint32_t> &shell_of_half_patch,
const stl_vector_mp<stl_vector_mp<uint32_t>> &components, const flat_index_group_t &components,
const stl_vector_mp<uint32_t> &component_of_patch, const stl_vector_mp<uint32_t> &component_of_patch,
stl_vector_mp<std::pair<uint32_t, uint32_t>> &shell_links); stl_vector_mp<std::pair<uint32_t, uint32_t>> &shell_links);

18
network_process/include/post_topo/filter_polygon_faces.hpp

@ -3,14 +3,14 @@
#include <fwd_types.hpp> #include <fwd_types.hpp>
// return: active face label // return: active face label
dynamic_bitset_mp<> filter_polygon_faces(const stl_vector_mp<polygon_face_t>& faces, dynamic_bitset_mp<> filter_polygon_faces(const stl_vector_mp<polygon_face_t>& faces,
const stl_vector_mp<stl_vector_mp<uint32_t>>& patches, const flat_index_group_t& patches,
const stl_vector_mp<stl_vector_mp<uint32_t>>& arrangement_cells, const flat_index_group_t& arrangement_cells,
const stl_vector_mp<uint32_t>& shell_of_half_patch, const stl_vector_mp<uint32_t>& shell_of_half_patch,
const stl_vector_mp<stl_vector_mp<uint32_t>>& shells, const flat_index_group_t& shells,
const stl_vector_mp<uint32_t>& shell_to_cell, const stl_vector_mp<uint32_t>& shell_to_cell,
const dynamic_bitset_mp<>& active_cell_label, const dynamic_bitset_mp<>& active_cell_label,
stl_vector_mp<uint32_t>& output_polygon_faces, stl_vector_mp<uint32_t>& output_polygon_faces,
stl_vector_mp<uint32_t>& output_vertex_counts_of_face); stl_vector_mp<uint32_t>& output_vertex_counts_of_face);
void filter_active_vertices(stl_vector_mp<Eigen::Vector3d>& vertices, stl_vector_mp<uint32_t>& output_polygon_faces); void filter_active_vertices(stl_vector_mp<Eigen::Vector3d>& vertices, stl_vector_mp<uint32_t>& output_polygon_faces);

18
network_process/include/post_topo/patch_propagation.hpp

@ -4,19 +4,19 @@
#include <blobtree.hpp> #include <blobtree.hpp>
void propagate_subface_labels(const baked_blobtree_t& tree, void propagate_subface_labels(const baked_blobtree_t& tree,
const stl_vector_mp<polygon_face_t>& faces, const stl_vector_mp<polygon_face_t>& faces,
const stl_vector_mp<stl_vector_mp<uint32_t>>& patches, const flat_index_group_t& patches,
const stl_vector_mp<stl_vector_mp<uint32_t>>& arrangement_cells, const flat_index_group_t& arrangement_cells,
const stl_vector_mp<uint32_t>& shell_of_half_patch, const stl_vector_mp<uint32_t>& shell_of_half_patch,
const stl_vector_mp<stl_vector_mp<uint32_t>>& shells, const flat_index_group_t& shells,
const stl_vector_mp<uint32_t>& shell_to_cell, const stl_vector_mp<uint32_t>& shell_to_cell,
// in turn: [subface_index][cell_index] = sign // in turn: [subface_index][cell_index] = sign
stl_vector_mp<dynamic_bitset_mp<>>& cell_subface_signs); stl_vector_mp<dynamic_bitset_mp<>>& cell_subface_signs);
void transform_subface_to_primitive_labels(const baked_blobtree_t& tree, void transform_subface_to_primitive_labels(const baked_blobtree_t& tree,
const stl_vector_mp<dynamic_bitset_mp<>>& cell_subface_signs, const stl_vector_mp<dynamic_bitset_mp<>>& cell_subface_signs,
stl_vector_mp<dynamic_bitset_mp<>>& cell_primitive_signs); stl_vector_mp<dynamic_bitset_mp<>>& cell_primitive_signs);
dynamic_bitset_mp<> filter_cells_by_boolean(const baked_blobtree_t& tree, dynamic_bitset_mp<> filter_cells_by_boolean(const baked_blobtree_t& tree,
stl_vector_mp<dynamic_bitset_mp<>>& cell_primitive_signs); stl_vector_mp<dynamic_bitset_mp<>>& cell_primitive_signs);

35
network_process/interface/fwd_types.hpp

@ -260,3 +260,38 @@ struct equal_to<pod_key_t<N>> {
} }
}; };
} // namespace std } // namespace std
// ==============================================================================
struct flat_index_group_t {
stl_vector_mp<uint32_t> index_group{}; /// a list of indices in the group
stl_vector_mp<uint32_t> start_indices{}; /// a list of start indices for each group in the flat index group
inline size_t size() const { return start_indices.size() - 1; }
template <typename F>
inline void foreach (F&& f) const
{
for (uint32_t group_idx = 0; group_idx < size(); ++group_idx) {
for (uint32_t elem_idx = 0; elem_idx < start_indices[group_idx + 1] - start_indices[group_idx]; ++elem_idx) {
f(group_idx, elem_idx, index_group[start_indices[group_idx] + elem_idx]);
}
}
}
template <typename F>
inline void group_foreach(F&& f) const
{
for (uint32_t group_idx = 0; group_idx < size(); ++group_idx) {
f(group_idx, start_indices[group_idx], start_indices[group_idx + 1]);
}
}
template <typename F>
inline void loop_on_group(uint32_t group_idx, F&& f) const
{
for (uint32_t elem_idx = 0; elem_idx < start_indices[group_idx + 1] - start_indices[group_idx]; ++elem_idx) {
f(elem_idx, index_group[start_indices[group_idx] + elem_idx]);
}
}
};

132
network_process/src/connect_by_topo/patch_connectivity.cpp

@ -44,18 +44,18 @@ void compute_patch_edges(const stl_vector_mp<polygon_face_t>& patch_faces,
void compute_patches(const stl_vector_mp<stl_vector_mp<uint32_t>>& edges_of_face, void compute_patches(const stl_vector_mp<stl_vector_mp<uint32_t>>& edges_of_face,
const stl_vector_mp<iso_edge_t>& patch_edges, const stl_vector_mp<iso_edge_t>& patch_edges,
const stl_vector_mp<polygon_face_t>& patch_faces, const stl_vector_mp<polygon_face_t>& patch_faces,
stl_vector_mp<stl_vector_mp<uint32_t>>& patches, flat_index_group_t& patches,
stl_vector_mp<uint32_t>& patch_of_face_mapping) stl_vector_mp<uint32_t>& patch_of_face_mapping)
{ {
stl_vector_mp<bool> visited_face(edges_of_face.size(), false); stl_vector_mp<bool> visited_face(edges_of_face.size(), false);
for (uint32_t i = 0; i < edges_of_face.size(); i++) { for (uint32_t i = 0; i < edges_of_face.size(); i++) {
if (!visited_face[i]) { if (!visited_face[i]) {
// new patch // new patch
auto& patch = patches.emplace_back(); const auto patch_id = static_cast<uint32_t>(patches.size());
const auto patch_id = static_cast<uint32_t>(patches.size() - 1); patches.start_indices.emplace_back(patch_id);
std::queue<uint32_t> Q{}; std::queue<uint32_t> Q{};
Q.emplace(i); Q.emplace(i);
patch.emplace_back(i); patches.index_group.emplace_back(i);
visited_face[i] = true; visited_face[i] = true;
patch_of_face_mapping[i] = patch_id; patch_of_face_mapping[i] = patch_id;
while (!Q.empty()) { while (!Q.empty()) {
@ -68,7 +68,7 @@ void compute_patches(const stl_vector_mp<stl_vector_mp<uint32_t>>& edges_of_face
: patch_edges[eId].headers[0].face_index; : patch_edges[eId].headers[0].face_index;
if (!visited_face[other_fId]) { if (!visited_face[other_fId]) {
Q.emplace(other_fId); Q.emplace(other_fId);
patch.emplace_back(other_fId); patches.index_group.emplace_back(other_fId);
patch_of_face_mapping[other_fId] = patch_id; patch_of_face_mapping[other_fId] = patch_id;
visited_face[other_fId] = true; visited_face[other_fId] = true;
} }
@ -77,11 +77,13 @@ void compute_patches(const stl_vector_mp<stl_vector_mp<uint32_t>>& edges_of_face
} }
} }
} }
patches.start_indices.emplace_back(static_cast<uint32_t>(patches.index_group.size()));
} }
void compute_chains(size_t iso_vert_count, void compute_chains(size_t iso_vert_count,
stl_vector_mp<iso_edge_t>& patch_edges, stl_vector_mp<iso_edge_t>& patch_edges,
stl_vector_mp<stl_list_mp<iso_edge_t>>& chains) flat_index_group_t& chains,
stl_vector_mp<iso_edge_t>& chain_representative_headers)
{ {
stl_vector_mp<stl_vector_mp<uint32_t>> non_manifold_edges_of_vert{}; stl_vector_mp<stl_vector_mp<uint32_t>> non_manifold_edges_of_vert{};
stl_vector_mp<uint32_t> non_manifold_edges{}; stl_vector_mp<uint32_t> non_manifold_edges{};
@ -98,16 +100,32 @@ void compute_chains(size_t iso_vert_count,
} }
} }
// NOTE: the rule of building chains here in brief:
// HINT: the chains can be seen as intersection edges of patches
// CONCEPT: the non-manifold edges where vertices do not have branches are called 'trivial';
// the non-manifold edges where vertices have branches (i.e. intersection of chains) are called 'singular', and the
// vertices are also called 'singular'.
// 1. each chain can start from arbitary vertex of a non-manifold edge, and end at visited edge (i.e. a ring is built) or
// singular vertex;
// 2. along each chain, the neighboring patch does not change, otherwise a new chain is raised.
stl_vector_mp<bool> visited_edge(patch_edges.size(), false); stl_vector_mp<bool> visited_edge(patch_edges.size(), false);
std::list<uint32_t> chain_edges{};
chains.index_group.reserve(non_manifold_edges.size() * 2);
chains.start_indices.reserve(8);
chain_representative_headers.reserve(8);
for (const auto& i : non_manifold_edges) { for (const auto& i : non_manifold_edges) {
if (!visited_edge[i]) { if (!visited_edge[i]) {
// unvisited non-manifold iso-edge (not a boundary edge) // unvisited non-manifold iso-edge (not a boundary edge)
// new chain // new chain
auto& chain = chains.emplace_back(); chains.start_indices.emplace_back(static_cast<uint32_t>(chains.index_group.size()));
auto& edge = patch_edges[i];
std::queue<uint32_t> Q{}; std::queue<uint32_t> Q{};
Q.emplace(i); Q.emplace(i);
chain.emplace_back(std::move(patch_edges[i])); chain_edges.emplace_back(edge.v1);
chain_edges.emplace_back(edge.v2);
chain_representative_headers.emplace_back(edge);
visited_edge[i] = true; visited_edge[i] = true;
chain_edges.clear();
while (!Q.empty()) { while (!Q.empty()) {
const auto eId = Q.front(); const auto eId = Q.front();
Q.pop(); Q.pop();
@ -116,9 +134,21 @@ void compute_chains(size_t iso_vert_count,
if (non_manifold_edges_of_vert[v].size() == 2) { if (non_manifold_edges_of_vert[v].size() == 2) {
const auto other_eId = (non_manifold_edges_of_vert[v][0] == eId) ? non_manifold_edges_of_vert[v][1] const auto other_eId = (non_manifold_edges_of_vert[v][0] == eId) ? non_manifold_edges_of_vert[v][1]
: non_manifold_edges_of_vert[v][0]; : non_manifold_edges_of_vert[v][0];
edge = patch_edges[other_eId];
if (!visited_edge[other_eId]) { if (!visited_edge[other_eId]) {
Q.emplace(other_eId); Q.emplace(other_eId);
chain.emplace_back(std::move(patch_edges[other_eId])); if (v == chain_edges.front()) {
if (v == edge.v1)
chain_edges.emplace_front(edge.v2);
else
chain_edges.emplace_front(edge.v1);
} else {
if (v == edge.v1)
chain_edges.emplace_front(edge.v2);
else
chain_edges.emplace_front(edge.v1);
}
visited_edge[other_eId] = true; visited_edge[other_eId] = true;
} }
} }
@ -127,21 +157,40 @@ void compute_chains(size_t iso_vert_count,
if (non_manifold_edges_of_vert[v].size() == 2) { if (non_manifold_edges_of_vert[v].size() == 2) {
const auto other_eId = (non_manifold_edges_of_vert[v][0] == eId) ? non_manifold_edges_of_vert[v][1] const auto other_eId = (non_manifold_edges_of_vert[v][0] == eId) ? non_manifold_edges_of_vert[v][1]
: non_manifold_edges_of_vert[v][0]; : non_manifold_edges_of_vert[v][0];
edge = patch_edges[other_eId];
if (!visited_edge[other_eId]) { if (!visited_edge[other_eId]) {
Q.emplace(other_eId); Q.emplace(other_eId);
chain.emplace_back(std::move(patch_edges[other_eId])); if (v == chain_edges.front()) {
if (v == edge.v1)
chain_edges.emplace_front(edge.v2);
else
chain_edges.emplace_front(edge.v1);
} else {
if (v == edge.v1)
chain_edges.emplace_front(edge.v2);
else
chain_edges.emplace_front(edge.v1);
}
visited_edge[other_eId] = true; visited_edge[other_eId] = true;
} }
} }
} }
// add chain edges to chains.index_group
chains.index_group.insert(chains.index_group.end(),
std::make_move_iterator(chain_edges.begin()),
std::make_move_iterator(chain_edges.end()));
} }
} }
chains.start_indices.emplace_back(static_cast<uint32_t>(chains.index_group.size()));
chains.start_indices.shrink_to_fit();
chains.index_group.shrink_to_fit();
} }
void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>>& half_patch_adj_list, void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>>& half_patch_adj_list,
stl_vector_mp<stl_vector_mp<uint32_t>>& shells, flat_index_group_t& shells,
stl_vector_mp<uint32_t>& shell_of_half_patch, stl_vector_mp<uint32_t>& shell_of_half_patch,
stl_vector_mp<stl_vector_mp<uint32_t>>& components, flat_index_group_t& components,
stl_vector_mp<uint32_t>& component_of_patch) stl_vector_mp<uint32_t>& component_of_patch)
{ {
const auto num_patch = half_patch_adj_list.size() / 2; const auto num_patch = half_patch_adj_list.size() / 2;
@ -152,11 +201,11 @@ void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>>&
for (uint32_t i = 0; i < 2 * num_patch; i++) { for (uint32_t i = 0; i < 2 * num_patch; i++) {
if (!visited_flags[i]) { if (!visited_flags[i]) {
// create new component // create new component
const uint32_t shell_Id = static_cast<uint32_t>(shells.size()); const auto shell_Id = static_cast<uint32_t>(shells.size());
auto& shell = shells.emplace_back(); shells.start_indices.emplace_back(shell_Id);
std::queue<uint32_t> Q{}; std::queue<uint32_t> Q{};
Q.emplace(i); Q.emplace(i);
shell.emplace_back(i); shells.index_group.emplace_back(i);
shell_of_half_patch[i] = shell_Id; shell_of_half_patch[i] = shell_Id;
visited_flags[i] = true; visited_flags[i] = true;
while (!Q.empty()) { while (!Q.empty()) {
@ -164,7 +213,7 @@ void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>>&
Q.pop(); Q.pop();
for (auto hp : half_patch_adj_list[half_patch]) { for (auto hp : half_patch_adj_list[half_patch]) {
if (!visited_flags[hp]) { if (!visited_flags[hp]) {
shell.emplace_back(hp); shells.index_group.emplace_back(hp);
shell_of_half_patch[hp] = shell_Id; shell_of_half_patch[hp] = shell_Id;
Q.emplace(hp); Q.emplace(hp);
visited_flags[hp] = true; visited_flags[hp] = true;
@ -173,20 +222,21 @@ void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>>&
} }
} }
} }
shells.start_indices.emplace_back(static_cast<uint32_t>(shells.index_group.size()));
// find connected component of patch-adjacency graph // find connected component of patch-adjacency graph
// each component is an iso-surface component // each component is an iso-surface component
visited_flags.clear(); visited_flags.clear();
visited_flags.resize(num_patch, false); visited_flags.resize(num_patch, false);
components.clear();
component_of_patch.resize(num_patch); component_of_patch.resize(num_patch);
for (uint32_t i = 0; i < num_patch; i++) { for (uint32_t i = 0; i < num_patch; i++) {
if (!visited_flags[i]) { if (!visited_flags[i]) {
// create new component // create new component
const uint32_t component_Id = static_cast<uint32_t>(components.size()); const auto component_Id = static_cast<uint32_t>(components.size());
auto& component = components.emplace_back(); components.start_indices.emplace_back(component_Id);
std::queue<uint32_t> Q{}; std::queue<uint32_t> Q{};
Q.emplace(i); Q.emplace(i);
component.emplace_back(i); components.index_group.emplace_back(i);
component_of_patch[i] = component_Id; component_of_patch[i] = component_Id;
visited_flags[i] = true; visited_flags[i] = true;
while (!Q.empty()) { while (!Q.empty()) {
@ -196,7 +246,7 @@ void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>>&
for (auto hp : half_patch_adj_list[2 * patch]) { for (auto hp : half_patch_adj_list[2 * patch]) {
if (!visited_flags[hp / 2]) { if (!visited_flags[hp / 2]) {
const auto p = hp / 2; const auto p = hp / 2;
component.emplace_back(p); components.index_group.emplace_back(p);
component_of_patch[p] = component_Id; component_of_patch[p] = component_Id;
Q.emplace(p); Q.emplace(p);
visited_flags[p] = true; visited_flags[p] = true;
@ -206,7 +256,7 @@ void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>>&
for (auto hp : half_patch_adj_list[2 * patch + 1]) { for (auto hp : half_patch_adj_list[2 * patch + 1]) {
if (!visited_flags[hp / 2]) { if (!visited_flags[hp / 2]) {
const auto p = hp / 2; const auto p = hp / 2;
component.emplace_back(p); components.index_group.emplace_back(p);
component_of_patch[p] = component_Id; component_of_patch[p] = component_Id;
Q.emplace(p); Q.emplace(p);
visited_flags[p] = true; visited_flags[p] = true;
@ -215,6 +265,8 @@ void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>>&
} }
} }
} }
components.start_indices.emplace_back(static_cast<uint32_t>(components.index_group.size()));
// get shells as list of patch indices // get shells as list of patch indices
// for (auto& shell : shells) { // for (auto& shell : shells) {
// for (auto& pId : shell) { // for (auto& pId : shell) {
@ -225,7 +277,7 @@ void compute_shells_and_components(const stl_vector_mp<stl_vector_mp<uint32_t>>&
void compute_arrangement_cells(uint32_t num_shell, void compute_arrangement_cells(uint32_t num_shell,
const stl_vector_mp<std::pair<uint32_t, uint32_t>>& shell_links, const stl_vector_mp<std::pair<uint32_t, uint32_t>>& shell_links,
stl_vector_mp<stl_vector_mp<uint32_t>>& arrangement_cells) flat_index_group_t& arrangement_cells)
{ {
// build shell adjacency list // build shell adjacency list
uint32_t sink_shell = num_shell; uint32_t sink_shell = num_shell;
@ -250,17 +302,17 @@ void compute_arrangement_cells(uint32_t
for (uint32_t i = 0; i < num_shell + 1; ++i) { for (uint32_t i = 0; i < num_shell + 1; ++i) {
if (!visited_shell[i]) { if (!visited_shell[i]) {
// create new component // create new component
auto& arr_cell = arrangement_cells.emplace_back(); arrangement_cells.start_indices.emplace_back(static_cast<uint32_t>(arrangement_cells.size()));
std::queue<uint32_t> Q{}; std::queue<uint32_t> Q{};
Q.emplace(i); Q.emplace(i);
arr_cell.emplace_back(i); arrangement_cells.index_group.emplace_back(i);
visited_shell[i] = true; visited_shell[i] = true;
while (!Q.empty()) { while (!Q.empty()) {
const auto shell_id = Q.front(); const auto shell_id = Q.front();
Q.pop(); Q.pop();
for (const auto s : adjacent_shells[shell_id]) { for (const auto s : adjacent_shells[shell_id]) {
if (!visited_shell[s]) { if (!visited_shell[s]) {
arr_cell.emplace_back(s); arrangement_cells.index_group.emplace_back(s);
Q.emplace(s); Q.emplace(s);
visited_shell[s] = true; visited_shell[s] = true;
} }
@ -270,13 +322,21 @@ void compute_arrangement_cells(uint32_t
} }
// remove sink shell from arrangement cells // remove sink shell from arrangement cells
stl_vector_mp<uint32_t> sink_free_shell_list{}; flat_index_group_t sink_free_cells{};
for (auto& arr_cell : arrangement_cells) { sink_free_cells.index_group.reserve(arrangement_cells.index_group.size());
sink_free_shell_list.clear(); sink_free_cells.start_indices.reserve(arrangement_cells.size() + 1);
for (const auto s : arr_cell) { sink_free_cells.start_indices.emplace_back(0);
if (s < num_shell) { sink_free_shell_list.emplace_back(s); } sink_free_cells.group_foreach([&](uint32_t group_idx, uint32_t range_start, uint32_t range_end) {
if (sink_free_cells.index_group.size() > sink_free_cells.start_indices.back()) {
sink_free_cells.start_indices.emplace_back(static_cast<uint32_t>(sink_free_cells.index_group.size()));
} }
// arr_cell = sink_free_shell_list; for (auto i = range_start; i < range_end; ++i) {
std::swap(arr_cell, sink_free_shell_list); if (arrangement_cells.index_group[i] < num_shell) {
} sink_free_cells.index_group.emplace_back(arrangement_cells.index_group[i]);
}
}
});
sink_free_cells.index_group.shrink_to_fit();
sink_free_cells.start_indices.shrink_to_fit();
std::swap(arrangement_cells, sink_free_cells);
} }

13
network_process/src/connect_by_topo/topo_ray_shooting/find_extermal_edges.hpp

@ -7,8 +7,8 @@ static inline void find_extremal_edges(
const scene_bg_mesh_info_t &scene_bg_mesh_info, const scene_bg_mesh_info_t &scene_bg_mesh_info,
const stl_vector_mp<iso_vertex_t> &iso_verts, const stl_vector_mp<iso_vertex_t> &iso_verts,
const stl_vector_mp<polygon_face_t> &iso_faces, const stl_vector_mp<polygon_face_t> &iso_faces,
const stl_vector_mp<stl_vector_mp<uint32_t>> &patches, const flat_index_group_t &patches,
const stl_vector_mp<stl_vector_mp<uint32_t>> &components, const flat_index_group_t &components,
const stl_vector_mp<uint32_t> &component_of_patch, const stl_vector_mp<uint32_t> &component_of_patch,
const flat_hash_map_mp<compact_bg_mesh_coord_t, compact_bg_mesh_coord_t> &next_vert, const flat_hash_map_mp<compact_bg_mesh_coord_t, compact_bg_mesh_coord_t> &next_vert,
// extremal edge of component i is stored at position [2*i], [2*i+1] // extremal edge of component i is stored at position [2*i], [2*i+1]
@ -26,11 +26,12 @@ static inline void find_extremal_edges(
iso_vId_compId_of_tet_vert.reserve(iso_faces.size() / 2); iso_vId_compId_of_tet_vert.reserve(iso_faces.size() / 2);
// //
stl_vector_mp<bool> is_iso_vert_visited(iso_verts.size(), false); stl_vector_mp<bool> is_iso_vert_visited(iso_verts.size(), false);
for (uint32_t i = 0; i < patches.size(); ++i) { patches.group_foreach([&](uint32_t patch_index, uint32_t range_start, uint32_t range_end) {
uint32_t component_id = component_of_patch[i]; uint32_t component_id = component_of_patch[patch_index];
auto &u1 = extremal_edge_of_component[2 * component_id]; auto &u1 = extremal_edge_of_component[2 * component_id];
auto &u2 = extremal_edge_of_component[2 * component_id + 1]; auto &u2 = extremal_edge_of_component[2 * component_id + 1];
for (auto fId : patches[i]) { for (uint32_t i = range_start; i < range_end; ++i) {
const auto &fId = patches.index_group[i];
for (const auto &tet_face : iso_faces[fId].headers) iso_face_id_of_tet_face.try_emplace(tet_face, fId); for (const auto &tet_face : iso_faces[fId].headers) iso_face_id_of_tet_face.try_emplace(tet_face, fId);
for (const auto vId : iso_faces[fId].vertex_indices) { for (const auto vId : iso_faces[fId].vertex_indices) {
if (!is_iso_vert_visited[vId]) { if (!is_iso_vert_visited[vId]) {
@ -82,5 +83,5 @@ static inline void find_extremal_edges(
} }
} }
} }
} });
} }

6
network_process/src/connect_by_topo/topology_ray_shooting.cpp

@ -10,11 +10,11 @@ void topo_ray_shooting(const scene_bg_mesh_info_t
const stl_vector_mp<arrangement_t> &tetrahedron_arrangements, const stl_vector_mp<arrangement_t> &tetrahedron_arrangements,
const stl_vector_mp<iso_vertex_t> &iso_verts, const stl_vector_mp<iso_vertex_t> &iso_verts,
const stl_vector_mp<polygon_face_t> &iso_faces, const stl_vector_mp<polygon_face_t> &iso_faces,
const stl_vector_mp<stl_vector_mp<uint32_t>> &patches, const flat_index_group_t &patches,
const stl_vector_mp<uint32_t> &patch_of_face, const stl_vector_mp<uint32_t> &patch_of_face,
const stl_vector_mp<stl_vector_mp<uint32_t>> &shells, const flat_index_group_t &shells,
const stl_vector_mp<uint32_t> &shell_of_half_patch, const stl_vector_mp<uint32_t> &shell_of_half_patch,
const stl_vector_mp<stl_vector_mp<uint32_t>> &components, const flat_index_group_t &components,
const stl_vector_mp<uint32_t> &component_of_patch, const stl_vector_mp<uint32_t> &component_of_patch,
stl_vector_mp<std::pair<uint32_t, uint32_t>> &shell_links) stl_vector_mp<std::pair<uint32_t, uint32_t>> &shell_links)
{ {

41
network_process/src/post_topo/filter_polygon_faces.cpp

@ -2,15 +2,15 @@
#include <post_topo.hpp> #include <post_topo.hpp>
dynamic_bitset_mp<> filter_polygon_faces(const stl_vector_mp<polygon_face_t>& faces, dynamic_bitset_mp<> filter_polygon_faces(const stl_vector_mp<polygon_face_t>& faces,
const stl_vector_mp<stl_vector_mp<uint32_t>>& patches, const flat_index_group_t& patches,
const stl_vector_mp<stl_vector_mp<uint32_t>>& arrangement_cells, const flat_index_group_t& arrangement_cells,
const stl_vector_mp<uint32_t>& shell_of_half_patch, const stl_vector_mp<uint32_t>& shell_of_half_patch,
const stl_vector_mp<stl_vector_mp<uint32_t>>& shells, const flat_index_group_t& shells,
const stl_vector_mp<uint32_t>& shell_to_cell, const stl_vector_mp<uint32_t>& shell_to_cell,
const dynamic_bitset_mp<>& active_cell_label, const dynamic_bitset_mp<>& active_cell_label,
stl_vector_mp<uint32_t>& output_polygon_faces, stl_vector_mp<uint32_t>& output_polygon_faces,
stl_vector_mp<uint32_t>& output_vertex_counts_of_face) stl_vector_mp<uint32_t>& output_vertex_counts_of_face)
{ {
dynamic_bitset_mp<> active_face_label(faces.size(), false); dynamic_bitset_mp<> active_face_label(faces.size(), false);
output_polygon_faces.reserve(faces.size() * 3); output_polygon_faces.reserve(faces.size() * 3);
@ -18,7 +18,6 @@ dynamic_bitset_mp<> filter_polygon_faces(const stl_vector_mp<polygon_face_t>&
stl_vector_mp<bool> visited_cells(arrangement_cells.size(), false); stl_vector_mp<bool> visited_cells(arrangement_cells.size(), false);
std::queue<uint32_t> Q{}; std::queue<uint32_t> Q{};
for (uint32_t i = 0; i < arrangement_cells.size(); ++i) { for (uint32_t i = 0; i < arrangement_cells.size(); ++i) {
if (active_cell_label[i]) { if (active_cell_label[i]) {
Q.emplace(i); Q.emplace(i);
@ -27,16 +26,16 @@ dynamic_bitset_mp<> filter_polygon_faces(const stl_vector_mp<polygon_face_t>&
} }
while (!Q.empty()) { while (!Q.empty()) {
const auto cell_index = Q.front(); const auto cell_index = Q.front();
const auto cell = arrangement_cells[cell_index];
Q.pop(); Q.pop();
if (!visited_cells[cell_index] && active_cell_label[cell_index]) { if (!visited_cells[cell_index] && active_cell_label[cell_index]) {
visited_cells[cell_index] = true; visited_cells[cell_index] = true;
for (auto shell : cell) { arrangement_cells.loop_on_group(cell_index, [&](uint32_t _, uint32_t shell_index) {
// it is secured that one shell cell cannot have a pair of half patches with same patch index // it is secured that one shell cell cannot have a pair of half patches with same patch index
for (auto half_patch : shells[shell]) { shells.loop_on_group(shell_index, [&](uint32_t _, uint32_t half_patch_index) {
bool sign = (half_patch % 2 == 0) ? 0 : 1; bool sign = (half_patch_index % 2 == 0) ? 0 : 1;
auto oppose_cell = shell_to_cell[shell_of_half_patch[!sign ? (half_patch + 1) : (half_patch - 1)]]; auto oppose_cell =
shell_to_cell[shell_of_half_patch[!sign ? (half_patch_index + 1) : (half_patch_index - 1)]];
// iff not interior patch, we do surface propagation // iff not interior patch, we do surface propagation
if (!active_cell_label[oppose_cell]) { if (!active_cell_label[oppose_cell]) {
@ -50,30 +49,30 @@ dynamic_bitset_mp<> filter_polygon_faces(const stl_vector_mp<polygon_face_t>&
// (i.e. clockwise), then its normal should point to the other side of that surface, which is the // (i.e. clockwise), then its normal should point to the other side of that surface, which is the
// outside of that surface, so it is also right // outside of that surface, so it is also right
if (!sign) { if (!sign) {
for (const auto face_index : patches[half_patch / 2]) { patches.loop_on_group(half_patch_index / 2, [&](uint32_t _, uint32_t face_index) {
active_face_label[face_index] = true; active_face_label[face_index] = true;
const auto& face_vertices = faces[face_index].vertex_indices; const auto& face_vertices = faces[face_index].vertex_indices;
output_vertex_counts_of_face.emplace_back(face_vertices.size()); output_vertex_counts_of_face.emplace_back(face_vertices.size());
output_polygon_faces.insert(output_polygon_faces.end(), output_polygon_faces.insert(output_polygon_faces.end(),
std::make_move_iterator(face_vertices.rbegin()), std::make_move_iterator(face_vertices.rbegin()),
std::make_move_iterator(face_vertices.rend())); std::make_move_iterator(face_vertices.rend()));
} });
} else { } else {
for (const auto face_index : patches[half_patch / 2]) { patches.loop_on_group(half_patch_index / 2, [&](uint32_t _, uint32_t face_index) {
active_face_label[face_index] = true; active_face_label[face_index] = true;
const auto& face_vertices = faces[face_index].vertex_indices; const auto& face_vertices = faces[face_index].vertex_indices;
output_vertex_counts_of_face.emplace_back(face_vertices.size()); output_vertex_counts_of_face.emplace_back(face_vertices.size());
output_polygon_faces.insert(output_polygon_faces.end(), output_polygon_faces.insert(output_polygon_faces.end(),
std::make_move_iterator(face_vertices.begin()), std::make_move_iterator(face_vertices.begin()),
std::make_move_iterator(face_vertices.end())); std::make_move_iterator(face_vertices.end()));
} });
} }
} else if (!visited_cells[oppose_cell]) { } else if (!visited_cells[oppose_cell]) {
// active and not visited cell, enqueue it // active and not visited cell, enqueue it
Q.emplace(oppose_cell); Q.emplace(oppose_cell);
} }
} });
} });
} }
} }

32
network_process/src/post_topo/patch_propagation.cpp

@ -3,14 +3,14 @@
#include <post_topo.hpp> #include <post_topo.hpp>
void propagate_subface_labels(const baked_blobtree_t& tree, void propagate_subface_labels(const baked_blobtree_t& tree,
const stl_vector_mp<polygon_face_t>& faces, const stl_vector_mp<polygon_face_t>& faces,
const stl_vector_mp<stl_vector_mp<uint32_t>>& patches, const flat_index_group_t& patches,
const stl_vector_mp<stl_vector_mp<uint32_t>>& arrangement_cells, const flat_index_group_t& arrangement_cells,
const stl_vector_mp<uint32_t>& shell_of_half_patch, const stl_vector_mp<uint32_t>& shell_of_half_patch,
const stl_vector_mp<stl_vector_mp<uint32_t>>& shells, const flat_index_group_t& shells,
const stl_vector_mp<uint32_t>& shell_to_cell, const stl_vector_mp<uint32_t>& shell_to_cell,
stl_vector_mp<dynamic_bitset_mp<>>& cell_subface_signs) stl_vector_mp<dynamic_bitset_mp<>>& cell_subface_signs)
{ {
const auto num_subface = tree.subfaces.size(); const auto num_subface = tree.subfaces.size();
@ -23,23 +23,23 @@ void propagate_subface_labels(const baked_blobtree_t& tree
Q.emplace(0); Q.emplace(0);
while (!Q.empty()) { while (!Q.empty()) {
const auto cell_index = Q.front(); const auto cell_index = Q.front();
const auto cell = arrangement_cells[cell_index];
Q.pop(); Q.pop();
if (!visited_cells[cell_index]) { if (!visited_cells[cell_index]) {
visited_cells[cell_index] = true; visited_cells[cell_index] = true;
cell_neighbors_map.clear(); cell_neighbors_map.clear();
for (auto shell : cell) { arrangement_cells.loop_on_group(cell_index, [&](uint32_t _, uint32_t shell_index) {
for (auto half_patch : shells[shell]) { shells.loop_on_group(shell_index, [&](uint32_t _, uint32_t half_patch_index) {
auto subface_label = faces[patches[half_patch / 2][0]].subface_index; auto subface_label = faces[patches.index_group[patches.start_indices[half_patch_index / 2]]].subface_index;
// CAUTION: we assume that the sign is 1 when the surface is inside the sdf before // CAUTION: we assume that the sign is 1 when the surface is inside the sdf before
// but in blobtree we assume that the sign is 0 when the surface is inside the sdf // but in blobtree we assume that the sign is 0 when the surface is inside the sdf
// so we need to flip the sign here // so we need to flip the sign here
// bool sign = (half_patch % 2 == 0) ? 1 : 0; // bool sign = (half_patch % 2 == 0) ? 1 : 0;
// auto oppose_cell = shell_to_cell[shell_of_half_patch[sign ? (half_patch + 1) : (half_patch - 1)]]; // auto oppose_cell = shell_to_cell[shell_of_half_patch[sign ? (half_patch + 1) : (half_patch - 1)]];
bool sign = (half_patch % 2 == 0) ? 0 : 1; bool sign = (half_patch_index % 2 == 0) ? 0 : 1;
auto oppose_cell = shell_to_cell[shell_of_half_patch[!sign ? (half_patch + 1) : (half_patch - 1)]]; auto oppose_cell =
shell_to_cell[shell_of_half_patch[!sign ? (half_patch_index + 1) : (half_patch_index - 1)]];
cell_neighbors_map[oppose_cell] = std::make_pair(subface_label, sign); cell_neighbors_map[oppose_cell] = std::make_pair(subface_label, sign);
#ifndef RELEASE_BRANCH #ifndef RELEASE_BRANCH
@ -57,8 +57,8 @@ void propagate_subface_labels(const baked_blobtree_t& tree
} }
cell_indices_of_inactive_subfaces[subface_label].clear(); cell_indices_of_inactive_subfaces[subface_label].clear();
} }
} });
} });
// fetch inactive subface index // fetch inactive subface index
for (uint32_t subface_index = 0; subface_index < num_subface; subface_index++) { for (uint32_t subface_index = 0; subface_index < num_subface; subface_index++) {

46
network_process/src/process.cpp

@ -1,3 +1,5 @@
#include <numeric>
#include <process.hpp> #include <process.hpp>
#include <prim_gen.hpp> #include <prim_gen.hpp>
@ -66,27 +68,29 @@ ISNP_API void build_implicit_network_by_blobtree(const s_settings&
iso_faces); iso_faces);
} }
// connect components by topology // connect components by topology
stl_vector_mp<uint32_t> patch_of_face(iso_faces.size()); // TODO: split headers of chains out, because it should not change during the chain
stl_vector_mp<uint32_t> shell_of_half_patch{}; stl_vector_mp<iso_edge_t> chain_representative_headers{};
stl_vector_mp<uint32_t> component_of_patch{}; stl_vector_mp<uint32_t> patch_of_face(iso_faces.size());
stl_vector_mp<stl_vector_mp<uint32_t>> patches{}; stl_vector_mp<uint32_t> shell_of_half_patch{};
stl_vector_mp<stl_list_mp<iso_edge_t>> chains{}; stl_vector_mp<uint32_t> component_of_patch{};
stl_vector_mp<stl_vector_mp<uint32_t>> shells{}; flat_index_group_t patches{};
stl_vector_mp<stl_vector_mp<uint32_t>> components{}; flat_index_group_t chains{};
stl_vector_mp<stl_vector_mp<uint32_t>> arrangement_cells{}; flat_index_group_t shells{};
stl_vector_mp<uint32_t> shell_to_cell{}; flat_index_group_t components{};
flat_index_group_t arrangement_cells{};
stl_vector_mp<uint32_t> shell_to_cell{};
{ {
{ {
stl_vector_mp<stl_vector_mp<uint32_t>> edges_of_iso_face{}; stl_vector_mp<stl_vector_mp<uint32_t>> edges_of_iso_face{};
stl_vector_mp<iso_edge_t> iso_edges{}; stl_vector_mp<iso_edge_t> iso_edges{};
compute_patch_edges(iso_faces, edges_of_iso_face, iso_edges); compute_patch_edges(iso_faces, edges_of_iso_face, iso_edges);
compute_patches(edges_of_iso_face, iso_edges, iso_faces, patches, patch_of_face); compute_patches(edges_of_iso_face, iso_edges, iso_faces, patches, patch_of_face);
compute_chains(iso_verts.size(), iso_edges, chains); compute_chains(iso_verts.size(), iso_edges, chains, chain_representative_headers);
} }
stl_vector_mp<stl_vector_mp<uint32_t>> half_patch_adj_list(2 * patches.size()); stl_vector_mp<stl_vector_mp<uint32_t>> half_patch_adj_list(2 * chains.size());
for (size_t i = 0; i < chains.size(); ++i) chains.group_foreach([&](uint32_t chain_index, uint32_t _, uint32_t __) {
compute_patch_order(tetrahedrons, compute_patch_order(tetrahedrons,
chains[i].front(), chain_representative_headers[chain_index],
iso_verts, iso_verts,
iso_faces, iso_faces,
tetrahedron_arrangements, tetrahedron_arrangements,
@ -95,12 +99,14 @@ ISNP_API void build_implicit_network_by_blobtree(const s_settings&
zero_vertex_to_incident_tet_mapping, zero_vertex_to_incident_tet_mapping,
patch_of_face, patch_of_face,
half_patch_adj_list); half_patch_adj_list);
});
compute_shells_and_components(half_patch_adj_list, shells, shell_of_half_patch, components, component_of_patch); compute_shells_and_components(half_patch_adj_list, shells, shell_of_half_patch, components, component_of_patch);
if (components.size() == 1) // no nesting problem, each shell is an arrangement cell if (components.size() == 1) // no nesting problem, each shell is an arrangement cell
{ {
arrangement_cells.reserve(shells.size()); arrangement_cells.index_group.resize(shells.size(), 0u);
for (uint32_t i = 0; i < shells.size(); ++i) { arrangement_cells.emplace_back(std::vector{i}); } arrangement_cells.start_indices.resize(shells.size() + 1);
std::iota(arrangement_cells.start_indices.begin(), arrangement_cells.start_indices.end(), 0u);
} else { } else {
{ {
stl_vector_mp<std::pair<uint32_t, uint32_t>> shell_links{}; stl_vector_mp<std::pair<uint32_t, uint32_t>> shell_links{};
@ -117,14 +123,14 @@ ISNP_API void build_implicit_network_by_blobtree(const s_settings&
components, components,
component_of_patch, component_of_patch,
shell_links); shell_links);
compute_arrangement_cells(static_cast<uint32_t>(shells.size()), shell_links, arrangement_cells); compute_arrangement_cells(shells.size(), shell_links, arrangement_cells);
} }
} }
shell_to_cell.resize(shells.size()); shell_to_cell.resize(arrangement_cells.size());
for (uint32_t i = 0; i < arrangement_cells.size(); i++) { arrangement_cells.foreach ([&shell_to_cell](uint32_t cell_index, uint32_t _, uint32_t shell_index) {
for (auto shell : arrangement_cells[i]) shell_to_cell[shell] = i; shell_to_cell[shell_index] = cell_index;
} });
} }
// post process // post process

4
shared_module/xmake.lua

@ -1,4 +1,4 @@
add_requires("mimalloc-adjust") add_requires("mimalloc", {configs = {shared = false}})
add_requires("parallel-hashmap") add_requires("parallel-hashmap")
add_requires("range-v3") add_requires("range-v3")
add_requires("eigen-latest") add_requires("eigen-latest")
@ -8,6 +8,6 @@ add_requires("xxhash")
target("shared_module") target("shared_module")
set_kind("headeronly") set_kind("headeronly")
add_includedirs("./", {public = true}) add_includedirs("./", {public = true})
add_packages("mimalloc-adjust", "parallel-hashmap", "range-v3", "eigen-latest", "xxhash", {public = true}) add_packages("mimalloc", "parallel-hashmap", "range-v3", "eigen-latest", "xxhash", {public = true})
add_rules("library.force.distribute.header", {headers = path.join(os.scriptdir(), "math", "math_defs.h")}) add_rules("library.force.distribute.header", {headers = path.join(os.scriptdir(), "math", "math_defs.h")})
target_end() target_end()
Loading…
Cancel
Save