**Problem** When using Sculpt Texture Paint to paint objects in a debug build, Blender will crash due to an Address Sanitizer exception, `stack-use-after-free`. This makes development for Sculpt Texture Paint harder since the feature can't be used in debug builds without turning off Address Sanitizer. Code-wise, the issue here happens when extending UV island borders. When creating and adding UV primitives to extend the UV border, the primitives are allocated locally and then added to a list. This means that when these primitives are accessed later from the list, the ASAN error is triggered since the primitives have been freed. Freed primitives are generally accessed when checking if a primitive has already been added to the primitive list belonging to its connected UV Edges. **Solution** The solution here is to change UV Edges to store the index of a UV primitive, and not use a pointer to the object itself. This is the best solution since it makes it fast and simple to check if an UV Edge already has a reference to its connected primitives, while still allowing access to the primitive objects since primitives can be accessed using indexes from mesh data objects. Co-authored-by: T0MIS0N <50230774+T0MIS0N@users.noreply.github.com> Pull Request: https://projects.blender.org/blender/blender/pulls/137032
379 lines
10 KiB
C++
379 lines
10 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*
|
|
* UV Islands for pbvh::Tree Pixel extraction. When primitives share an edge they belong to the
|
|
* same UV Island.
|
|
*
|
|
* \note Similar to `uvedit_islands.cc`, but optimized for pbvh::Tree painting without using BMesh
|
|
* for performance reasons. Non-manifold meshes only (i.e. edges must have less than 3 faces).
|
|
*
|
|
* Polygons (face with more than 3 edges) are supported as they are split up to primitives.
|
|
*
|
|
* \note After the algorithm is stable the OO data structures should be converted back to use DOD
|
|
* principles to improve reusability. Currently this is not done (yet) as during implementation it
|
|
* was hard to follow when the algorithm evolved during several iterations. At that time we needed
|
|
* more flexibility.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <optional>
|
|
|
|
#include "BLI_array.hh"
|
|
#include "BLI_map.hh"
|
|
#include "BLI_math_vector_types.hh"
|
|
#include "BLI_offset_indices.hh"
|
|
#include "BLI_vector.hh"
|
|
#include "BLI_vector_list.hh"
|
|
|
|
namespace blender::bke::pbvh::uv_islands {
|
|
|
|
struct UVBorder;
|
|
struct UVEdge;
|
|
struct UVIslands;
|
|
struct UVIslandsMask;
|
|
struct UVPrimitive;
|
|
struct MeshData;
|
|
struct UVVertex;
|
|
|
|
class VertToEdgeMap {
|
|
Array<Vector<int>> edges_of_vert_;
|
|
|
|
public:
|
|
VertToEdgeMap() = delete;
|
|
VertToEdgeMap(const int verts_num)
|
|
{
|
|
edges_of_vert_.reinitialize(verts_num);
|
|
}
|
|
|
|
void add(const int edge_i, const int v1, const int v2)
|
|
{
|
|
edges_of_vert_[v1].append(edge_i);
|
|
edges_of_vert_[v2].append(edge_i);
|
|
}
|
|
Span<int> operator[](const int vert_i) const
|
|
{
|
|
return edges_of_vert_[vert_i];
|
|
}
|
|
};
|
|
|
|
class EdgeToPrimitiveMap {
|
|
Array<Vector<int>> primitives_of_edge_;
|
|
|
|
public:
|
|
EdgeToPrimitiveMap() = delete;
|
|
EdgeToPrimitiveMap(const int edges_num)
|
|
{
|
|
primitives_of_edge_.reinitialize(edges_num);
|
|
}
|
|
|
|
void add(const int primitive_i, const int edge_i)
|
|
{
|
|
primitives_of_edge_[edge_i].append(primitive_i);
|
|
}
|
|
Span<int> operator[](const int edge_i) const
|
|
{
|
|
return primitives_of_edge_[edge_i];
|
|
}
|
|
};
|
|
|
|
class TriangleToEdgeMap {
|
|
Array<std::array<int, 3>> edges_of_triangle_;
|
|
|
|
public:
|
|
TriangleToEdgeMap() = delete;
|
|
TriangleToEdgeMap(const int edges_num)
|
|
{
|
|
edges_of_triangle_.reinitialize(edges_num);
|
|
}
|
|
|
|
void add(const Span<int> edges, const int tri_i)
|
|
{
|
|
std::copy(edges.begin(), edges.end(), edges_of_triangle_[tri_i].begin());
|
|
}
|
|
Span<int> operator[](const int tri_i) const
|
|
{
|
|
return edges_of_triangle_[tri_i];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* MeshData contains input geometry data converted in a list of primitives, edges and vertices for
|
|
* quick access for both local space and uv space.
|
|
*/
|
|
struct MeshData {
|
|
public:
|
|
OffsetIndices<int> faces;
|
|
Span<int3> corner_tris;
|
|
Span<int> corner_verts;
|
|
Span<float2> uv_map;
|
|
Span<float3> vert_positions;
|
|
|
|
VertToEdgeMap vert_to_edge_map;
|
|
|
|
Vector<int2> edges;
|
|
EdgeToPrimitiveMap edge_to_primitive_map;
|
|
|
|
TriangleToEdgeMap primitive_to_edge_map;
|
|
|
|
/**
|
|
* UV island each primitive belongs to. This is used to speed up the initial uv island
|
|
* extraction and should not be used afterwards.
|
|
*/
|
|
Array<int> uv_island_ids;
|
|
/** Total number of found uv islands. */
|
|
int64_t uv_island_len;
|
|
|
|
explicit MeshData(OffsetIndices<int> faces,
|
|
Span<int3> corner_tris,
|
|
Span<int> corner_verts,
|
|
Span<float2> uv_map,
|
|
Span<float3> vert_positions);
|
|
};
|
|
|
|
struct UVVertex {
|
|
int vertex;
|
|
/* Position in uv space. */
|
|
float2 uv;
|
|
|
|
/* uv edges that share this UVVertex. */
|
|
Vector<UVEdge *> uv_edges;
|
|
|
|
struct {
|
|
bool is_border : 1;
|
|
bool is_extended : 1;
|
|
} flags;
|
|
|
|
explicit UVVertex();
|
|
explicit UVVertex(const MeshData &mesh_data, const int loop);
|
|
};
|
|
|
|
struct UVEdge {
|
|
std::array<UVVertex *, 2> vertices;
|
|
Vector<int, 2> uv_primitive_indices;
|
|
|
|
UVVertex *get_other_uv_vertex(const int vertex_index);
|
|
bool has_shared_edge(Span<float2> uv_map, const int loop_1, const int loop_2) const;
|
|
bool has_shared_edge(const UVEdge &other) const;
|
|
bool has_same_vertices(const int2 &edge) const;
|
|
bool is_border_edge() const;
|
|
|
|
private:
|
|
bool has_shared_edge(const UVVertex &v1, const UVVertex &v2) const;
|
|
bool has_same_vertices(const int vert1, const int vert2) const;
|
|
bool has_same_uv_vertices(const UVEdge &other) const;
|
|
};
|
|
|
|
struct UVPrimitive {
|
|
/**
|
|
* Index of the primitive in the original mesh.
|
|
*/
|
|
const int primitive_i;
|
|
Vector<UVEdge *, 3> edges;
|
|
|
|
explicit UVPrimitive(const int primitive_i);
|
|
|
|
Vector<std::pair<UVEdge *, UVEdge *>> shared_edges(UVPrimitive &other);
|
|
bool has_shared_edge(const UVPrimitive &other) const;
|
|
bool has_shared_edge(const MeshData &mesh_data, int other_triangle_index) const;
|
|
|
|
/**
|
|
* Get the UVVertex in the order that the verts are ordered in the MeshPrimitive.
|
|
*/
|
|
const UVVertex *get_uv_vertex(const MeshData &mesh_data, const uint8_t mesh_vert_index) const;
|
|
|
|
/**
|
|
* Get the UVEdge that share the given uv coordinates.
|
|
* Will assert when no UVEdge found.
|
|
*/
|
|
UVEdge *get_uv_edge(const float2 uv1, const float2 uv2) const;
|
|
UVEdge *get_uv_edge(const int v1, const int v2) const;
|
|
|
|
bool contains_uv_vertex(const UVVertex *uv_vertex) const;
|
|
const UVVertex *get_other_uv_vertex(const UVVertex *v1, const UVVertex *v2) const;
|
|
|
|
UVBorder extract_border() const;
|
|
};
|
|
|
|
struct UVBorderEdge {
|
|
UVEdge *edge;
|
|
bool tag = false;
|
|
UVPrimitive *uv_primitive;
|
|
/* Should the vertices of the edge be evaluated in reverse order. */
|
|
bool reverse_order = false;
|
|
|
|
int64_t index = -1;
|
|
int64_t prev_index = -1;
|
|
int64_t next_index = -1;
|
|
int64_t border_index = -1;
|
|
|
|
explicit UVBorderEdge(UVEdge *edge, UVPrimitive *uv_primitive);
|
|
|
|
UVVertex *get_uv_vertex(int index);
|
|
const UVVertex *get_uv_vertex(int index) const;
|
|
|
|
/**
|
|
* Get the uv vertex from the primitive that is not part of the edge.
|
|
*/
|
|
const UVVertex *get_other_uv_vertex() const;
|
|
|
|
float length() const;
|
|
};
|
|
|
|
struct UVBorderCorner {
|
|
UVBorderEdge *first;
|
|
UVBorderEdge *second;
|
|
float angle;
|
|
|
|
explicit UVBorderCorner(UVBorderEdge *first, UVBorderEdge *second, float angle);
|
|
|
|
/**
|
|
* Calculate a uv coordinate between the edges of the corner.
|
|
*
|
|
* 'min_uv_distance' is the minimum distance between the corner and the
|
|
* resulting uv coordinate. The distance is in uv space.
|
|
*/
|
|
float2 uv(float factor, float min_uv_distance);
|
|
|
|
/**
|
|
* Does this corner exist as 2 connected edges of the mesh.
|
|
*
|
|
* During the extraction phase a connection can be made in uv-space that
|
|
* doesn't reflect to two connected edges inside the mesh.
|
|
*/
|
|
bool connected_in_mesh() const;
|
|
void print_debug() const;
|
|
};
|
|
|
|
struct UVBorder {
|
|
/** Ordered list of UV Verts of the border of this island. */
|
|
Vector<UVBorderEdge> edges;
|
|
|
|
/**
|
|
* Check if the border is counter clock wise from its island.
|
|
*/
|
|
bool is_ccw() const;
|
|
|
|
/**
|
|
* Flip the order of the verts, changing the order between CW and CCW.
|
|
*/
|
|
void flip_order();
|
|
|
|
/**
|
|
* Calculate the outside angle of the given vert.
|
|
*/
|
|
float outside_angle(const UVBorderEdge &edge) const;
|
|
|
|
void update_indexes(uint64_t border_index);
|
|
|
|
static std::optional<UVBorder> extract_from_edges(Vector<UVBorderEdge> &edges);
|
|
|
|
/** Remove edge from the border. updates the indexes. */
|
|
void remove(int64_t index);
|
|
};
|
|
|
|
struct UVIsland {
|
|
/**
|
|
* Id (Index) of the UVIsland. Contains the index of this island in UVIslands.
|
|
*
|
|
* Useful during debugging to set a breaking condition on a specific island/vert.
|
|
*/
|
|
int id;
|
|
VectorList<UVVertex> uv_vertices;
|
|
VectorList<UVEdge> uv_edges;
|
|
VectorList<UVPrimitive> uv_primitives;
|
|
/**
|
|
* List of borders of this island. There can be multiple borders per island as a border could
|
|
* be completely encapsulated by another one.
|
|
*/
|
|
Vector<UVBorder> borders;
|
|
|
|
/**
|
|
* Key is mesh vert index, Value is list of UVVertices that refer to the mesh vertex with that
|
|
* index. Map is used internally to quickly lookup similar UVVertices.
|
|
*/
|
|
Map<int64_t, Vector<UVVertex *>> uv_vertex_lookup;
|
|
|
|
UVVertex *lookup(const UVVertex &vertex);
|
|
UVVertex *lookup_or_create(const UVVertex &vertex);
|
|
UVEdge *lookup(const UVEdge &edge);
|
|
UVEdge *lookup_or_create(const UVEdge &edge);
|
|
|
|
/** Initialize the border attribute. */
|
|
void extract_borders();
|
|
/** Iterative extend border to fit the mask. */
|
|
void extend_border(const MeshData &mesh_data,
|
|
const UVIslandsMask &mask,
|
|
const short island_index);
|
|
|
|
private:
|
|
void append(const UVPrimitive &primitive);
|
|
|
|
public:
|
|
bool has_shared_edge(const UVPrimitive &primitive) const;
|
|
bool has_shared_edge(const MeshData &mesh_data, const int primitive_i) const;
|
|
void extend_border(const UVPrimitive &primitive);
|
|
|
|
/** Print a python script to the console that generates a mesh representing this UVIsland. */
|
|
void print_debug(const MeshData &mesh_data) const;
|
|
};
|
|
|
|
struct UVIslands {
|
|
Vector<UVIsland> islands;
|
|
|
|
explicit UVIslands(const MeshData &mesh_data);
|
|
|
|
void extract_borders();
|
|
void extend_borders(const MeshData &mesh_data, const UVIslandsMask &islands_mask);
|
|
void print_debug(const MeshData &mesh_data) const;
|
|
};
|
|
|
|
/** Mask to find the index of the UVIsland for a given UV coordinate. */
|
|
struct UVIslandsMask {
|
|
|
|
/** Mask for each udim tile. */
|
|
struct Tile {
|
|
float2 udim_offset;
|
|
ushort2 tile_resolution;
|
|
ushort2 mask_resolution;
|
|
Array<uint16_t> mask;
|
|
|
|
Tile(float2 udim_offset, ushort2 tile_resolution);
|
|
|
|
bool is_masked(const uint16_t island_index, const float2 uv) const;
|
|
bool contains(const float2 uv) const;
|
|
float get_pixel_size_in_uv_space() const;
|
|
};
|
|
|
|
Vector<Tile> tiles;
|
|
|
|
void add_tile(float2 udim_offset, ushort2 resolution);
|
|
|
|
/**
|
|
* Find a tile containing the given uv coordinate.
|
|
*/
|
|
const Tile *find_tile(const float2 uv) const;
|
|
|
|
/**
|
|
* Is the given uv coordinate part of the given island_index mask.
|
|
*
|
|
* true - part of the island mask.
|
|
* false - not part of the island mask.
|
|
*/
|
|
bool is_masked(const uint16_t island_index, const float2 uv) const;
|
|
|
|
/**
|
|
* Add the given UVIslands to the mask. Tiles should be added beforehand using the 'add_tile'
|
|
* method.
|
|
*/
|
|
void add(const MeshData &mesh_data, const UVIslands &islands);
|
|
|
|
void dilate(int max_iterations);
|
|
};
|
|
|
|
} // namespace blender::bke::pbvh::uv_islands
|