Fix #126606: GPv3: Wrong projection and various other issues with exporters
Fixes the camera and View3D projection used for calculating the rendered region for SVG/PDF export. Also drastically simplifies this code by removing unnecessary variables and using an optional transform to indicate when camera data is actual available. Also fixes missing layers in export due to incorrect early exit in the layer loop. Pull Request: https://projects.blender.org/blender/blender/pulls/126691
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_bounds.hh"
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_vector.h"
|
||||
@@ -33,6 +32,8 @@
|
||||
#include "ED_object.hh"
|
||||
#include "ED_view3d.hh"
|
||||
|
||||
#include "UI_view2d.hh"
|
||||
|
||||
#include "grease_pencil_io_intern.hh"
|
||||
#include <optional>
|
||||
|
||||
@@ -230,13 +231,13 @@ static std::optional<Bounds<float2>> compute_drawing_bounds(
|
||||
for (const int point_i : points) {
|
||||
const float3 pos_world = math::transform_point(layer_to_world,
|
||||
deformation.positions[point_i]);
|
||||
float2 pos_view;
|
||||
float2 screen_co;
|
||||
eV3DProjStatus result = ED_view3d_project_float_global(
|
||||
®ion, pos_world, pos_view, V3D_PROJ_TEST_NOP);
|
||||
®ion, pos_world, screen_co, V3D_PROJ_TEST_NOP);
|
||||
if (result == V3D_PROJ_RET_OK) {
|
||||
const float pixels = radii[point_i] / ED_view3d_pixel_size(&rv3d, pos_world);
|
||||
|
||||
std::optional<Bounds<float2>> point_bounds = Bounds<float2>(pos_view);
|
||||
std::optional<Bounds<float2>> point_bounds = Bounds<float2>(screen_co);
|
||||
point_bounds->pad(pixels);
|
||||
drawing_bounds = bounds::merge(drawing_bounds, point_bounds);
|
||||
}
|
||||
@@ -285,67 +286,57 @@ static std::optional<Bounds<float2>> compute_objects_bounds(
|
||||
return full_bounds;
|
||||
}
|
||||
|
||||
void GreasePencilExporter::prepare_camera_params(Scene &scene,
|
||||
const int frame_number,
|
||||
const bool force_camera_view)
|
||||
static float4x4 persmat_from_camera_object(Scene &scene)
|
||||
{
|
||||
const bool use_camera_view = force_camera_view && (context_.v3d->camera != nullptr);
|
||||
|
||||
/* Ensure camera switch is applied. */
|
||||
BKE_scene_camera_switch_update(&scene);
|
||||
|
||||
/* Calculate camera matrix. */
|
||||
Object *cam_ob = scene.camera;
|
||||
if (cam_ob != nullptr) {
|
||||
/* Set up parameters. */
|
||||
CameraParams params;
|
||||
BKE_camera_params_init(¶ms);
|
||||
BKE_camera_params_from_object(¶ms, cam_ob);
|
||||
|
||||
/* Compute matrix, view-plane, etc. */
|
||||
BKE_camera_params_compute_viewplane(
|
||||
¶ms, scene.r.xsch, scene.r.ysch, scene.r.xasp, scene.r.yasp);
|
||||
BKE_camera_params_compute_matrix(¶ms);
|
||||
|
||||
float4x4 viewmat = math::invert(cam_ob->object_to_world());
|
||||
persmat_ = float4x4(params.winmat) * viewmat;
|
||||
}
|
||||
else {
|
||||
persmat_ = float4x4::identity();
|
||||
if (cam_ob == nullptr) {
|
||||
/* XXX not sure when this could ever happen if v3d camera is not null,
|
||||
* conditions are from GPv2 and not explained anywhere. */
|
||||
return float4x4::identity();
|
||||
}
|
||||
|
||||
win_size_ = {context_.region->winx, context_.region->winy};
|
||||
/* Set up parameters. */
|
||||
CameraParams params;
|
||||
BKE_camera_params_init(¶ms);
|
||||
BKE_camera_params_from_object(¶ms, cam_ob);
|
||||
|
||||
/* Compute matrix, view-plane, etc. */
|
||||
BKE_camera_params_compute_viewplane(
|
||||
¶ms, scene.r.xsch, scene.r.ysch, scene.r.xasp, scene.r.yasp);
|
||||
BKE_camera_params_compute_matrix(¶ms);
|
||||
|
||||
float4x4 viewmat = math::invert(cam_ob->object_to_world());
|
||||
return float4x4(params.winmat) * viewmat;
|
||||
}
|
||||
|
||||
void GreasePencilExporter::prepare_render_params(Scene &scene, const int frame_number)
|
||||
{
|
||||
const bool use_camera_view = (context_.rv3d->persp == RV3D_CAMOB) &&
|
||||
(context_.v3d->camera != nullptr);
|
||||
|
||||
/* Camera rectangle. */
|
||||
if ((context_.rv3d->persp == RV3D_CAMOB) || (use_camera_view)) {
|
||||
BKE_render_resolution(&scene.r, false, &render_size_.x, &render_size_.y);
|
||||
|
||||
if (use_camera_view) {
|
||||
rctf camera_rect;
|
||||
ED_view3d_calc_camera_border(&scene,
|
||||
context_.depsgraph,
|
||||
context_.region,
|
||||
context_.v3d,
|
||||
context_.rv3d,
|
||||
&camera_rect_,
|
||||
&camera_rect,
|
||||
true);
|
||||
is_camera_ = true;
|
||||
camera_ratio_ = render_size_.x / (camera_rect_.xmax - camera_rect_.xmin);
|
||||
offset_.x = camera_rect_.xmin;
|
||||
offset_.y = camera_rect_.ymin;
|
||||
render_rect_ = {{camera_rect.xmin, camera_rect.ymin}, {camera_rect.xmax, camera_rect.ymax}};
|
||||
camera_persmat_ = persmat_from_camera_object(scene);
|
||||
}
|
||||
else {
|
||||
is_camera_ = false;
|
||||
|
||||
Vector<ObjectInfo> objects = this->retrieve_objects();
|
||||
std::optional<Bounds<float2>> full_bounds = compute_objects_bounds(
|
||||
*context_.region, *context_.rv3d, *context_.depsgraph, objects, frame_number);
|
||||
if (full_bounds) {
|
||||
render_size_ = int2(full_bounds->size());
|
||||
offset_ = full_bounds->min;
|
||||
}
|
||||
else {
|
||||
camera_ratio_ = 1.0f;
|
||||
offset_ = float2(0);
|
||||
}
|
||||
render_rect_ = full_bounds ? *full_bounds : Bounds<float2>{float2(0.0f), float2(0.0f)};
|
||||
camera_persmat_ = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,7 +385,7 @@ Vector<GreasePencilExporter::ObjectInfo> GreasePencilExporter::retrieve_objects(
|
||||
const float3 position = object->object_to_world().location();
|
||||
|
||||
/* Save z-depth from view to sort from back to front. */
|
||||
const bool use_ortho_depth = is_camera_ || !context_.rv3d->is_persp;
|
||||
const bool use_ortho_depth = camera_persmat_ || !context_.rv3d->is_persp;
|
||||
const float depth = use_ortho_depth ? math::dot(camera_z_axis, position) :
|
||||
-ED_view3d_calc_zfac(context_.rv3d, position);
|
||||
objects.append({object, depth});
|
||||
@@ -562,17 +553,26 @@ void GreasePencilExporter::foreach_stroke_in_layer(const Object &object,
|
||||
float2 GreasePencilExporter::project_to_screen(const float4x4 &transform,
|
||||
const float3 &position) const
|
||||
{
|
||||
float2 screen_co = float2(0.0f);
|
||||
if (ED_view3d_project_float_ex(context_.region,
|
||||
const_cast<float(*)[4]>(context_.rv3d->winmat),
|
||||
false,
|
||||
math::transform_point(transform, position),
|
||||
screen_co,
|
||||
V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK)
|
||||
{
|
||||
return screen_co;
|
||||
const float3 world_pos = math::transform_point(transform, position);
|
||||
|
||||
if (camera_persmat_) {
|
||||
/* Use camera render space. */
|
||||
return (float2(math::project_point(*camera_persmat_, world_pos)) + 1.0f) / 2.0f *
|
||||
float2(render_rect_.size());
|
||||
}
|
||||
return float2(0.0f);
|
||||
|
||||
/* Use 3D view screen space. */
|
||||
float2 screen_co;
|
||||
if (ED_view3d_project_float_global(context_.region, world_pos, screen_co, V3D_PROJ_TEST_NOP) ==
|
||||
V3D_PROJ_RET_OK)
|
||||
{
|
||||
if (!ELEM(V2D_IS_CLIPPED, screen_co.x, screen_co.y)) {
|
||||
/* Apply offset and scale. */
|
||||
return screen_co - render_rect_.min;
|
||||
}
|
||||
}
|
||||
|
||||
return float2(V2D_IS_CLIPPED);
|
||||
}
|
||||
|
||||
} // namespace blender::io::grease_pencil
|
||||
|
||||
@@ -80,7 +80,7 @@ bool PDFExporter::export_scene(Scene &scene, StringRefNull filepath)
|
||||
case ExportParams::FrameMode::Active: {
|
||||
const int frame_number = scene.r.cfra;
|
||||
|
||||
this->prepare_camera_params(scene, frame_number, true);
|
||||
this->prepare_render_params(scene, frame_number);
|
||||
this->add_page();
|
||||
this->export_grease_pencil_objects(frame_number);
|
||||
result = this->write_to_file(filepath);
|
||||
@@ -99,7 +99,7 @@ bool PDFExporter::export_scene(Scene &scene, StringRefNull filepath)
|
||||
scene.r.cfra = frame_number;
|
||||
BKE_scene_graph_update_for_newframe(context_.depsgraph);
|
||||
|
||||
this->prepare_camera_params(scene, frame_number, true);
|
||||
this->prepare_render_params(scene, frame_number);
|
||||
this->add_page();
|
||||
this->export_grease_pencil_objects(frame_number);
|
||||
}
|
||||
@@ -135,11 +135,11 @@ void PDFExporter::export_grease_pencil_objects(const int frame_number)
|
||||
|
||||
for (const bke::greasepencil::Layer *layer : grease_pencil_eval->layers()) {
|
||||
if (!layer->is_visible()) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
const Drawing *drawing = grease_pencil_eval->get_drawing_at(*layer, frame_number);
|
||||
if (drawing == nullptr) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
export_grease_pencil_layer(*ob_eval, *layer, *drawing);
|
||||
@@ -154,8 +154,6 @@ void PDFExporter::export_grease_pencil_layer(const Object &object,
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
const float4x4 layer_to_world = layer.to_world_space(object);
|
||||
const float4x4 viewmat = float4x4(context_.rv3d->viewmat);
|
||||
const float4x4 layer_to_view = viewmat * layer_to_world;
|
||||
|
||||
auto write_stroke = [&](const Span<float3> positions,
|
||||
const bool cyclic,
|
||||
@@ -164,7 +162,7 @@ void PDFExporter::export_grease_pencil_layer(const Object &object,
|
||||
const std::optional<float> width,
|
||||
const bool /*round_cap*/,
|
||||
const bool /*is_outline*/) {
|
||||
write_stroke_to_polyline(layer_to_view, positions, cyclic, color, opacity, width);
|
||||
write_stroke_to_polyline(layer_to_world, positions, cyclic, color, opacity, width);
|
||||
};
|
||||
|
||||
foreach_stroke_in_layer(object, layer, drawing, write_stroke);
|
||||
@@ -192,8 +190,8 @@ bool PDFExporter::add_page()
|
||||
return false;
|
||||
}
|
||||
|
||||
HPDF_Page_SetWidth(page_, render_size_.x);
|
||||
HPDF_Page_SetHeight(page_, render_size_.y);
|
||||
HPDF_Page_SetWidth(page_, render_rect_.size().x);
|
||||
HPDF_Page_SetHeight(page_, render_rect_.size().y);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -221,14 +219,14 @@ void PDFExporter::write_stroke_to_polyline(const float4x4 &transform,
|
||||
HPDF_Page_SetRGBFill(page_, srgb.r, srgb.g, srgb.b);
|
||||
HPDF_Page_SetRGBStroke(page_, srgb.r, srgb.g, srgb.b);
|
||||
if (gstate) {
|
||||
HPDF_ExtGState_SetAlphaFill(gstate, std::clamp(opacity, 0.0f, 1.0f));
|
||||
HPDF_ExtGState_SetAlphaStroke(gstate, std::clamp(opacity, 0.0f, 1.0f));
|
||||
HPDF_ExtGState_SetAlphaFill(gstate, std::clamp(total_opacity, 0.0f, 1.0f));
|
||||
HPDF_ExtGState_SetAlphaStroke(gstate, std::clamp(total_opacity, 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
else {
|
||||
HPDF_Page_SetRGBFill(page_, srgb.r, srgb.g, srgb.b);
|
||||
if (gstate) {
|
||||
HPDF_ExtGState_SetAlphaFill(gstate, std::clamp(opacity, 0.0f, 1.0f));
|
||||
HPDF_ExtGState_SetAlphaFill(gstate, std::clamp(total_opacity, 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
if (gstate) {
|
||||
|
||||
@@ -139,7 +139,7 @@ bool SVGExporter::export_scene(Scene &scene, StringRefNull filepath)
|
||||
{
|
||||
const int frame_number = scene.r.cfra;
|
||||
|
||||
this->prepare_camera_params(scene, frame_number, false);
|
||||
this->prepare_render_params(scene, frame_number);
|
||||
|
||||
this->write_document_header();
|
||||
pugi::xml_node main_node = this->write_main_node();
|
||||
@@ -152,7 +152,7 @@ void SVGExporter::export_grease_pencil_objects(pugi::xml_node node, const int fr
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
const bool is_clipping = is_camera_ && params_.use_clip_camera;
|
||||
const bool is_clipping = camera_persmat_ && params_.use_clip_camera;
|
||||
|
||||
Vector<ObjectInfo> objects = retrieve_objects();
|
||||
|
||||
@@ -165,7 +165,7 @@ void SVGExporter::export_grease_pencil_objects(pugi::xml_node node, const int fr
|
||||
clip_node.append_attribute("id").set_value(
|
||||
("clip-path" + std::to_string(frame_number)).c_str());
|
||||
|
||||
write_rect(clip_node, 0, 0, render_size_.x, render_size_.y, 0.0f, "#000000");
|
||||
write_rect(clip_node, 0, 0, render_rect_.size().x, render_rect_.size().y, 0.0f, "#000000");
|
||||
}
|
||||
|
||||
pugi::xml_node frame_node = node.append_child("g");
|
||||
@@ -191,11 +191,11 @@ void SVGExporter::export_grease_pencil_objects(pugi::xml_node node, const int fr
|
||||
|
||||
for (const bke::greasepencil::Layer *layer : grease_pencil_eval->layers()) {
|
||||
if (!layer->is_visible()) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
const Drawing *drawing = grease_pencil_eval->get_drawing_at(*layer, frame_number);
|
||||
if (drawing == nullptr) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Layer node. */
|
||||
@@ -218,10 +218,6 @@ void SVGExporter::export_grease_pencil_layer(pugi::xml_node layer_node,
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
const float4x4 layer_to_world = layer.to_world_space(object);
|
||||
const float4x4 viewmat = float4x4(context_.rv3d->viewmat);
|
||||
/* SVG has inverted Y axis. */
|
||||
const float4x4 svg_coords = math::from_scale<float4x4>(float3(1, -1, 1));
|
||||
const float4x4 layer_to_view = svg_coords * viewmat * layer_to_world;
|
||||
|
||||
auto write_stroke = [&](const Span<float3> positions,
|
||||
const bool cyclic,
|
||||
@@ -231,14 +227,14 @@ void SVGExporter::export_grease_pencil_layer(pugi::xml_node layer_node,
|
||||
const bool round_cap,
|
||||
const bool is_outline) {
|
||||
if (is_outline) {
|
||||
pugi::xml_node element_node = write_path(layer_node, layer_to_view, positions, cyclic);
|
||||
pugi::xml_node element_node = write_path(layer_node, layer_to_world, positions, cyclic);
|
||||
write_fill_color_attribute(element_node, color, opacity);
|
||||
}
|
||||
else {
|
||||
/* Fill is always exported as polygon because the stroke of the fill is done
|
||||
* in a different SVG command. */
|
||||
pugi::xml_node element_node = write_polyline(
|
||||
layer_node, layer_to_view, positions, cyclic, width);
|
||||
layer_node, layer_to_world, positions, cyclic, width);
|
||||
|
||||
if (width) {
|
||||
write_stroke_color_attribute(element_node, color, opacity, round_cap);
|
||||
@@ -278,8 +274,8 @@ pugi::xml_node SVGExporter::write_main_node()
|
||||
main_node.append_attribute("y").set_value("0px");
|
||||
main_node.append_attribute("xmlns").set_value("http://www.w3.org/2000/svg");
|
||||
|
||||
std::string width = std::to_string(render_size_.x);
|
||||
std::string height = std::to_string(render_size_.y);
|
||||
std::string width = std::to_string(render_rect_.size().x);
|
||||
std::string height = std::to_string(render_rect_.size().y);
|
||||
|
||||
main_node.append_attribute("width").set_value((width + "px").c_str());
|
||||
main_node.append_attribute("height").set_value((height + "px").c_str());
|
||||
@@ -300,8 +296,10 @@ pugi::xml_node SVGExporter::write_polygon(pugi::xml_node node,
|
||||
if (i > 0) {
|
||||
txt.append(" ");
|
||||
}
|
||||
/* SVG has inverted Y axis. */
|
||||
const float2 screen_co = this->project_to_screen(transform, positions[i]);
|
||||
txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
|
||||
txt.append(std::to_string(screen_co.x) + "," +
|
||||
std::to_string(render_rect_.size().y - screen_co.y));
|
||||
}
|
||||
|
||||
element_node.append_attribute("points").set_value(txt.c_str());
|
||||
@@ -326,8 +324,10 @@ pugi::xml_node SVGExporter::write_polyline(pugi::xml_node node,
|
||||
if (i > 0) {
|
||||
txt.append(" ");
|
||||
}
|
||||
/* SVG has inverted Y axis. */
|
||||
const float2 screen_co = this->project_to_screen(transform, positions[i]);
|
||||
txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
|
||||
txt.append(std::to_string(screen_co.x) + "," +
|
||||
std::to_string(render_rect_.size().y - screen_co.y));
|
||||
}
|
||||
|
||||
element_node.append_attribute("points").set_value(txt.c_str());
|
||||
@@ -348,7 +348,9 @@ pugi::xml_node SVGExporter::write_path(pugi::xml_node node,
|
||||
txt.append("L");
|
||||
}
|
||||
const float2 screen_co = this->project_to_screen(transform, positions[i]);
|
||||
txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
|
||||
/* SVG has inverted Y axis. */
|
||||
txt.append(std::to_string(screen_co.x) + "," +
|
||||
std::to_string(render_rect_.size().y - screen_co.y));
|
||||
}
|
||||
/* Close patch (cyclic). */
|
||||
if (cyclic) {
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_bounds.hh"
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DNA_vec_types.h"
|
||||
|
||||
#include "grease_pencil_io.hh"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -58,20 +57,14 @@ class GreasePencilExporter {
|
||||
const IOContext context_;
|
||||
const ExportParams params_;
|
||||
|
||||
/* Camera parameters. */
|
||||
float4x4 persmat_;
|
||||
int2 win_size_;
|
||||
int2 render_size_;
|
||||
bool is_camera_;
|
||||
float camera_ratio_;
|
||||
rctf camera_rect_;
|
||||
|
||||
float2 offset_;
|
||||
/* Camera projection matrix, only available with an active camera. */
|
||||
std::optional<float4x4> camera_persmat_;
|
||||
blender::Bounds<float2> render_rect_;
|
||||
|
||||
public:
|
||||
GreasePencilExporter(const IOContext &context, const ExportParams ¶ms);
|
||||
|
||||
void prepare_camera_params(Scene &scene, int frame_number, bool force_camera_view);
|
||||
void prepare_render_params(Scene &scene, int frame_number);
|
||||
|
||||
static ColorGeometry4f compute_average_stroke_color(const Material &material,
|
||||
const Span<ColorGeometry4f> vertex_colors);
|
||||
|
||||
Reference in New Issue
Block a user