44cae1f00 Fix RGBA color painting bug. Fix screen position calculation to allow image projection when in perspective view

This commit is contained in:
sentientstardust
2026-05-02 13:58:59 +01:00
parent f719d895f5
commit 7530e344d7
4 changed files with 311 additions and 24 deletions

View File

@@ -4307,6 +4307,78 @@ static void color_facets_append_sampled_triangle(TriangleColorSplittingData
data.colors_rgba.emplace_back(cc);
}
static bool color_facets_append_existing_or_sampled_triangle(
const TriangleColorSplittingData &old_data,
TriangleColorSplittingData &new_data,
const TextureMappingColorSampler &sampler,
const TextureMappingColorLeafResamplePredicate &resample_leaf,
size_t source_triangle,
int bitstream_end,
size_t color_end,
int &bit_idx,
size_t &color_idx,
const std::array<Vec3f, 3> &vertices,
const std::array<Vec3f, 3> &barycentrics,
int depth,
int min_depth,
int max_depth,
float split_color_threshold)
{
if (bit_idx + 3 >= bitstream_end)
return false;
int code = 0;
for (int bit = 0; bit < 4; ++bit)
code |= int(old_data.bitstream[size_t(bit_idx++)]) << bit;
const int split_sides = code & 0b11;
if (split_sides == 0) {
if (color_idx >= color_end || color_idx >= old_data.colors_rgba.size())
return false;
const uint32_t rgba = old_data.colors_rgba[color_idx++];
if (resample_leaf && resample_leaf(source_triangle, vertices, barycentrics, rgba)) {
color_facets_append_sampled_triangle(new_data,
sampler,
source_triangle,
vertices,
barycentrics,
depth,
min_depth,
max_depth,
split_color_threshold);
} else {
color_facets_append_nibble(new_data.bitstream, 0u);
new_data.colors_rgba.emplace_back(rgba);
}
return true;
}
const int special_side = (code >> 2) & 0b11;
color_facets_append_nibble(new_data.bitstream, unsigned(code));
const std::array<std::array<Vec3f, 3>, 4> child_vertices = color_facets_split_triangle(vertices, split_sides, special_side);
const std::array<std::array<Vec3f, 3>, 4> child_barycentrics = color_facets_split_triangle(barycentrics, split_sides, special_side);
for (int child_idx = split_sides; child_idx >= 0; --child_idx) {
if (!color_facets_append_existing_or_sampled_triangle(old_data,
new_data,
sampler,
resample_leaf,
source_triangle,
bitstream_end,
color_end,
bit_idx,
color_idx,
child_vertices[size_t(child_idx)],
child_barycentrics[size_t(child_idx)],
depth + 1,
min_depth,
max_depth,
split_color_threshold))
return false;
}
return true;
}
static void color_facets_extract_triangle(const TriangleColorSplittingData &data,
int bitstream_end,
size_t color_end,
@@ -4445,12 +4517,18 @@ bool ColorFacetsAnnotation::set_from_triangle_sampler(const ModelVolume
const TextureMappingColorSampler &sampler,
int max_depth,
float split_color_threshold,
const TextureMappingColorSubdivisionDepths &subdivision_depths)
const TextureMappingColorSubdivisionDepths &subdivision_depths,
const std::vector<bool> *resample_triangles,
const TextureMappingColorLeafResamplePredicate &resample_leaf)
{
TriangleColorSplittingData new_data;
new_data.metadata_json = m_data.metadata_json;
const indexed_triangle_set &its = mv.mesh().its;
new_data.triangles_to_split.reserve(its.indices.size());
if (resample_triangles != nullptr) {
new_data.bitstream.reserve(m_data.bitstream.size());
new_data.colors_rgba.reserve(m_data.colors_rgba.size());
}
const std::array<Vec3f, 3> root_barycentrics = {
Vec3f(1.f, 0.f, 0.f),
@@ -4461,6 +4539,60 @@ bool ColorFacetsAnnotation::set_from_triangle_sampler(const ModelVolume
max_depth = std::clamp(max_depth, 0, 7);
split_color_threshold = std::max(split_color_threshold, 0.f);
size_t preserved_mapping_idx = 0;
auto existing_triangle_range = [this, &preserved_mapping_idx](size_t tri_idx,
int &bitstream_start,
int &bitstream_end,
int &color_start,
size_t &color_end) {
while (preserved_mapping_idx < m_data.triangles_to_split.size() &&
m_data.triangles_to_split[preserved_mapping_idx].triangle_idx < int(tri_idx))
++preserved_mapping_idx;
if (preserved_mapping_idx >= m_data.triangles_to_split.size() ||
m_data.triangles_to_split[preserved_mapping_idx].triangle_idx != int(tri_idx))
return false;
const auto mapping_it = m_data.triangles_to_split.begin() + preserved_mapping_idx;
const auto next_it = std::next(mapping_it);
bitstream_start = mapping_it->bitstream_start_idx;
bitstream_end = next_it == m_data.triangles_to_split.end() ?
int(m_data.bitstream.size()) :
next_it->bitstream_start_idx;
color_start = mapping_it->color_start_idx;
color_end = next_it == m_data.triangles_to_split.end() ?
m_data.colors_rgba.size() :
size_t(next_it->color_start_idx);
if (bitstream_start < 0 ||
bitstream_start >= bitstream_end ||
size_t(bitstream_end) > m_data.bitstream.size() ||
color_start < 0 ||
size_t(color_start) >= color_end ||
color_end > m_data.colors_rgba.size())
return false;
return true;
};
auto append_preserved_triangle = [this, &new_data, &existing_triangle_range](size_t tri_idx) {
int bitstream_start = 0;
int bitstream_end = 0;
int color_start = 0;
size_t color_end = 0;
if (!existing_triangle_range(tri_idx, bitstream_start, bitstream_end, color_start, color_end))
return false;
new_data.triangles_to_split.emplace_back(int(tri_idx), int(new_data.bitstream.size()), int(new_data.colors_rgba.size()));
new_data.bitstream.insert(new_data.bitstream.end(),
m_data.bitstream.begin() + bitstream_start,
m_data.bitstream.begin() + bitstream_end);
new_data.colors_rgba.insert(new_data.colors_rgba.end(),
m_data.colors_rgba.begin() + color_start,
m_data.colors_rgba.begin() + color_end);
return true;
};
for (size_t tri_idx = 0; tri_idx < its.indices.size(); ++tri_idx) {
const auto &tri = its.indices[tri_idx];
if (tri[0] < 0 || tri[1] < 0 || tri[2] < 0)
@@ -4476,6 +4608,16 @@ bool ColorFacetsAnnotation::set_from_triangle_sampler(const ModelVolume
its.vertices[size_t(tri[2])].cast<float>()
};
const bool should_resample_triangle =
resample_triangles == nullptr ||
tri_idx >= resample_triangles->size() ||
(*resample_triangles)[tri_idx];
if (resample_triangles != nullptr &&
tri_idx < resample_triangles->size() &&
!(*resample_triangles)[tri_idx] &&
append_preserved_triangle(tri_idx))
continue;
int triangle_min_depth = 0;
int triangle_max_depth = max_depth;
if (subdivision_depths) {
@@ -4487,6 +4629,36 @@ bool ColorFacetsAnnotation::set_from_triangle_sampler(const ModelVolume
}
new_data.triangles_to_split.emplace_back(int(tri_idx), int(new_data.bitstream.size()), int(new_data.colors_rgba.size()));
if (resample_leaf && should_resample_triangle) {
int bitstream_start = 0;
int bitstream_end = 0;
int color_start = 0;
size_t color_end = 0;
if (existing_triangle_range(tri_idx, bitstream_start, bitstream_end, color_start, color_end)) {
int bit_idx = bitstream_start;
size_t color_idx = size_t(color_start);
const size_t new_bitstream_start = new_data.bitstream.size();
const size_t new_color_start = new_data.colors_rgba.size();
if (color_facets_append_existing_or_sampled_triangle(m_data,
new_data,
sampler,
resample_leaf,
tri_idx,
bitstream_end,
color_end,
bit_idx,
color_idx,
vertices,
root_barycentrics,
0,
triangle_min_depth,
triangle_max_depth,
split_color_threshold))
continue;
new_data.bitstream.resize(new_bitstream_start);
new_data.colors_rgba.resize(new_color_start);
}
}
color_facets_append_sampled_triangle(new_data,
sampler,
tri_idx,
@@ -4666,12 +4838,29 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec
});
}
template<class T>
static bool model_volume_imported_vector_matches(const ModelVolumeImportedVector<T> &lhs, const ModelVolumeImportedVector<T> &rhs)
{
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
static bool model_volume_texture_mapping_data_matches(const ModelVolume &mv_old, const ModelVolume &mv_new)
{
return mv_old.texture_mapping_color_facets.timestamp_matches(mv_new.texture_mapping_color_facets) &&
model_volume_imported_vector_matches(mv_old.imported_vertex_colors_rgba, mv_new.imported_vertex_colors_rgba) &&
model_volume_imported_vector_matches(mv_old.imported_texture_uvs_per_face, mv_new.imported_texture_uvs_per_face) &&
model_volume_imported_vector_matches(mv_old.imported_texture_uv_valid, mv_new.imported_texture_uv_valid) &&
model_volume_imported_vector_matches(mv_old.imported_texture_rgba, mv_new.imported_texture_rgba) &&
mv_old.imported_texture_width == mv_new.imported_texture_width &&
mv_old.imported_texture_height == mv_new.imported_texture_height;
}
bool model_texture_mapping_color_data_changed(const ModelObject& mo, const ModelObject& mo_new)
{
return model_property_changed(mo, mo_new,
[](const ModelVolumeType t) { return t == ModelVolumeType::MODEL_PART; },
[](const ModelVolume &mv_old, const ModelVolume &mv_new) {
return mv_old.texture_mapping_color_facets.timestamp_matches(mv_new.texture_mapping_color_facets);
return model_volume_texture_mapping_data_matches(mv_old, mv_new);
});
}

View File

@@ -846,6 +846,8 @@ struct ColorFacetTriangle
using TextureMappingColorSampler = std::function<uint32_t(size_t, const Vec3f &, const Vec3f &)>;
using TextureMappingColorSubdivisionDepths = std::function<std::pair<int, int>(size_t, const std::array<Vec3f, 3> &)>;
using TextureMappingColorLeafResamplePredicate =
std::function<bool(size_t, const std::array<Vec3f, 3> &, const std::array<Vec3f, 3> &, uint32_t)>;
class ColorFacetsAnnotation final : public ObjectWithTimestamp {
public:
@@ -886,7 +888,9 @@ public:
const TextureMappingColorSampler &sampler,
int max_depth = 2,
float split_color_threshold = 0.045f,
const TextureMappingColorSubdivisionDepths &subdivision_depths = {});
const TextureMappingColorSubdivisionDepths &subdivision_depths = {},
const std::vector<bool> *resample_triangles = nullptr,
const TextureMappingColorLeafResamplePredicate &resample_leaf = {});
void get_facet_triangles(const ModelVolume &mv, std::vector<ColorFacetTriangle> &facets) const;
private:

View File

@@ -80,6 +80,12 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets);
assert(mv_dst.texture_mapping_color_facets.id() == mv_src.texture_mapping_color_facets.id());
mv_dst.texture_mapping_color_facets.assign(mv_src.texture_mapping_color_facets);
mv_dst.imported_vertex_colors_rgba = mv_src.imported_vertex_colors_rgba;
mv_dst.imported_texture_uvs_per_face = mv_src.imported_texture_uvs_per_face;
mv_dst.imported_texture_uv_valid = mv_src.imported_texture_uv_valid;
mv_dst.imported_texture_rgba = mv_src.imported_texture_rgba;
mv_dst.imported_texture_width = mv_src.imported_texture_width;
mv_dst.imported_texture_height = mv_src.imported_texture_height;
assert(mv_dst.fuzzy_skin_facets.id() == mv_src.fuzzy_skin_facets.id());
mv_dst.fuzzy_skin_facets.assign(mv_src.fuzzy_skin_facets);
//FIXME what to do with the materials?

View File

@@ -706,6 +706,36 @@ static float distance_between_segments(const Vec3f &p1, const Vec3f &q1, const V
return (p1 + d1 * s - (p2 + d2 * t)).norm();
}
static bool rgb_point_in_triangle(const Vec3f &point, const std::array<Vec3f, 3> &triangle)
{
Vec3f weights = Vec3f::Zero();
const float tolerance = -1e-4f;
return barycentric_weights_for_region_vertex_colors(point, triangle[0], triangle[1], triangle[2], weights) &&
weights.x() >= tolerance &&
weights.y() >= tolerance &&
weights.z() >= tolerance;
}
static bool rgb_triangles_overlap(const std::array<Vec3f, 3> &lhs, const std::array<Vec3f, 3> &rhs)
{
for (const Vec3f &point : lhs)
if (rgb_point_in_triangle(point, rhs))
return true;
for (const Vec3f &point : rhs)
if (rgb_point_in_triangle(point, lhs))
return true;
const float edge_tolerance = 1e-4f;
for (size_t lhs_idx = 0; lhs_idx < 3; ++lhs_idx)
for (size_t rhs_idx = 0; rhs_idx < 3; ++rhs_idx)
if (distance_between_segments(lhs[lhs_idx],
lhs[(lhs_idx + 1) % 3],
rhs[rhs_idx],
rhs[(rhs_idx + 1) % 3]) <= edge_tolerance)
return true;
return false;
}
static Vec3f transform_point(const Transform3d &matrix, const Vec3f &point)
{
return (matrix * point.cast<double>()).cast<float>();
@@ -963,27 +993,43 @@ static std::vector<bool> rgb_brush_candidate_source_triangles(
return candidates;
}
static int rgb_leaf_count_depth(size_t leaf_count)
static int rgb_color_tree_max_depth(const TriangleColorSplittingData &data, int bitstream_end, int &bit_idx, int depth)
{
int depth = 0;
size_t depth_leaf_count = 1;
while (depth_leaf_count < leaf_count && depth < 7) {
depth_leaf_count *= 4;
++depth;
}
return depth;
if (bit_idx + 3 >= bitstream_end)
return std::clamp(depth, 0, 7);
int code = 0;
for (int bit = 0; bit < 4; ++bit)
code |= int(data.bitstream[size_t(bit_idx++)]) << bit;
const int split_sides = code & 0b11;
if (split_sides == 0)
return std::clamp(depth, 0, 7);
int max_depth = std::clamp(depth + 1, 0, 7);
for (int child_idx = split_sides; child_idx >= 0; --child_idx)
max_depth = std::max(max_depth, rgb_color_tree_max_depth(data, bitstream_end, bit_idx, depth + 1));
return std::clamp(max_depth, 0, 7);
}
static std::vector<int> rgb_existing_source_triangle_depths(const std::vector<ColorFacetTriangle> &facets, size_t triangle_count)
static std::vector<int> rgb_existing_source_triangle_depths(const TriangleColorSplittingData &data, size_t triangle_count)
{
std::vector<size_t> leaf_counts(triangle_count, 0);
for (const ColorFacetTriangle &facet : facets)
if (facet.source_triangle >= 0 && size_t(facet.source_triangle) < leaf_counts.size())
++leaf_counts[size_t(facet.source_triangle)];
std::vector<int> depths(triangle_count, 0);
for (size_t tri_idx = 0; tri_idx < leaf_counts.size(); ++tri_idx)
depths[tri_idx] = rgb_leaf_count_depth(leaf_counts[tri_idx]);
for (auto mapping_it = data.triangles_to_split.begin(); mapping_it != data.triangles_to_split.end(); ++mapping_it) {
if (mapping_it->triangle_idx < 0 || size_t(mapping_it->triangle_idx) >= depths.size())
continue;
const auto next_it = std::next(mapping_it);
const int bitstream_start = mapping_it->bitstream_start_idx;
const int bitstream_end = next_it == data.triangles_to_split.end() ?
int(data.bitstream.size()) :
next_it->bitstream_start_idx;
if (bitstream_start < 0 || bitstream_start >= bitstream_end || size_t(bitstream_end) > data.bitstream.size())
continue;
int bit_idx = bitstream_start;
depths[size_t(mapping_it->triangle_idx)] = rgb_color_tree_max_depth(data, bitstream_end, bit_idx, 0);
}
return depths;
}
@@ -1076,7 +1122,7 @@ static bool apply_rgb_stroke_to_volume(ModelVolume
const int safe_max_depth = texture_mapping_depth_for_budget(volume.mesh().its.indices.size(), 7, 1800000);
const float brush_subdivision_target = true_color_brush_subdivision_target(brush_radius);
const std::vector<int> existing_source_triangle_depths =
rgb_existing_source_triangle_depths(existing_facets, volume.mesh().its.indices.size());
rgb_existing_source_triangle_depths(volume.texture_mapping_color_facets.get_data(), volume.mesh().its.indices.size());
TextureMappingColorSubdivisionDepths subdivision_depths =
[mesh_span,
safe_max_depth,
@@ -1103,7 +1149,49 @@ static bool apply_rgb_stroke_to_volume(ModelVolume
return std::make_pair(min_depth, max_depth);
};
return volume.texture_mapping_color_facets.set_from_triangle_sampler(volume, sampler, safe_max_depth, 0.012f, subdivision_depths);
TextureMappingColorLeafResamplePredicate resample_leaf =
[opacity,
brush_radius,
use_brush_path,
&brush_stroke_points_world,
&world_matrix,
&stroke_by_source_triangle,
&stroke_facets](size_t tri_idx, const std::array<Vec3f, 3> &vertices, const std::array<Vec3f, 3> &, uint32_t) {
if (opacity <= 0.f || brush_radius <= EPSILON)
return false;
if (use_brush_path) {
const std::array<Vec3f, 3> world_vertices = transform_triangle(world_matrix, vertices);
for (size_t point_idx = 0; point_idx < brush_stroke_points_world.size(); ++point_idx) {
const Vec3f segment_a = brush_stroke_points_world[point_idx];
const Vec3f segment_b = point_idx + 1 < brush_stroke_points_world.size() ?
brush_stroke_points_world[point_idx + 1] :
brush_stroke_points_world[point_idx];
if (triangle_intersects_brush_segment(world_vertices, segment_a, segment_b, brush_radius))
return true;
}
return false;
}
auto found = stroke_by_source_triangle.find(int(tri_idx));
if (found == stroke_by_source_triangle.end())
return false;
for (const size_t facet_idx : found->second) {
if (facet_idx >= stroke_facets.size())
continue;
if (rgb_triangles_overlap(vertices, stroke_facets[facet_idx].vertices))
return true;
}
return false;
};
return volume.texture_mapping_color_facets.set_from_triangle_sampler(volume,
sampler,
safe_max_depth,
0.012f,
subdivision_depths,
&brush_candidate_triangles,
resample_leaf);
}
static bool build_volume_rgb_data(const ModelVolume &volume, const ColorRGBA &background, ColorFacetsAnnotation &out)
@@ -6228,7 +6316,7 @@ bool GLGizmoImageProjection::project_to_vertex_colors(ModelObject *object)
const OverlayRect rect = overlay_rect();
ProjectionContext context;
context.view_projection = (camera.get_projection_matrix() * camera.get_view_matrix()).matrix();
context.view_projection = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix();
context.canvas_width = std::max(1, viewport[2]);
context.canvas_height = std::max(1, viewport[3]);
context.overlay_left = rect.left;
@@ -6355,7 +6443,7 @@ bool GLGizmoImageProjection::project_to_image_texture(ModelObject *object)
const OverlayRect rect = overlay_rect();
ProjectionContext context;
context.view_projection = (camera.get_projection_matrix() * camera.get_view_matrix()).matrix();
context.view_projection = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix();
context.canvas_width = std::max(1, viewport[2]);
context.canvas_height = std::max(1, viewport[3]);
context.overlay_left = rect.left;
@@ -6522,7 +6610,7 @@ bool GLGizmoImageProjection::project_to_rgb_data(ModelObject *object)
const OverlayRect rect = overlay_rect();
ProjectionContext context;
context.view_projection = (camera.get_projection_matrix() * camera.get_view_matrix()).matrix();
context.view_projection = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix();
context.canvas_width = std::max(1, viewport[2]);
context.canvas_height = std::max(1, viewport[3]);
context.overlay_left = rect.left;