Allow rotating projected images and text

This commit is contained in:
sentientstardust
2026-05-22 11:42:35 +01:00
parent c6035a7c91
commit 63f5828425
2 changed files with 330 additions and 77 deletions

View File

@@ -76,6 +76,7 @@ constexpr float SlopeAutoPaintMaxAngleDeg = 180.f;
constexpr float ProjectionTextRasterBaseFontSize = 200.f;
constexpr int ProjectionTextRasterMaxDimension = 2048;
constexpr int ProjectionTextRasterMinFontSize = 18;
constexpr double ProjectionSliderDoubleClickMaxSeconds = 0.24;
static bool queue_color_data_crash_backup(ModelObject *object)
{
@@ -2954,10 +2955,137 @@ struct ProjectionContext
uint32_t image_width = 0;
uint32_t image_height = 0;
float image_opacity = 1.f;
float overlay_rotation_deg = 0.f;
bool apply_transparency_as_background = false;
ClippingPlane section_clipping_plane;
};
struct ProjectionScreenBounds
{
float left = 0.f;
float top = 0.f;
float right = 0.f;
float bottom = 0.f;
};
static float normalize_projection_rotation_deg(float rotation_deg)
{
if (!std::isfinite(rotation_deg))
return 0.f;
rotation_deg = std::fmod(rotation_deg, 360.f);
if (rotation_deg > 180.f)
rotation_deg -= 360.f;
if (rotation_deg < -180.f)
rotation_deg += 360.f;
return rotation_deg;
}
static Vec2f rotate_projection_screen_offset(const Vec2f &offset, float rotation_deg)
{
const float angle = Geometry::deg2rad(rotation_deg);
const float c = std::cos(angle);
const float s = std::sin(angle);
return Vec2f(offset.x() * c - offset.y() * s,
offset.x() * s + offset.y() * c);
}
static Vec2f projection_overlay_center(const ProjectionContext &context)
{
return Vec2f(context.overlay_left + context.overlay_width * 0.5f,
context.overlay_top + context.overlay_height * 0.5f);
}
static std::array<Vec2f, 4> projection_overlay_screen_corners(const ProjectionContext &context)
{
const Vec2f center = projection_overlay_center(context);
const float half_w = context.overlay_width * 0.5f;
const float half_h = context.overlay_height * 0.5f;
const std::array<Vec2f, 4> offsets = {
Vec2f(-half_w, -half_h),
Vec2f( half_w, -half_h),
Vec2f( half_w, half_h),
Vec2f(-half_w, half_h)
};
std::array<Vec2f, 4> corners;
const float rotation_deg = normalize_projection_rotation_deg(context.overlay_rotation_deg);
for (size_t idx = 0; idx < offsets.size(); ++idx)
corners[idx] = center + rotate_projection_screen_offset(offsets[idx], rotation_deg);
return corners;
}
static ProjectionScreenBounds projection_overlay_screen_bounds(const ProjectionContext &context)
{
ProjectionScreenBounds bounds;
const std::array<Vec2f, 4> corners = projection_overlay_screen_corners(context);
bounds.left = std::min({ corners[0].x(), corners[1].x(), corners[2].x(), corners[3].x() });
bounds.top = std::min({ corners[0].y(), corners[1].y(), corners[2].y(), corners[3].y() });
bounds.right = std::max({ corners[0].x(), corners[1].x(), corners[2].x(), corners[3].x() });
bounds.bottom = std::max({ corners[0].y(), corners[1].y(), corners[2].y(), corners[3].y() });
return bounds;
}
static bool projection_screen_to_image_uv(const ProjectionContext &context,
const Vec2f &screen,
Vec2f &uv,
bool reject_outside)
{
if (context.overlay_width <= EPSILON ||
context.overlay_height <= EPSILON ||
context.image_width == 0 ||
context.image_height == 0)
return false;
const Vec2f center = projection_overlay_center(context);
const Vec2f unrotated = rotate_projection_screen_offset(screen - center, -normalize_projection_rotation_deg(context.overlay_rotation_deg));
uv.x() = (unrotated.x() + context.overlay_width * 0.5f) / context.overlay_width;
uv.y() = (unrotated.y() + context.overlay_height * 0.5f) / context.overlay_height;
if (!std::isfinite(uv.x()) || !std::isfinite(uv.y()))
return false;
if (reject_outside) {
constexpr float edge_epsilon = 1e-6f;
if (uv.x() < -edge_epsilon ||
uv.y() < -edge_epsilon ||
uv.x() > 1.f + edge_epsilon ||
uv.y() > 1.f + edge_epsilon)
return false;
uv.x() = std::clamp(uv.x(), 0.f, 1.f);
uv.y() = std::clamp(uv.y(), 0.f, 1.f);
}
return true;
}
static bool projection_screen_to_image_pixel(const ProjectionContext &context,
const Vec2f &screen,
Vec2f &image_pixel,
bool reject_outside)
{
Vec2f uv = Vec2f::Zero();
if (!projection_screen_to_image_uv(context, screen, uv, reject_outside))
return false;
image_pixel.x() = uv.x() * float(context.image_width);
image_pixel.y() = uv.y() * float(context.image_height);
return std::isfinite(image_pixel.x()) && std::isfinite(image_pixel.y());
}
static bool projection_slider_short_double_clicked(double &last_click_time, bool click_changed_value)
{
if (!ImGui::IsItemHovered() || !ImGui::IsMouseClicked(ImGuiMouseButton_Left))
return false;
if (click_changed_value) {
last_click_time = -1.0;
return false;
}
const double now = ImGui::GetTime();
const bool double_clicked =
last_click_time >= 0.0 &&
now - last_click_time <= ProjectionSliderDoubleClickMaxSeconds;
last_click_time = double_clicked ? -1.0 : now;
return double_clicked;
}
static bool projection_section_view_active(const ProjectionContext &context)
{
return context.section_clipping_plane.is_active();
@@ -3695,15 +3823,11 @@ static std::optional<ColorRGBA> projected_image_color_at_point(const ProjectionC
Vec2f screen = Vec2f::Zero();
if (!project_point_to_screen(context, world_point, screen))
return std::nullopt;
if (screen.x() < context.overlay_left ||
screen.y() < context.overlay_top ||
screen.x() > context.overlay_left + context.overlay_width ||
screen.y() > context.overlay_top + context.overlay_height)
return std::nullopt;
const float u = (screen.x() - context.overlay_left) / context.overlay_width;
const float v = (screen.y() - context.overlay_top) / context.overlay_height;
return sample_rgba_bilinear_clamped(*context.image_rgba, context.image_width, context.image_height, u, v);
Vec2f uv = Vec2f::Zero();
if (!projection_screen_to_image_uv(context, screen, uv, true))
return std::nullopt;
return sample_rgba_bilinear_clamped(*context.image_rgba, context.image_width, context.image_height, uv.x(), uv.y());
}
static std::vector<uint8_t> projected_raw_offsets_at_point(const ProjectionContext &context,
@@ -3721,15 +3845,11 @@ static std::vector<uint8_t> projected_raw_offsets_at_point(const ProjectionConte
Vec2f screen = Vec2f::Zero();
if (!project_point_to_screen(context, world_point, screen))
return {};
if (screen.x() < context.overlay_left ||
screen.y() < context.overlay_top ||
screen.x() > context.overlay_left + context.overlay_width ||
screen.y() > context.overlay_top + context.overlay_height)
return {};
const float u = (screen.x() - context.overlay_left) / context.overlay_width;
const float v = (screen.y() - context.overlay_top) / context.overlay_height;
return sample_raw_offsets_bilinear_clamped(atlas, u, v);
Vec2f uv = Vec2f::Zero();
if (!projection_screen_to_image_uv(context, screen, uv, true))
return {};
return sample_raw_offsets_bilinear_clamped(atlas, uv.x(), uv.y());
}
static bool projection_triangle_intersects_overlay(const ProjectionContext &context,
@@ -3750,19 +3870,22 @@ static bool projection_triangle_intersects_overlay(const ProjectionContext
Vec2f screen = Vec2f::Zero();
if (!project_point_to_depth_clipped_screen(context, vertex, screen))
continue;
min_x = std::min(min_x, screen.x());
min_y = std::min(min_y, screen.y());
max_x = std::max(max_x, screen.x());
max_y = std::max(max_y, screen.y());
Vec2f image_pixel = Vec2f::Zero();
if (!projection_screen_to_image_pixel(context, screen, image_pixel, false))
continue;
min_x = std::min(min_x, image_pixel.x());
min_y = std::min(min_y, image_pixel.y());
max_x = std::max(max_x, image_pixel.x());
max_y = std::max(max_y, image_pixel.y());
any_projected = true;
}
if (!any_projected)
return false;
return max_x >= context.overlay_left &&
min_x <= context.overlay_left + context.overlay_width &&
max_y >= context.overlay_top &&
min_y <= context.overlay_top + context.overlay_height;
return max_x >= 0.f &&
min_x <= float(context.image_width) &&
max_y >= 0.f &&
min_y <= float(context.image_height);
}
static bool project_point_to_image_pixel(const ProjectionContext &context,
@@ -3779,9 +3902,7 @@ static bool project_point_to_image_pixel(const ProjectionContext &context,
if (!project_point_to_screen(context, world_point, screen))
return false;
image_pixel.x() = ((screen.x() - context.overlay_left) / context.overlay_width) * float(context.image_width);
image_pixel.y() = ((screen.y() - context.overlay_top) / context.overlay_height) * float(context.image_height);
return std::isfinite(image_pixel.x()) && std::isfinite(image_pixel.y());
return projection_screen_to_image_pixel(context, screen, image_pixel, false);
}
static bool project_depth_clipped_point_to_image_pixel(const ProjectionContext &context,
@@ -3798,9 +3919,7 @@ static bool project_depth_clipped_point_to_image_pixel(const ProjectionContext &
if (!project_point_to_depth_clipped_screen(context, world_point, screen))
return false;
image_pixel.x() = ((screen.x() - context.overlay_left) / context.overlay_width) * float(context.image_width);
image_pixel.y() = ((screen.y() - context.overlay_top) / context.overlay_height) * float(context.image_height);
return std::isfinite(image_pixel.x()) && std::isfinite(image_pixel.y());
return projection_screen_to_image_pixel(context, screen, image_pixel, false);
}
static float projection_triangle_image_pixel_span(const ProjectionContext &context,
@@ -3885,28 +4004,27 @@ static float projection_triangle_overlay_image_pixel_span(const ProjectionContex
polygon.reserve(world_polygon.size());
for (const Vec3d &vertex : world_polygon) {
Vec2f screen = Vec2f::Zero();
if (project_point_to_depth_clipped_screen(context, vertex, screen))
polygon.emplace_back(screen);
if (!project_point_to_depth_clipped_screen(context, vertex, screen))
continue;
Vec2f image_pixel = Vec2f::Zero();
if (projection_screen_to_image_pixel(context, screen, image_pixel, false))
polygon.emplace_back(image_pixel);
}
if (polygon.size() < 2)
return projection_triangle_image_pixel_span(context, world_matrix, vertices);
projection_clip_screen_polygon(polygon, 0, context.overlay_left, true);
projection_clip_screen_polygon(polygon, 0, context.overlay_left + context.overlay_width, false);
projection_clip_screen_polygon(polygon, 1, context.overlay_top, true);
projection_clip_screen_polygon(polygon, 1, context.overlay_top + context.overlay_height, false);
projection_clip_screen_polygon(polygon, 0, 0.f, true);
projection_clip_screen_polygon(polygon, 0, float(context.image_width), false);
projection_clip_screen_polygon(polygon, 1, 0.f, true);
projection_clip_screen_polygon(polygon, 1, float(context.image_height), false);
if (polygon.size() < 2)
return projection_triangle_image_pixel_span(context, world_matrix, vertices);
float span = 0.f;
for (size_t i = 0; i + 1 < polygon.size(); ++i) {
const Vec2f pixel_i(((polygon[i].x() - context.overlay_left) / context.overlay_width) * float(context.image_width),
((polygon[i].y() - context.overlay_top) / context.overlay_height) * float(context.image_height));
for (size_t j = i + 1; j < polygon.size(); ++j) {
const Vec2f pixel_j(((polygon[j].x() - context.overlay_left) / context.overlay_width) * float(context.image_width),
((polygon[j].y() - context.overlay_top) / context.overlay_height) * float(context.image_height));
span = std::max(span, (pixel_i - pixel_j).norm());
span = std::max(span, (polygon[i] - polygon[j]).norm());
}
}
return span;
@@ -5016,12 +5134,18 @@ static ProjectionVisibility build_projection_visibility(const ProjectionContext
if (object == nullptr || context.overlay_width <= 0.f || context.overlay_height <= 0.f)
return visibility;
const float max_dim = std::max(context.overlay_width, context.overlay_height);
const ProjectionScreenBounds bounds = projection_overlay_screen_bounds(context);
const float bounds_width = bounds.right - bounds.left;
const float bounds_height = bounds.bottom - bounds.top;
if (bounds_width <= 0.f || bounds_height <= 0.f)
return visibility;
const float max_dim = std::max(bounds_width, bounds_height);
visibility.scale = max_dim > 2048.f ? 2048.f / max_dim : 1.f;
visibility.width = std::max(1, int(std::ceil(context.overlay_width * visibility.scale)));
visibility.height = std::max(1, int(std::ceil(context.overlay_height * visibility.scale)));
visibility.left = context.overlay_left;
visibility.top = context.overlay_top;
visibility.width = std::max(1, int(std::ceil(bounds_width * visibility.scale)));
visibility.height = std::max(1, int(std::ceil(bounds_height * visibility.scale)));
visibility.left = bounds.left;
visibility.top = bounds.top;
visibility.depth.assign(size_t(visibility.width) * size_t(visibility.height), std::numeric_limits<float>::max());
visibility.triangle_keys.assign(visibility.depth.size(), PROJECTION_VISIBILITY_INVALID_TRIANGLE_KEY);
@@ -14802,8 +14926,17 @@ void GLGizmoImageProjection::on_load(cereal::BinaryInputArchive& ar)
m_text_capitalize = true;
}
try {
ar(m_projection_rotation_deg,
m_projection_panel_expanded);
} catch (...) {
m_projection_rotation_deg = 0.f;
m_projection_panel_expanded = true;
}
m_projection_mode = ProjectionMode(std::clamp(mode, 0, 2));
m_projection_mode_initialized = false;
m_projection_rotation_deg = normalize_projection_rotation_deg(m_projection_rotation_deg);
m_text_font_size = ProjectionTextRasterBaseFontSize;
for (float &channel : m_text_color)
channel = std::clamp(channel, 0.f, 1.f);
@@ -14883,6 +15016,9 @@ void GLGizmoImageProjection::on_save(cereal::BinaryOutputArchive& ar) const
m_text_background_color[2],
m_text_background_transparent,
m_text_capitalize);
ar(m_projection_rotation_deg,
m_projection_panel_expanded);
}
void GLGizmoImageProjection::on_render()
@@ -15053,6 +15189,7 @@ void GLGizmoImageProjection::mark_text_projection_dirty()
m_text_raw_atlas = {};
if (m_text_mode)
m_overlay_texture_dirty = true;
show_projection_overlay();
m_parent.set_as_dirty();
m_parent.request_extra_frame();
}
@@ -15277,6 +15414,46 @@ bool GLGizmoImageProjection::ensure_overlay_texture()
return true;
}
void GLGizmoImageProjection::render_projection_overlay()
{
if (!m_show_overlay || !ensure_overlay_texture())
return;
const OverlayRect rect = overlay_rect();
if (rect.width <= 0.f || rect.height <= 0.f)
return;
ProjectionContext overlay_context;
overlay_context.overlay_left = rect.left;
overlay_context.overlay_top = rect.top;
overlay_context.overlay_width = rect.width;
overlay_context.overlay_height = rect.height;
overlay_context.image_width = active_projection_width();
overlay_context.image_height = active_projection_height();
overlay_context.overlay_rotation_deg = m_projection_rotation_deg;
const std::array<Vec2f, 4> corners = projection_overlay_screen_corners(overlay_context);
ImGui::GetBackgroundDrawList()->AddImageQuad((void *)(intptr_t)m_overlay_texture.get_id(),
ImVec2(corners[0].x(), corners[0].y()),
ImVec2(corners[1].x(), corners[1].y()),
ImVec2(corners[2].x(), corners[2].y()),
ImVec2(corners[3].x(), corners[3].y()),
ImVec2(0.f, 0.f),
ImVec2(1.f, 0.f),
ImVec2(1.f, 1.f),
ImVec2(0.f, 1.f),
ImGui::GetColorU32(
ImVec4(1.f, 1.f, 1.f, 0.72f * std::clamp(m_projection_opacity, 0.f, 1.f))));
}
void GLGizmoImageProjection::show_projection_overlay()
{
if (m_show_overlay)
return;
m_show_overlay = true;
m_parent.set_as_dirty();
m_parent.request_extra_frame();
}
GLGizmoImageProjection::OverlayRect GLGizmoImageProjection::overlay_rect() const
{
OverlayRect rect;
@@ -15454,6 +15631,30 @@ bool GLGizmoImageProjection::selected_object_has_raw_atlas_texture_data() const
return false;
}
bool GLGizmoImageProjection::render_projection_action_controls()
{
if (active_projection_empty()) {
bool overlay = false;
m_imgui->disabled_begin(true);
ImGui::Checkbox(m_text_mode ? "Show text overlay" : "Show image overlay", &overlay);
m_imgui->disabled_end();
} else if (ImGui::Checkbox(m_text_mode ? "Show text overlay" : "Show image overlay", &m_show_overlay)) {
m_parent.set_as_dirty();
m_parent.request_extra_frame();
}
m_imgui->disabled_begin(active_projection_empty() || !projection_mode_allowed(m_projection_mode));
const wxString project_label = m_text_mode ? _L("Project text onto model") : _L("Project image onto model");
const bool projected = m_imgui->button(project_label) && project_image_to_selected_object();
if (projected) {
m_show_overlay = false;
m_parent.set_as_dirty();
m_parent.request_extra_frame();
}
m_imgui->disabled_end();
return projected;
}
void GLGizmoImageProjection::on_render_input_window(float x, float y, float bottom_limit)
{
if (m_text_mode)
@@ -15469,6 +15670,20 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
const float max_tooltip_width = ImGui::GetFontSize() * 20.f;
if (m_imgui->button(m_projection_panel_expanded ? _L("Collapse") : _L("Expand"))) {
m_projection_panel_expanded = !m_projection_panel_expanded;
m_parent.set_as_dirty();
m_parent.request_extra_frame();
}
if (!m_projection_panel_expanded) {
render_projection_action_controls();
GizmoImguiEnd();
ImGuiWrapper::pop_toolbar_style();
render_projection_overlay();
return;
}
if (m_imgui->button(_L("Manage Color Data for this object")))
open_color_data_management_dialog();
@@ -15498,6 +15713,7 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott
if (m_text_mode)
ensure_text_projection_image();
update_default_projection_mode();
show_projection_overlay();
m_parent.set_as_dirty();
m_parent.request_extra_frame();
}
@@ -15518,11 +15734,6 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott
m_imgui->warning_text(raw_offset_data_rgba_conversion_warning_text(), m_imgui->scaled(RawOffsetDataWarningWrapEm));
}
if (!active_projection_empty() && ImGui::Checkbox(m_text_mode ? "Show text overlay" : "Show image overlay", &m_show_overlay)) {
m_parent.set_as_dirty();
m_parent.request_extra_frame();
}
// if (has_rgba_data)
// m_imgui->warning_text(_L("Note: Image Texture mode is disabled as this object has RGBA data (must use RGBA data mode)"));
@@ -15539,6 +15750,7 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott
if (ImGui::Selectable(mode_labels[idx], selected, flags) && allowed) {
mode = idx;
m_projection_mode = candidate;
show_projection_overlay();
}
if (selected)
ImGui::SetItemDefaultFocus();
@@ -15549,10 +15761,39 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott
float opacity_pct = m_projection_opacity * 100.f;
m_imgui->text(_L("Opacity"));
ImGui::PushItemWidth(m_imgui->scaled(8.f));
if (m_imgui->bbl_slider_float_style("##image_projection_opacity", &opacity_pct, 0.f, 100.f, "%.0f%%", 1.f, true)) {
m_projection_opacity = std::clamp(opacity_pct / 100.f, 0.f, 1.f);
const bool opacity_changed =
m_imgui->bbl_slider_float_style("##image_projection_opacity", &opacity_pct, 0.f, 100.f, "%.0f%%", 1.f, true);
const bool opacity_reset = projection_slider_short_double_clicked(m_projection_opacity_last_click_time, opacity_changed);
if (opacity_reset)
m_projection_opacity_reset_active = true;
const bool suppress_opacity_slider = opacity_reset || m_projection_opacity_reset_active;
if (suppress_opacity_slider || opacity_changed) {
m_projection_opacity = suppress_opacity_slider ? 1.f : std::clamp(opacity_pct / 100.f, 0.f, 1.f);
show_projection_overlay();
m_parent.set_as_dirty();
m_parent.request_extra_frame();
}
if (m_projection_opacity_reset_active && !ImGui::IsMouseDown(ImGuiMouseButton_Left))
m_projection_opacity_reset_active = false;
ImGui::PopItemWidth();
float rotation_deg = m_projection_rotation_deg;
m_imgui->text(_L("Rotation"));
ImGui::PushItemWidth(m_imgui->scaled(8.f));
const bool rotation_changed =
m_imgui->bbl_slider_float_style("##image_projection_rotation", &rotation_deg, -180.f, 180.f, "%.0f deg", 1.f, true);
const bool rotation_reset = projection_slider_short_double_clicked(m_projection_rotation_last_click_time, rotation_changed);
if (rotation_reset)
m_projection_rotation_reset_active = true;
const bool suppress_rotation_slider = rotation_reset || m_projection_rotation_reset_active;
if (suppress_rotation_slider || rotation_changed) {
m_projection_rotation_deg = suppress_rotation_slider ? 0.f : normalize_projection_rotation_deg(rotation_deg);
show_projection_overlay();
m_parent.set_as_dirty();
m_parent.request_extra_frame();
}
if (m_projection_rotation_reset_active && !ImGui::IsMouseDown(ImGuiMouseButton_Left))
m_projection_rotation_reset_active = false;
ImGui::PopItemWidth();
if (m_c != nullptr && m_c->object_clipper() != nullptr) {
@@ -15564,21 +15805,36 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott
if (m_c != nullptr && m_c->object_clipper() != nullptr)
m_c->object_clipper()->set_position_by_ratio(-1., false);
});
show_projection_overlay();
}
float clp_dist = float(m_c->object_clipper()->get_position());
ImGui::PushItemWidth(m_imgui->scaled(8.f));
if (m_imgui->bbl_slider_float_style("##image_projection_clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.f, true))
const bool section_changed =
m_imgui->bbl_slider_float_style("##image_projection_clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.f, true);
const bool section_reset = projection_slider_short_double_clicked(m_projection_section_last_click_time, section_changed);
if (section_reset)
m_projection_section_reset_active = true;
const bool suppress_section_slider = section_reset || m_projection_section_reset_active;
if (suppress_section_slider) {
m_c->object_clipper()->set_position_by_ratio(0., true);
show_projection_overlay();
} else if (section_changed) {
m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
show_projection_overlay();
}
if (m_projection_section_reset_active && !ImGui::IsMouseDown(ImGuiMouseButton_Left))
m_projection_section_reset_active = false;
ImGui::PopItemWidth();
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Section view"), max_tooltip_width);
}
if (!m_text_mode)
ImGui::Checkbox("Apply transparent regions as background color", &m_apply_transparency_as_background);
ImGui::Checkbox("Pass through model", &m_pass_through_model);
if (!m_text_mode && ImGui::Checkbox("Apply transparent regions as background color", &m_apply_transparency_as_background))
show_projection_overlay();
if (ImGui::Checkbox("Pass through model", &m_pass_through_model))
show_projection_overlay();
const bool show_raw_conversion_option =
active_raw_atlas_valid() &&
@@ -15587,30 +15843,12 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott
if (show_raw_conversion_option)
ImGui::Checkbox("Convert existing colors to raw offset data (slow)", &m_convert_existing_colors_to_raw_offsets);
m_imgui->disabled_begin(active_projection_empty() || !projection_mode_allowed(m_projection_mode));
const wxString project_label = m_text_mode ? _L("Project text onto model") : _L("Project image onto model");
if (m_imgui->button(project_label) && project_image_to_selected_object()) {
m_show_overlay = false;
m_parent.set_as_dirty();
m_parent.request_extra_frame();
}
m_imgui->disabled_end();
render_projection_action_controls();
GizmoImguiEnd();
ImGuiWrapper::pop_toolbar_style();
if (m_show_overlay && ensure_overlay_texture()) {
const OverlayRect rect = overlay_rect();
if (rect.width > 0.f && rect.height > 0.f) {
ImGui::GetBackgroundDrawList()->AddImage((void *)(intptr_t)m_overlay_texture.get_id(),
ImVec2(rect.left, rect.top),
ImVec2(rect.left + rect.width, rect.top + rect.height),
ImVec2(0.f, 0.f),
ImVec2(1.f, 1.f),
ImGui::GetColorU32(
ImVec4(1.f, 1.f, 1.f, 0.72f * std::clamp(m_projection_opacity, 0.f, 1.f))));
}
}
render_projection_overlay();
}
bool GLGizmoImageProjection::project_image_to_selected_object()
@@ -15708,6 +15946,7 @@ bool GLGizmoImageProjection::project_image_to_selected_object()
context.image_width = active_projection_width();
context.image_height = active_projection_height();
context.image_opacity = m_projection_opacity;
context.overlay_rotation_deg = m_projection_rotation_deg;
context.apply_transparency_as_background = effective_apply_transparency_as_background();
apply_projection_section_view(context, m_c != nullptr ? m_c->object_clipper() : nullptr);
@@ -15753,6 +15992,7 @@ bool GLGizmoImageProjection::project_to_vertex_colors(ModelObject *object)
context.image_width = active_projection_width();
context.image_height = active_projection_height();
context.image_opacity = m_projection_opacity;
context.overlay_rotation_deg = m_projection_rotation_deg;
context.apply_transparency_as_background = effective_apply_transparency_as_background();
apply_projection_section_view(context, m_c != nullptr ? m_c->object_clipper() : nullptr);
@@ -15904,6 +16144,7 @@ bool GLGizmoImageProjection::project_to_image_texture(ModelObject *object)
context.image_width = active_projection_width();
context.image_height = active_projection_height();
context.image_opacity = m_projection_opacity;
context.overlay_rotation_deg = m_projection_rotation_deg;
context.apply_transparency_as_background = effective_apply_transparency_as_background();
apply_projection_section_view(context, m_c != nullptr ? m_c->object_clipper() : nullptr);
@@ -16223,6 +16464,7 @@ bool GLGizmoImageProjection::project_to_rgb_data(ModelObject *object)
context.image_width = active_projection_width();
context.image_height = active_projection_height();
context.image_opacity = m_projection_opacity;
context.overlay_rotation_deg = m_projection_rotation_deg;
context.apply_transparency_as_background = effective_apply_transparency_as_background();
apply_projection_section_view(context, m_c != nullptr ? m_c->object_clipper() : nullptr);

View File

@@ -522,6 +522,9 @@ private:
bool ensure_text_projection_image();
bool update_text_raw_atlas(const std::vector<uint8_t> &glyph_mask);
void render_text_projection_controls(float max_tooltip_width);
bool render_projection_action_controls();
void render_projection_overlay();
void show_projection_overlay();
bool ensure_overlay_texture();
OverlayRect overlay_rect() const;
const std::vector<uint8_t> &active_projection_rgba() const;
@@ -579,6 +582,14 @@ private:
bool m_overlay_texture_dirty = false;
bool m_show_overlay = true;
float m_projection_opacity = 1.f;
bool m_projection_opacity_reset_active = false;
double m_projection_opacity_last_click_time = -1.0;
float m_projection_rotation_deg = 0.f;
bool m_projection_rotation_reset_active = false;
double m_projection_rotation_last_click_time = -1.0;
bool m_projection_section_reset_active = false;
double m_projection_section_last_click_time = -1.0;
bool m_projection_panel_expanded = true;
bool m_apply_transparency_as_background = false;
bool m_pass_through_model = false;
bool m_convert_existing_colors_to_raw_offsets = true;