forked from blender/blender
main sync #3
@ -9,8 +9,11 @@
|
|||||||
|
|
||||||
#include "DNA_image_types.h"
|
#include "DNA_image_types.h"
|
||||||
|
|
||||||
|
#include "BLI_math.h"
|
||||||
#include "BLI_math_vector_types.hh"
|
#include "BLI_math_vector_types.hh"
|
||||||
|
|
||||||
|
#include "IMB_imbuf_types.h"
|
||||||
|
|
||||||
namespace blender::bke::image {
|
namespace blender::bke::image {
|
||||||
|
|
||||||
/** Type to use for UDIM tile numbers (1001). */
|
/** Type to use for UDIM tile numbers (1001). */
|
||||||
@ -44,4 +47,44 @@ struct ImageTileWrapper {
|
|||||||
return (tile_number - 1001) / 10;
|
return (tile_number - 1001) / 10;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename T, int Channels = 4> struct ImageBufferAccessor {
|
||||||
|
static_assert(std::is_same_v<T, int> || std::is_same_v<T, float4>);
|
||||||
|
|
||||||
|
ImBuf &image_buffer;
|
||||||
|
|
||||||
|
ImageBufferAccessor(ImBuf &image_buffer) : image_buffer(image_buffer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 read_pixel(const int2 coordinate)
|
||||||
|
{
|
||||||
|
if constexpr ((std::is_same_v<T, float4>)) {
|
||||||
|
int offset = (coordinate.y * image_buffer.x + coordinate.x) * Channels;
|
||||||
|
return float4(&image_buffer.rect_float[offset]);
|
||||||
|
}
|
||||||
|
if constexpr ((std::is_same_v<T, int>)) {
|
||||||
|
int offset = (coordinate.y * image_buffer.x + coordinate.x);
|
||||||
|
float4 result;
|
||||||
|
rgba_uchar_to_float(result,
|
||||||
|
static_cast<uchar *>(static_cast<void *>(&image_buffer.rect[offset])));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return float4();
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_pixel(const int2 coordinate, float4 new_value)
|
||||||
|
{
|
||||||
|
if constexpr ((std::is_same_v<T, float>)) {
|
||||||
|
int offset = (coordinate.y * image_buffer.x + coordinate.x) * Channels;
|
||||||
|
copy_v4_v4(&image_buffer.rect_float[offset], new_value);
|
||||||
|
}
|
||||||
|
if constexpr ((std::is_same_v<T, int>)) {
|
||||||
|
int offset = (coordinate.y * image_buffer.x + coordinate.x);
|
||||||
|
rgba_float_to_uchar(static_cast<uchar *>(static_cast<void *>(&image_buffer.rect[offset])),
|
||||||
|
new_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace blender::bke::image
|
} // namespace blender::bke::image
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include "BLI_math.h"
|
#include "BLI_math.h"
|
||||||
#include "BLI_math_vector_types.hh"
|
#include "BLI_math_vector.hh"
|
||||||
#include "BLI_rect.h"
|
#include "BLI_rect.h"
|
||||||
#include "BLI_vector.hh"
|
#include "BLI_vector.hh"
|
||||||
|
|
||||||
@ -233,6 +235,15 @@ struct NodeData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void collect_dirty_tiles(Vector<image::TileNumber> &r_dirty_tiles)
|
||||||
|
{
|
||||||
|
for (UDIMTilePixels &tile : tiles) {
|
||||||
|
if (tile.flags.dirty) {
|
||||||
|
r_dirty_tiles.append_non_duplicates(tile.tile_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void clear_data()
|
void clear_data()
|
||||||
{
|
{
|
||||||
tiles.clear();
|
tiles.clear();
|
||||||
@ -245,11 +256,166 @@ struct NodeData {
|
|||||||
MEM_delete(node_data);
|
MEM_delete(node_data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/** \name Fix non-manifold edge bleeding.
|
||||||
|
* \{ */
|
||||||
|
|
||||||
|
struct DeltaCopyPixelCommand {
|
||||||
|
char2 delta_source_1;
|
||||||
|
char2 delta_source_2;
|
||||||
|
uint8_t mix_factor;
|
||||||
|
|
||||||
|
DeltaCopyPixelCommand(char2 delta_source_1, char2 delta_source_2, uint8_t mix_factor)
|
||||||
|
: delta_source_1(delta_source_1), delta_source_2(delta_source_2), mix_factor(mix_factor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CopyPixelGroup {
|
||||||
|
int2 start_destination;
|
||||||
|
int2 start_source_1;
|
||||||
|
int64_t start_delta_index;
|
||||||
|
int num_deltas;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Pixel copy command to mix 2 source pixels and write to a destination pixel. */
|
||||||
|
struct CopyPixelCommand {
|
||||||
|
/** Pixel coordinate to write to. */
|
||||||
|
int2 destination;
|
||||||
|
/** Pixel coordinate to read first source from. */
|
||||||
|
int2 source_1;
|
||||||
|
/** Pixel coordinate to read second source from. */
|
||||||
|
int2 source_2;
|
||||||
|
/** Factor to mix between first and second source. */
|
||||||
|
float mix_factor;
|
||||||
|
|
||||||
|
CopyPixelCommand() = default;
|
||||||
|
CopyPixelCommand(const CopyPixelGroup &group)
|
||||||
|
: destination(group.start_destination),
|
||||||
|
source_1(group.start_source_1),
|
||||||
|
source_2(),
|
||||||
|
mix_factor(0.0f)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void mix_source_and_write_destination(image::ImageBufferAccessor<T> &tile_buffer) const
|
||||||
|
{
|
||||||
|
float4 source_color_1 = tile_buffer.read_pixel(source_1);
|
||||||
|
float4 source_color_2 = tile_buffer.read_pixel(source_2);
|
||||||
|
float4 destination_color = source_color_1 * (1.0f - mix_factor) + source_color_2 * mix_factor;
|
||||||
|
tile_buffer.write_pixel(destination, destination_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply(const DeltaCopyPixelCommand &item)
|
||||||
|
{
|
||||||
|
destination.x += 1;
|
||||||
|
source_1 += int2(item.delta_source_1);
|
||||||
|
source_2 = source_1 + int2(item.delta_source_2);
|
||||||
|
mix_factor = float(item.mix_factor) / 255.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeltaCopyPixelCommand encode_delta(const CopyPixelCommand &next_command) const
|
||||||
|
{
|
||||||
|
return DeltaCopyPixelCommand(char2(next_command.source_1 - source_1),
|
||||||
|
char2(next_command.source_2 - next_command.source_1),
|
||||||
|
uint8_t(next_command.mix_factor * 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_be_extended(const CopyPixelCommand &command) const
|
||||||
|
{
|
||||||
|
/* Can only extend sequential pixels. */
|
||||||
|
if (destination.x != command.destination.x - 1 || destination.y != command.destination.y) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Can only extend when the delta between with the previous source fits in a single byte.*/
|
||||||
|
int2 delta_source_1 = source_1 - command.source_1;
|
||||||
|
if (max_ii(UNPACK2(blender::math::abs(delta_source_1))) > 127) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CopyPixelTile {
|
||||||
|
image::TileNumber tile_number;
|
||||||
|
Vector<CopyPixelGroup> groups;
|
||||||
|
Vector<DeltaCopyPixelCommand> command_deltas;
|
||||||
|
|
||||||
|
CopyPixelTile(image::TileNumber tile_number) : tile_number(tile_number)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_pixels(ImBuf &tile_buffer, IndexRange group_range) const
|
||||||
|
{
|
||||||
|
if (tile_buffer.rect_float) {
|
||||||
|
image::ImageBufferAccessor<float4> accessor(tile_buffer);
|
||||||
|
copy_pixels<float4>(accessor, group_range);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
image::ImageBufferAccessor<int> accessor(tile_buffer);
|
||||||
|
copy_pixels<int>(accessor, group_range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_compression_rate()
|
||||||
|
{
|
||||||
|
int decoded_size = command_deltas.size() * sizeof(CopyPixelCommand);
|
||||||
|
int encoded_size = groups.size() * sizeof(CopyPixelGroup) +
|
||||||
|
command_deltas.size() * sizeof(DeltaCopyPixelCommand);
|
||||||
|
printf("Tile %d compression rate: %d->%d = %d%%\n",
|
||||||
|
tile_number,
|
||||||
|
decoded_size,
|
||||||
|
encoded_size,
|
||||||
|
int(100.0 * float(encoded_size) / float(decoded_size)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<typename T>
|
||||||
|
void copy_pixels(image::ImageBufferAccessor<T> &image_buffer, IndexRange group_range) const
|
||||||
|
{
|
||||||
|
for (const int64_t group_index : group_range) {
|
||||||
|
const CopyPixelGroup &group = groups[group_index];
|
||||||
|
CopyPixelCommand copy_command(group);
|
||||||
|
for (const DeltaCopyPixelCommand &item : Span<const DeltaCopyPixelCommand>(
|
||||||
|
&command_deltas[group.start_delta_index], group.num_deltas)) {
|
||||||
|
copy_command.apply(item);
|
||||||
|
copy_command.mix_source_and_write_destination<T>(image_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CopyPixelTiles {
|
||||||
|
Vector<CopyPixelTile> tiles;
|
||||||
|
|
||||||
|
std::optional<std::reference_wrapper<CopyPixelTile>> find_tile(image::TileNumber tile_number)
|
||||||
|
{
|
||||||
|
for (CopyPixelTile &tile : tiles) {
|
||||||
|
if (tile.tile_number == tile_number) {
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
tiles.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** \} */
|
||||||
|
|
||||||
struct PBVHData {
|
struct PBVHData {
|
||||||
/* Per UVPRimitive contains the paint data. */
|
/* Per UVPRimitive contains the paint data. */
|
||||||
PaintGeometryPrimitives geom_primitives;
|
PaintGeometryPrimitives geom_primitives;
|
||||||
|
|
||||||
|
/** Per ImageTile the pixels to copy to fix non-manifold bleeding. */
|
||||||
|
CopyPixelTiles tiles_copy_pixels;
|
||||||
|
|
||||||
void clear_data()
|
void clear_data()
|
||||||
{
|
{
|
||||||
geom_primitives.clear();
|
geom_primitives.clear();
|
||||||
@ -259,5 +425,11 @@ struct PBVHData {
|
|||||||
NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node);
|
NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node);
|
||||||
void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &image_user);
|
void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &image_user);
|
||||||
PBVHData &BKE_pbvh_pixels_data_get(PBVH &pbvh);
|
PBVHData &BKE_pbvh_pixels_data_get(PBVH &pbvh);
|
||||||
|
void BKE_pbvh_pixels_collect_dirty_tiles(PBVHNode &node, Vector<image::TileNumber> &r_dirty_tiles);
|
||||||
|
|
||||||
|
void BKE_pbvh_pixels_copy_pixels(PBVH &pbvh,
|
||||||
|
Image &image,
|
||||||
|
ImageUser &image_user,
|
||||||
|
image::TileNumber tile_number);
|
||||||
|
|
||||||
} // namespace blender::bke::pbvh::pixels
|
} // namespace blender::bke::pbvh::pixels
|
||||||
|
@ -251,6 +251,7 @@ set(SRC
|
|||||||
intern/pbvh_bmesh.cc
|
intern/pbvh_bmesh.cc
|
||||||
intern/pbvh_colors.cc
|
intern/pbvh_colors.cc
|
||||||
intern/pbvh_pixels.cc
|
intern/pbvh_pixels.cc
|
||||||
|
intern/pbvh_pixels_copy.cc
|
||||||
intern/pbvh_uv_islands.cc
|
intern/pbvh_uv_islands.cc
|
||||||
intern/pointcache.c
|
intern/pointcache.c
|
||||||
intern/pointcloud.cc
|
intern/pointcloud.cc
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "bmesh.h"
|
#include "bmesh.h"
|
||||||
|
|
||||||
#include "pbvh_intern.hh"
|
#include "pbvh_intern.hh"
|
||||||
|
#include "pbvh_pixels_copy.hh"
|
||||||
#include "pbvh_uv_islands.hh"
|
#include "pbvh_uv_islands.hh"
|
||||||
|
|
||||||
namespace blender::bke::pbvh::pixels {
|
namespace blender::bke::pbvh::pixels {
|
||||||
@ -714,6 +715,9 @@ static bool update_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image
|
|||||||
apply_watertight_check(pbvh, image, image_user);
|
apply_watertight_check(pbvh, image, image_user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Add solution for non-manifold parts of the model. */
|
||||||
|
BKE_pbvh_pixels_copy_update(*pbvh, *image, *image_user, mesh_data);
|
||||||
|
|
||||||
/* Rebuild the undo regions. */
|
/* Rebuild the undo regions. */
|
||||||
for (PBVHNode *node : nodes_to_update) {
|
for (PBVHNode *node : nodes_to_update) {
|
||||||
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
|
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
|
||||||
@ -797,6 +801,13 @@ void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &i
|
|||||||
node_data->flags.dirty = false;
|
node_data->flags.dirty = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BKE_pbvh_pixels_collect_dirty_tiles(PBVHNode &node, Vector<image::TileNumber> &r_dirty_tiles)
|
||||||
|
{
|
||||||
|
NodeData *node_data = static_cast<NodeData *>(node.pixels.node_data);
|
||||||
|
node_data->collect_dirty_tiles(r_dirty_tiles);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace blender::bke::pbvh::pixels
|
} // namespace blender::bke::pbvh::pixels
|
||||||
|
|
||||||
using namespace blender::bke::pbvh::pixels;
|
using namespace blender::bke::pbvh::pixels;
|
||||||
|
576
source/blender/blenkernel/intern/pbvh_pixels_copy.cc
Normal file
576
source/blender/blenkernel/intern/pbvh_pixels_copy.cc
Normal file
@ -0,0 +1,576 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
* Copyright 2023 Blender Foundation. All rights reserved. */
|
||||||
|
|
||||||
|
#include "BLI_array.hh"
|
||||||
|
#include "BLI_bit_vector.hh"
|
||||||
|
#include "BLI_math.h"
|
||||||
|
#include "BLI_math_vector.hh"
|
||||||
|
#include "BLI_task.hh"
|
||||||
|
#include "BLI_vector.hh"
|
||||||
|
|
||||||
|
#include "IMB_imbuf.h"
|
||||||
|
#include "IMB_imbuf_types.h"
|
||||||
|
|
||||||
|
#include "BKE_image_wrappers.hh"
|
||||||
|
#include "BKE_pbvh.h"
|
||||||
|
#include "BKE_pbvh_pixels.hh"
|
||||||
|
|
||||||
|
#include "pbvh_intern.hh"
|
||||||
|
#include "pbvh_pixels_copy.hh"
|
||||||
|
#include "pbvh_uv_islands.hh"
|
||||||
|
|
||||||
|
namespace blender::bke::pbvh::pixels {
|
||||||
|
|
||||||
|
const int THREADING_GRAIN_SIZE = 128;
|
||||||
|
|
||||||
|
/** Coordinate space of a coordinate. */
|
||||||
|
enum class CoordSpace {
|
||||||
|
/**
|
||||||
|
* Coordinate is in UV coordinate space. As in unmodified from mesh data.
|
||||||
|
*/
|
||||||
|
UV,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinate is in Tile coordinate space.
|
||||||
|
*
|
||||||
|
* With tile coordinate space each unit is a single pixel of the tile.
|
||||||
|
* Range is [0..buffer width].
|
||||||
|
*/
|
||||||
|
Tile,
|
||||||
|
};
|
||||||
|
|
||||||
|
template<CoordSpace Space> struct Vertex {
|
||||||
|
float2 coordinate;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<CoordSpace Space> struct Edge {
|
||||||
|
Vertex<Space> vertex_1;
|
||||||
|
Vertex<Space> vertex_2;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Calculate the bounds of the given edge. */
|
||||||
|
static rcti get_bounds(const Edge<CoordSpace::Tile> &tile_edge)
|
||||||
|
{
|
||||||
|
rcti bounds;
|
||||||
|
BLI_rcti_init_minmax(&bounds);
|
||||||
|
BLI_rcti_do_minmax_v(&bounds, int2(tile_edge.vertex_1.coordinate));
|
||||||
|
BLI_rcti_do_minmax_v(&bounds, int2(tile_edge.vertex_2.coordinate));
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add a margin to the given bounds. */
|
||||||
|
static void add_margin(rcti &bounds, int margin)
|
||||||
|
{
|
||||||
|
bounds.xmin -= margin;
|
||||||
|
bounds.xmax += margin;
|
||||||
|
bounds.ymin -= margin;
|
||||||
|
bounds.ymax += margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clamp bounds to be between 0,0 and the given resolution. */
|
||||||
|
static void clamp(rcti &bounds, int2 resolution)
|
||||||
|
{
|
||||||
|
rcti clamping_bounds;
|
||||||
|
BLI_rcti_init(&clamping_bounds, 0, resolution.x - 1, 0, resolution.y - 1);
|
||||||
|
BLI_rcti_isect(&bounds, &clamping_bounds, &bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Vertex<CoordSpace::Tile> convert_coord_space(const Vertex<CoordSpace::UV> &uv_vertex,
|
||||||
|
const image::ImageTileWrapper image_tile,
|
||||||
|
const int2 tile_resolution)
|
||||||
|
{
|
||||||
|
return Vertex<CoordSpace::Tile>{(uv_vertex.coordinate - float2(image_tile.get_tile_offset())) *
|
||||||
|
float2(tile_resolution)};
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Edge<CoordSpace::Tile> convert_coord_space(const Edge<CoordSpace::UV> &uv_edge,
|
||||||
|
const image::ImageTileWrapper image_tile,
|
||||||
|
const int2 tile_resolution)
|
||||||
|
{
|
||||||
|
return Edge<CoordSpace::Tile>{
|
||||||
|
convert_coord_space(uv_edge.vertex_1, image_tile, tile_resolution),
|
||||||
|
convert_coord_space(uv_edge.vertex_2, image_tile, tile_resolution),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonManifoldTileEdges : public Vector<Edge<CoordSpace::Tile>> {};
|
||||||
|
|
||||||
|
class NonManifoldUVEdges : public Vector<Edge<CoordSpace::UV>> {
|
||||||
|
public:
|
||||||
|
NonManifoldUVEdges(const uv_islands::MeshData &mesh_data)
|
||||||
|
{
|
||||||
|
int num_non_manifold_edges = count_non_manifold_edges(mesh_data);
|
||||||
|
reserve(num_non_manifold_edges);
|
||||||
|
for (const int primitive_id : mesh_data.looptris.index_range()) {
|
||||||
|
for (const int edge_id : mesh_data.primitive_to_edge_map[primitive_id]) {
|
||||||
|
if (is_manifold(mesh_data, edge_id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const MLoopTri &loop_tri = mesh_data.looptris[primitive_id];
|
||||||
|
const uv_islands::MeshEdge &mesh_edge = mesh_data.edges[edge_id];
|
||||||
|
Edge<CoordSpace::UV> edge;
|
||||||
|
|
||||||
|
edge.vertex_1.coordinate = find_uv(mesh_data, loop_tri, mesh_edge.vert1);
|
||||||
|
edge.vertex_2.coordinate = find_uv(mesh_data, loop_tri, mesh_edge.vert2);
|
||||||
|
append(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BLI_assert_msg(size() == num_non_manifold_edges,
|
||||||
|
"Incorrect number of non manifold edges added. ");
|
||||||
|
}
|
||||||
|
|
||||||
|
NonManifoldTileEdges extract_tile_edges(const image::ImageTileWrapper image_tile,
|
||||||
|
const int2 tile_resolution) const
|
||||||
|
{
|
||||||
|
NonManifoldTileEdges result;
|
||||||
|
for (const Edge<CoordSpace::UV> &uv_edge : *this) {
|
||||||
|
const Edge<CoordSpace::Tile> tile_edge = convert_coord_space(
|
||||||
|
uv_edge, image_tile, tile_resolution);
|
||||||
|
result.append(tile_edge);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static int64_t count_non_manifold_edges(const uv_islands::MeshData &mesh_data)
|
||||||
|
{
|
||||||
|
int64_t result = 0;
|
||||||
|
for (const int primitive_id : mesh_data.looptris.index_range()) {
|
||||||
|
for (const int edge_id : mesh_data.primitive_to_edge_map[primitive_id]) {
|
||||||
|
if (is_manifold(mesh_data, edge_id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_manifold(const uv_islands::MeshData &mesh_data, const int edge_id)
|
||||||
|
{
|
||||||
|
return mesh_data.edge_to_primitive_map[edge_id].size() == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float2 find_uv(const uv_islands::MeshData &mesh_data,
|
||||||
|
const MLoopTri &loop_tri,
|
||||||
|
int vertex_i)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
int loop_i = loop_tri.tri[i];
|
||||||
|
const MLoop &loop = mesh_data.loops[loop_i];
|
||||||
|
if (loop.v == vertex_i) {
|
||||||
|
return mesh_data.uv_map[loop_i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BLI_assert_unreachable();
|
||||||
|
return float2(0.0f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class PixelNodesTileData : public Vector<std::reference_wrapper<UDIMTilePixels>> {
|
||||||
|
public:
|
||||||
|
PixelNodesTileData(PBVH &pbvh, const image::ImageTileWrapper &image_tile)
|
||||||
|
{
|
||||||
|
reserve(count_nodes(pbvh, image_tile));
|
||||||
|
|
||||||
|
for (PBVHNode &node : MutableSpan(pbvh.nodes, pbvh.totnode)) {
|
||||||
|
if (should_add_node(node, image_tile)) {
|
||||||
|
NodeData &node_data = *static_cast<NodeData *>(node.pixels.node_data);
|
||||||
|
UDIMTilePixels &tile_pixels = *node_data.find_tile_data(image_tile);
|
||||||
|
append(tile_pixels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool should_add_node(PBVHNode &node, const image::ImageTileWrapper &image_tile)
|
||||||
|
{
|
||||||
|
if ((node.flag & PBVH_Leaf) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (node.pixels.node_data == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NodeData &node_data = *static_cast<NodeData *>(node.pixels.node_data);
|
||||||
|
if (node_data.find_tile_data(image_tile) == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t count_nodes(PBVH &pbvh, const image::ImageTileWrapper &image_tile)
|
||||||
|
{
|
||||||
|
int64_t result = 0;
|
||||||
|
for (PBVHNode &node : MutableSpan(pbvh.nodes, pbvh.totnode)) {
|
||||||
|
if (should_add_node(node, image_tile)) {
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Row contains intermediate data per pixel for a single image row. It is used during updating to
|
||||||
|
* encode pixels.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct Rows {
|
||||||
|
enum class PixelType {
|
||||||
|
Undecided,
|
||||||
|
/** This pixel is directly affected by a brush and doesn't need to be solved. */
|
||||||
|
Brush,
|
||||||
|
SelectedForCloserExamination,
|
||||||
|
/** This pixel will be copid from another pixel to solve non-manifold edge bleeding. */
|
||||||
|
CopyFromClosestEdge,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Pixel {
|
||||||
|
PixelType type;
|
||||||
|
float distance;
|
||||||
|
CopyPixelCommand copy_command;
|
||||||
|
/**
|
||||||
|
* Index of the edge in the list of non-manifold edges.
|
||||||
|
*
|
||||||
|
* The edge is kept to calculate athe mix factor between the two pixels that have chosen to
|
||||||
|
* be mixed.
|
||||||
|
*/
|
||||||
|
int64_t edge_index;
|
||||||
|
|
||||||
|
Pixel() = default;
|
||||||
|
|
||||||
|
void init(int2 coordinate)
|
||||||
|
{
|
||||||
|
copy_command.destination = coordinate;
|
||||||
|
copy_command.source_1 = coordinate;
|
||||||
|
copy_command.source_2 = coordinate;
|
||||||
|
copy_command.mix_factor = 0.0f;
|
||||||
|
type = PixelType::Undecided;
|
||||||
|
distance = std::numeric_limits<float>::max();
|
||||||
|
edge_index = -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int2 resolution;
|
||||||
|
int margin;
|
||||||
|
Array<Pixel> pixels;
|
||||||
|
|
||||||
|
struct RowView {
|
||||||
|
int row_number = 0;
|
||||||
|
/** Not owning pointer into Row.pixels starts at the start of the row.*/
|
||||||
|
MutableSpan<Pixel> pixels;
|
||||||
|
RowView() = delete;
|
||||||
|
RowView(Rows &rows, int64_t row_number)
|
||||||
|
: row_number(row_number),
|
||||||
|
pixels(
|
||||||
|
MutableSpan<Pixel>(&rows.pixels[row_number * rows.resolution.x], rows.resolution.x))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Rows(int2 resolution, int margin)
|
||||||
|
: resolution(resolution), margin(margin), pixels(resolution.x * resolution.y)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_pixels()
|
||||||
|
{
|
||||||
|
int64_t index = 0;
|
||||||
|
for (int y : IndexRange(resolution.y)) {
|
||||||
|
for (int64_t x : IndexRange(resolution.x)) {
|
||||||
|
int2 position(x, y);
|
||||||
|
pixels[index++].init(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark pixels that are painted on by the brush. Those pixels don't need to be updated, but will
|
||||||
|
* act as a source for other pixels.
|
||||||
|
*/
|
||||||
|
void mark_pixels_effected_by_brush(const PixelNodesTileData &nodes_tile_pixels)
|
||||||
|
{
|
||||||
|
for (const UDIMTilePixels &tile_pixels : nodes_tile_pixels) {
|
||||||
|
threading::parallel_for_each(
|
||||||
|
tile_pixels.pixel_rows, [&](const PackedPixelRow &encoded_pixels) {
|
||||||
|
for (int x = encoded_pixels.start_image_coordinate.x;
|
||||||
|
x < encoded_pixels.start_image_coordinate.x + encoded_pixels.num_pixels;
|
||||||
|
x++) {
|
||||||
|
int64_t index = encoded_pixels.start_image_coordinate.y * resolution.x + x;
|
||||||
|
pixels[index].type = PixelType::Brush;
|
||||||
|
pixels[index].distance = 0.0f;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look for a second source pixel that will be blended with the first source pixel to improve
|
||||||
|
* the quality of the fix.
|
||||||
|
*
|
||||||
|
* - The second source pixel must be a neighbour pixel of the first source, or the same as the
|
||||||
|
* first source when no second pixel could be found.
|
||||||
|
* - The second source pixel must be a pixel that is painted on by the brush.
|
||||||
|
* - The second source pixel must be the second closest pixel , or the first source
|
||||||
|
* when no second pixel could be found.
|
||||||
|
*/
|
||||||
|
int2 find_second_source(int2 destination, int2 first_source)
|
||||||
|
{
|
||||||
|
rcti search_bounds;
|
||||||
|
BLI_rcti_init(&search_bounds,
|
||||||
|
max_ii(first_source.x - 1, 0),
|
||||||
|
min_ii(first_source.x + 1, resolution.x - 1),
|
||||||
|
max_ii(first_source.y - 1, 0),
|
||||||
|
min_ii(first_source.y + 1, resolution.y - 1));
|
||||||
|
/* Initialize to the first source, so when no other source could be found it will use the
|
||||||
|
* first_source. */
|
||||||
|
int2 found_source = first_source;
|
||||||
|
float found_distance = std::numeric_limits<float>().max();
|
||||||
|
for (int sy : IndexRange(search_bounds.ymin, BLI_rcti_size_y(&search_bounds) + 1)) {
|
||||||
|
for (int sx : IndexRange(search_bounds.xmin, BLI_rcti_size_x(&search_bounds) + 1)) {
|
||||||
|
int2 source(sx, sy);
|
||||||
|
/* Skip first source as it should be the closest and already selected. */
|
||||||
|
if (source == first_source) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int pixel_index = sy * resolution.y + sx;
|
||||||
|
if (pixels[pixel_index].type != PixelType::Brush) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float new_distance = blender::math::distance(float2(destination), float2(source));
|
||||||
|
if (new_distance < found_distance) {
|
||||||
|
found_distance = new_distance;
|
||||||
|
found_source = source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found_source;
|
||||||
|
}
|
||||||
|
|
||||||
|
float determine_mix_factor(const int2 destination,
|
||||||
|
const int2 source_1,
|
||||||
|
const int2 source_2,
|
||||||
|
const Edge<CoordSpace::Tile> &edge)
|
||||||
|
{
|
||||||
|
/* Use stable result when both sources are the same. */
|
||||||
|
if (source_1 == source_2) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float2 clamped_to_edge;
|
||||||
|
float destination_lambda = closest_to_line_v2(
|
||||||
|
clamped_to_edge, float2(destination), edge.vertex_1.coordinate, edge.vertex_2.coordinate);
|
||||||
|
float source_1_lambda = closest_to_line_v2(
|
||||||
|
clamped_to_edge, float2(source_1), edge.vertex_1.coordinate, edge.vertex_2.coordinate);
|
||||||
|
float source_2_lambda = closest_to_line_v2(
|
||||||
|
clamped_to_edge, float2(source_2), edge.vertex_1.coordinate, edge.vertex_2.coordinate);
|
||||||
|
|
||||||
|
return clamp_f(
|
||||||
|
(destination_lambda - source_1_lambda) / (source_2_lambda - source_1_lambda), 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void find_copy_source(Pixel &pixel, const NonManifoldTileEdges &tile_edges)
|
||||||
|
{
|
||||||
|
BLI_assert(pixel.type == PixelType::SelectedForCloserExamination);
|
||||||
|
|
||||||
|
rcti bounds;
|
||||||
|
BLI_rcti_init(&bounds,
|
||||||
|
pixel.copy_command.destination.x,
|
||||||
|
pixel.copy_command.destination.x,
|
||||||
|
pixel.copy_command.destination.y,
|
||||||
|
pixel.copy_command.destination.y);
|
||||||
|
add_margin(bounds, margin);
|
||||||
|
clamp(bounds, resolution);
|
||||||
|
|
||||||
|
float found_distance = std::numeric_limits<float>().max();
|
||||||
|
int2 found_source(0);
|
||||||
|
|
||||||
|
for (int sy : IndexRange(bounds.ymin, BLI_rcti_size_y(&bounds))) {
|
||||||
|
int pixel_index = sy * resolution.x;
|
||||||
|
for (int sx : IndexRange(bounds.xmin, BLI_rcti_size_x(&bounds))) {
|
||||||
|
Pixel &source = pixels[pixel_index + sx];
|
||||||
|
if (source.type != PixelType::Brush) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float new_distance = blender::math::distance(float2(sx, sy),
|
||||||
|
float2(pixel.copy_command.destination));
|
||||||
|
if (new_distance < found_distance) {
|
||||||
|
found_source = int2(sx, sy);
|
||||||
|
found_distance = new_distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found_distance == std::numeric_limits<float>().max()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pixel.type = PixelType::CopyFromClosestEdge;
|
||||||
|
pixel.distance = found_distance;
|
||||||
|
pixel.copy_command.source_1 = found_source;
|
||||||
|
pixel.copy_command.source_2 = find_second_source(pixel.copy_command.destination, found_source);
|
||||||
|
pixel.copy_command.mix_factor = determine_mix_factor(pixel.copy_command.destination,
|
||||||
|
pixel.copy_command.source_1,
|
||||||
|
pixel.copy_command.source_2,
|
||||||
|
tile_edges[pixel.edge_index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void find_copy_source(Vector<std::reference_wrapper<Pixel>> &selected_pixels,
|
||||||
|
const NonManifoldTileEdges &tile_edges)
|
||||||
|
{
|
||||||
|
threading::parallel_for(
|
||||||
|
IndexRange(selected_pixels.size()), THREADING_GRAIN_SIZE, [&](IndexRange range) {
|
||||||
|
for (int selected_pixel_index : range) {
|
||||||
|
Pixel ¤t_pixel = selected_pixels[selected_pixel_index];
|
||||||
|
find_copy_source(current_pixel, tile_edges);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<std::reference_wrapper<Pixel>> filter_pixels_for_closer_examination(
|
||||||
|
const NonManifoldTileEdges &tile_edges)
|
||||||
|
{
|
||||||
|
Vector<std::reference_wrapper<Pixel>> selected_pixels;
|
||||||
|
selected_pixels.reserve(10000);
|
||||||
|
|
||||||
|
for (int tile_edge_index : tile_edges.index_range()) {
|
||||||
|
const Edge<CoordSpace::Tile> &tile_edge = tile_edges[tile_edge_index];
|
||||||
|
rcti edge_bounds = get_bounds(tile_edge);
|
||||||
|
add_margin(edge_bounds, margin);
|
||||||
|
clamp(edge_bounds, resolution);
|
||||||
|
|
||||||
|
for (const int64_t sy : IndexRange(edge_bounds.ymin, BLI_rcti_size_y(&edge_bounds))) {
|
||||||
|
for (const int64_t sx : IndexRange(edge_bounds.xmin, BLI_rcti_size_x(&edge_bounds))) {
|
||||||
|
const int64_t index = sy * resolution.x + sx;
|
||||||
|
Pixel &pixel = pixels[index];
|
||||||
|
if (pixel.type == PixelType::Brush) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BLI_assert_msg(pixel.type != PixelType::CopyFromClosestEdge,
|
||||||
|
"PixelType::CopyFromClosestEdge isn't allowed to be set as it is set "
|
||||||
|
"when finding the pixels to copy.");
|
||||||
|
|
||||||
|
const float2 point(sx, sy);
|
||||||
|
float2 closest_edge_point;
|
||||||
|
closest_to_line_segment_v2(closest_edge_point,
|
||||||
|
point,
|
||||||
|
tile_edge.vertex_1.coordinate,
|
||||||
|
tile_edge.vertex_2.coordinate);
|
||||||
|
float distance_to_edge = blender::math::distance(closest_edge_point, point);
|
||||||
|
if (distance_to_edge < margin && distance_to_edge < pixel.distance) {
|
||||||
|
if (pixel.type != PixelType::SelectedForCloserExamination) {
|
||||||
|
selected_pixels.append(std::reference_wrapper<Pixel>(pixel));
|
||||||
|
}
|
||||||
|
pixel.type = PixelType::SelectedForCloserExamination;
|
||||||
|
pixel.distance = distance_to_edge;
|
||||||
|
pixel.edge_index = tile_edge_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selected_pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pack_into(const Vector<std::reference_wrapper<Pixel>> &selected_pixels,
|
||||||
|
CopyPixelTile ©_tile) const
|
||||||
|
{
|
||||||
|
std::optional<std::reference_wrapper<CopyPixelGroup>> last_group = std::nullopt;
|
||||||
|
std::optional<CopyPixelCommand> last_command = std::nullopt;
|
||||||
|
|
||||||
|
for (const Pixel &elem : selected_pixels) {
|
||||||
|
if (elem.type == PixelType::CopyFromClosestEdge) {
|
||||||
|
if (!last_command.has_value() || !last_command->can_be_extended(elem.copy_command)) {
|
||||||
|
CopyPixelGroup new_group = {elem.copy_command.destination - int2(1, 0),
|
||||||
|
elem.copy_command.source_1,
|
||||||
|
copy_tile.command_deltas.size(),
|
||||||
|
0};
|
||||||
|
copy_tile.groups.append(new_group);
|
||||||
|
last_group = copy_tile.groups.last();
|
||||||
|
last_command = CopyPixelCommand(*last_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeltaCopyPixelCommand delta_command = last_command->encode_delta(elem.copy_command);
|
||||||
|
copy_tile.command_deltas.append(delta_command);
|
||||||
|
last_group->get().num_deltas++;
|
||||||
|
last_command = elem.copy_command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}; // namespace blender::bke::pbvh::pixels
|
||||||
|
|
||||||
|
void BKE_pbvh_pixels_copy_update(PBVH &pbvh,
|
||||||
|
Image &image,
|
||||||
|
ImageUser &image_user,
|
||||||
|
const uv_islands::MeshData &mesh_data)
|
||||||
|
{
|
||||||
|
PBVHData &pbvh_data = BKE_pbvh_pixels_data_get(pbvh);
|
||||||
|
pbvh_data.tiles_copy_pixels.clear();
|
||||||
|
const NonManifoldUVEdges non_manifold_edges(mesh_data);
|
||||||
|
if (non_manifold_edges.is_empty()) {
|
||||||
|
/* Early exit: No non manifold edges detected. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageUser tile_user = image_user;
|
||||||
|
LISTBASE_FOREACH (ImageTile *, tile, &image.tiles) {
|
||||||
|
const image::ImageTileWrapper image_tile = image::ImageTileWrapper(tile);
|
||||||
|
tile_user.tile = image_tile.get_tile_number();
|
||||||
|
|
||||||
|
ImBuf *tile_buffer = BKE_image_acquire_ibuf(&image, &tile_user, nullptr);
|
||||||
|
if (tile_buffer == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const PixelNodesTileData nodes_tile_pixels(pbvh, image_tile);
|
||||||
|
|
||||||
|
int2 tile_resolution(tile_buffer->x, tile_buffer->y);
|
||||||
|
BKE_image_release_ibuf(&image, tile_buffer, nullptr);
|
||||||
|
|
||||||
|
NonManifoldTileEdges tile_edges = non_manifold_edges.extract_tile_edges(image_tile,
|
||||||
|
tile_resolution);
|
||||||
|
CopyPixelTile copy_tile(image_tile.get_tile_number());
|
||||||
|
|
||||||
|
Rows rows(tile_resolution, image.seam_margin);
|
||||||
|
rows.init_pixels();
|
||||||
|
rows.mark_pixels_effected_by_brush(nodes_tile_pixels);
|
||||||
|
|
||||||
|
Vector<std::reference_wrapper<Rows::Pixel>> selected_pixels =
|
||||||
|
rows.filter_pixels_for_closer_examination(tile_edges);
|
||||||
|
rows.find_copy_source(selected_pixels, tile_edges);
|
||||||
|
rows.pack_into(selected_pixels, copy_tile);
|
||||||
|
|
||||||
|
copy_tile.print_compression_rate();
|
||||||
|
pbvh_data.tiles_copy_pixels.tiles.append(copy_tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BKE_pbvh_pixels_copy_pixels(PBVH &pbvh,
|
||||||
|
Image &image,
|
||||||
|
ImageUser &image_user,
|
||||||
|
image::TileNumber tile_number)
|
||||||
|
{
|
||||||
|
PBVHData &pbvh_data = BKE_pbvh_pixels_data_get(pbvh);
|
||||||
|
std::optional<std::reference_wrapper<CopyPixelTile>> pixel_tile =
|
||||||
|
pbvh_data.tiles_copy_pixels.find_tile(tile_number);
|
||||||
|
if (!pixel_tile.has_value()) {
|
||||||
|
/* No pixels need to be copied. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageUser tile_user = image_user;
|
||||||
|
tile_user.tile = tile_number;
|
||||||
|
ImBuf *tile_buffer = BKE_image_acquire_ibuf(&image, &tile_user, nullptr);
|
||||||
|
if (tile_buffer == nullptr) {
|
||||||
|
/* No tile buffer found to copy. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyPixelTile &tile = pixel_tile->get();
|
||||||
|
threading::parallel_for(tile.groups.index_range(), THREADING_GRAIN_SIZE, [&](IndexRange range) {
|
||||||
|
tile.copy_pixels(*tile_buffer, range);
|
||||||
|
});
|
||||||
|
|
||||||
|
BKE_image_release_ibuf(&image, tile_buffer, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::bke::pbvh::pixels
|
24
source/blender/blenkernel/intern/pbvh_pixels_copy.hh
Normal file
24
source/blender/blenkernel/intern/pbvh_pixels_copy.hh
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
* Copyright 2023 Blender Foundation. All rights reserved. */
|
||||||
|
|
||||||
|
#include "BLI_math.h"
|
||||||
|
#include "BLI_math_vector_types.hh"
|
||||||
|
#include "BLI_vector.hh"
|
||||||
|
|
||||||
|
#include "IMB_imbuf.h"
|
||||||
|
#include "IMB_imbuf_types.h"
|
||||||
|
|
||||||
|
#include "BKE_image_wrappers.hh"
|
||||||
|
#include "BKE_pbvh.h"
|
||||||
|
#include "BKE_pbvh_pixels.hh"
|
||||||
|
|
||||||
|
#include "pbvh_uv_islands.hh"
|
||||||
|
|
||||||
|
namespace blender::bke::pbvh::pixels {
|
||||||
|
|
||||||
|
void BKE_pbvh_pixels_copy_update(PBVH &pbvh,
|
||||||
|
Image &image,
|
||||||
|
ImageUser &image_user,
|
||||||
|
const uv_islands::MeshData &mesh_data);
|
||||||
|
|
||||||
|
} // namespace blender::bke::pbvh::pixels
|
@ -645,6 +645,7 @@ template<typename T> struct AssertUnitEpsilon {
|
|||||||
|
|
||||||
} // namespace math
|
} // namespace math
|
||||||
|
|
||||||
|
using char2 = blender::VecBase<int8_t, 2>;
|
||||||
using char3 = blender::VecBase<int8_t, 3>;
|
using char3 = blender::VecBase<int8_t, 3>;
|
||||||
|
|
||||||
using uchar3 = blender::VecBase<uint8_t, 3>;
|
using uchar3 = blender::VecBase<uint8_t, 3>;
|
||||||
|
@ -511,6 +511,39 @@ static void do_mark_dirty_regions(void *__restrict userdata,
|
|||||||
PBVHNode *node = data->nodes[n];
|
PBVHNode *node = data->nodes[n];
|
||||||
BKE_pbvh_pixels_mark_image_dirty(*node, *data->image_data.image, *data->image_data.image_user);
|
BKE_pbvh_pixels_mark_image_dirty(*node, *data->image_data.image, *data->image_data.image_user);
|
||||||
}
|
}
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/** \name Fix non-manifold edge bleeding.
|
||||||
|
* \{ */
|
||||||
|
|
||||||
|
static Vector<image::TileNumber> collect_dirty_tiles(Span<PBVHNode *> nodes)
|
||||||
|
{
|
||||||
|
Vector<image::TileNumber> dirty_tiles;
|
||||||
|
for (PBVHNode *node : nodes) {
|
||||||
|
BKE_pbvh_pixels_collect_dirty_tiles(*node, dirty_tiles);
|
||||||
|
}
|
||||||
|
return dirty_tiles;
|
||||||
|
}
|
||||||
|
static void fix_non_manifold_seam_bleeding(PBVH &pbvh,
|
||||||
|
TexturePaintingUserData &user_data,
|
||||||
|
Span<TileNumber> tile_numbers_to_fix)
|
||||||
|
{
|
||||||
|
for (image::TileNumber tile_number : tile_numbers_to_fix) {
|
||||||
|
BKE_pbvh_pixels_copy_pixels(
|
||||||
|
pbvh, *user_data.image_data.image, *user_data.image_data.image_user, tile_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fix_non_manifold_seam_bleeding(Object &ob,
|
||||||
|
const int totnode,
|
||||||
|
TexturePaintingUserData &user_data)
|
||||||
|
{
|
||||||
|
Vector<image::TileNumber> dirty_tiles = collect_dirty_tiles(
|
||||||
|
Span<PBVHNode *>(user_data.nodes, totnode));
|
||||||
|
fix_non_manifold_seam_bleeding(*ob.sculpt->pbvh, user_data, dirty_tiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \} */
|
||||||
|
|
||||||
} // namespace blender::ed::sculpt_paint::paint::image
|
} // namespace blender::ed::sculpt_paint::paint::image
|
||||||
|
|
||||||
@ -568,6 +601,7 @@ void SCULPT_do_paint_brush_image(PaintModeSettings *paint_mode_settings,
|
|||||||
BKE_pbvh_parallel_range_settings(&settings, true, texnodes_num);
|
BKE_pbvh_parallel_range_settings(&settings, true, texnodes_num);
|
||||||
BLI_task_parallel_range(0, texnodes_num, &data, do_push_undo_tile, &settings);
|
BLI_task_parallel_range(0, texnodes_num, &data, do_push_undo_tile, &settings);
|
||||||
BLI_task_parallel_range(0, texnodes_num, &data, do_paint_pixels, &settings);
|
BLI_task_parallel_range(0, texnodes_num, &data, do_paint_pixels, &settings);
|
||||||
|
fix_non_manifold_seam_bleeding(*ob, texnodes_num, data);
|
||||||
|
|
||||||
TaskParallelSettings settings_flush;
|
TaskParallelSettings settings_flush;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user