CPUParticles2D - fix interpolated transforms and culling

1) Physics interpolated particles in global mode are specified in global space. In VisualServer they should therefore ignore local transform.

2) Additionally, the expected final_transform should be passed on to children, rather than the identity transform used on the local item.

3) Local bounds in hierarchical culling are fixed for items using identity transform, by calculating their local bound in local space from the global space particles.
This commit is contained in:
lawnjelly 2023-08-22 09:30:32 +01:00
parent 17b403af81
commit 723632a76a
7 changed files with 80 additions and 19 deletions

View file

@ -732,7 +732,7 @@ void CanvasItem::set_canvas_item_use_identity_transform(bool p_enable) {
_set_use_identity_transform(p_enable); _set_use_identity_transform(p_enable);
// Let VisualServer know not to concatenate the parent transform during the render. // Let VisualServer know not to concatenate the parent transform during the render.
VisualServer::get_singleton()->canvas_item_set_ignore_parent_transform(get_canvas_item(), p_enable); VisualServer::get_singleton()->canvas_item_set_use_identity_transform(get_canvas_item(), p_enable);
if (is_inside_tree()) { if (is_inside_tree()) {
if (p_enable) { if (p_enable) {

View file

@ -1006,7 +1006,7 @@ public:
bool light_masked : 1; bool light_masked : 1;
bool on_interpolate_transform_list : 1; bool on_interpolate_transform_list : 1;
bool interpolated : 1; bool interpolated : 1;
bool ignore_parent_xform : 1; bool use_identity_xform : 1;
mutable bool custom_rect : 1; mutable bool custom_rect : 1;
mutable bool rect_dirty : 1; mutable bool rect_dirty : 1;
mutable bool bound_dirty : 1; mutable bool bound_dirty : 1;
@ -1262,7 +1262,7 @@ public:
update_when_visible = false; update_when_visible = false;
on_interpolate_transform_list = false; on_interpolate_transform_list = false;
interpolated = true; interpolated = true;
ignore_parent_xform = false; use_identity_xform = false;
local_bound_last_update_tick = 0; local_bound_last_update_tick = 0;
} }
virtual ~Item() { virtual ~Item() {

View file

@ -29,6 +29,7 @@
/**************************************************************************/ /**************************************************************************/
#include "visual_server_canvas.h" #include "visual_server_canvas.h"
#include "core/fixed_array.h"
#include "core/math/transform_interpolator.h" #include "core/math/transform_interpolator.h"
#include "visual_server_globals.h" #include "visual_server_globals.h"
#include "visual_server_raster.h" #include "visual_server_raster.h"
@ -277,10 +278,57 @@ void VisualServerCanvas::_calculate_canvas_item_bound(Item *p_canvas_item, Rect2
} }
} }
Transform2D VisualServerCanvas::_calculate_item_global_xform(const Item *p_canvas_item) {
// If we use more than the maximum scene tree depth, we are out of luck.
// But that would be super inefficient anyway.
FixedArray<const Transform2D *, 64> transforms;
while (p_canvas_item) {
// Should only happen if scene tree depth too high.
if (transforms.is_full()) {
WARN_PRINT_ONCE("SceneTree depth too high for hierarchical culling.");
break;
}
// Note this is only using the CURRENT transform.
// This may have implications for interpolated bounds - investigate.
transforms.push_back(&p_canvas_item->xform_curr);
if (canvas_item_owner.owns(p_canvas_item->parent)) {
p_canvas_item = canvas_item_owner.get(p_canvas_item->parent);
} else {
p_canvas_item = nullptr;
}
}
Transform2D tr;
for (int n = (int)transforms.size() - 1; n >= 0; n--) {
tr *= *transforms[n];
}
return tr;
}
void VisualServerCanvas::_finalize_and_merge_local_bound_to_branch(Item *p_canvas_item, Rect2 *r_branch_bound) { void VisualServerCanvas::_finalize_and_merge_local_bound_to_branch(Item *p_canvas_item, Rect2 *r_branch_bound) {
if (r_branch_bound) { if (r_branch_bound) {
Rect2 this_rect = p_canvas_item->get_rect(); Rect2 this_rect = p_canvas_item->get_rect();
// Special case .. if the canvas_item has use_identity_xform,
// we need to transform the rect from global space to local space,
// because the hierarchical culling expects local space.
if (p_canvas_item->use_identity_xform) {
// This is incredibly inefficient, but should only occur for e.g. CPUParticles2D,
// and is difficult to avoid because global transform is not usually kept track of
// in VisualServer (only final transform which is combinated with camera, and that
// is only calculated on render, so is no use for culling purposes).
Transform2D global_xform = _calculate_item_global_xform(p_canvas_item);
this_rect = global_xform.affine_inverse().xform(this_rect);
// Note that the efficiency will depend linearly on the scene tree depth of the
// identity transform item.
// So e.g. interpolated global CPUParticles2D may run faster at lower depths
// in extreme circumstances.
}
// If this item has a bound... // If this item has a bound...
if (!p_canvas_item->local_bound.has_no_area()) { if (!p_canvas_item->local_bound.has_no_area()) {
// If the rect has an area... // If the rect has an area...
@ -340,13 +388,19 @@ void VisualServerCanvas::_render_canvas_item_cull_by_item(Item *p_canvas_item, c
TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f); TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f);
} }
if (!p_canvas_item->ignore_parent_xform) { // Always calculate final transform as if not using identity xform.
final_xform = p_transform * final_xform; // This is so the expected transform is passed to children.
// However, if use_identity_xform is set,
// we can override the transform for rendering purposes for this item only.
final_xform = p_transform * final_xform;
Rect2 global_rect;
if (!p_canvas_item->use_identity_xform) {
global_rect = final_xform.xform(rect);
} else { } else {
final_xform = _current_camera_transform * final_xform; global_rect = _current_camera_transform.xform(rect);
} }
Rect2 global_rect = final_xform.xform(rect);
global_rect.position += p_clip_rect.position; global_rect.position += p_clip_rect.position;
if (ci->use_parent_material && p_material_owner) { if (ci->use_parent_material && p_material_owner) {
@ -422,7 +476,7 @@ void VisualServerCanvas::_render_canvas_item_cull_by_item(Item *p_canvas_item, c
if ((!ci->commands.empty() && p_clip_rect.intersects(global_rect, true)) || ci->vp_render || ci->copy_back_buffer) { if ((!ci->commands.empty() && p_clip_rect.intersects(global_rect, true)) || ci->vp_render || ci->copy_back_buffer) {
//something to draw? //something to draw?
ci->final_transform = final_xform; ci->final_transform = !p_canvas_item->use_identity_xform ? final_xform : _current_camera_transform;
ci->final_modulate = Color(modulate.r * ci->self_modulate.r, modulate.g * ci->self_modulate.g, modulate.b * ci->self_modulate.b, modulate.a * ci->self_modulate.a); ci->final_modulate = Color(modulate.r * ci->self_modulate.r, modulate.g * ci->self_modulate.g, modulate.b * ci->self_modulate.b, modulate.a * ci->self_modulate.a);
ci->global_rect_cache = global_rect; ci->global_rect_cache = global_rect;
ci->global_rect_cache.position -= p_clip_rect.position; ci->global_rect_cache.position -= p_clip_rect.position;
@ -481,15 +535,21 @@ void VisualServerCanvas::_render_canvas_item_cull_by_node(Item *p_canvas_item, c
TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f); TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f);
} }
if (!p_canvas_item->ignore_parent_xform) { // Always calculate final transform as if not using identity xform.
final_xform = p_transform * final_xform; // This is so the expected transform is passed to children.
// However, if use_identity_xform is set,
// we can override the transform for rendering purposes for this item only.
final_xform = p_transform * final_xform;
Rect2 global_rect;
if (!p_canvas_item->use_identity_xform) {
global_rect = final_xform.xform(rect);
} else { } else {
final_xform = _current_camera_transform * final_xform; global_rect = _current_camera_transform.xform(rect);
} }
Rect2 global_rect = final_xform.xform(rect);
ci->global_rect_cache = global_rect; ci->global_rect_cache = global_rect;
ci->final_transform = final_xform; ci->final_transform = !p_canvas_item->use_identity_xform ? final_xform : _current_camera_transform;
global_rect.position += p_clip_rect.position; global_rect.position += p_clip_rect.position;
@ -955,11 +1015,11 @@ void VisualServerCanvas::canvas_item_set_draw_behind_parent(RID p_item, bool p_e
_check_bound_integrity(canvas_item); _check_bound_integrity(canvas_item);
} }
void VisualServerCanvas::canvas_item_set_ignore_parent_transform(RID p_item, bool p_enable) { void VisualServerCanvas::canvas_item_set_use_identity_transform(RID p_item, bool p_enable) {
Item *canvas_item = canvas_item_owner.getornull(p_item); Item *canvas_item = canvas_item_owner.getornull(p_item);
ERR_FAIL_COND(!canvas_item); ERR_FAIL_COND(!canvas_item);
canvas_item->ignore_parent_xform = p_enable; canvas_item->use_identity_xform = p_enable;
_make_bound_dirty(canvas_item); _make_bound_dirty(canvas_item);
} }

View file

@ -181,6 +181,7 @@ private:
void _prepare_tree_bounds(Item *p_root); void _prepare_tree_bounds(Item *p_root);
void _calculate_canvas_item_bound(Item *p_canvas_item, Rect2 *r_branch_bound); void _calculate_canvas_item_bound(Item *p_canvas_item, Rect2 *r_branch_bound);
Transform2D _calculate_item_global_xform(const Item *p_canvas_item);
void _finalize_and_merge_local_bound_to_branch(Item *p_canvas_item, Rect2 *r_branch_bound); void _finalize_and_merge_local_bound_to_branch(Item *p_canvas_item, Rect2 *r_branch_bound);
void _merge_local_bound_to_branch(Item *p_canvas_item, Rect2 *r_branch_bound); void _merge_local_bound_to_branch(Item *p_canvas_item, Rect2 *r_branch_bound);
@ -227,7 +228,7 @@ public:
void canvas_item_set_self_modulate(RID p_item, const Color &p_color); void canvas_item_set_self_modulate(RID p_item, const Color &p_color);
void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable); void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable);
void canvas_item_set_ignore_parent_transform(RID p_item, bool p_enable); void canvas_item_set_use_identity_transform(RID p_item, bool p_enable);
void canvas_item_set_update_when_visible(RID p_item, bool p_update); void canvas_item_set_update_when_visible(RID p_item, bool p_update);

View file

@ -690,7 +690,7 @@ public:
BIND2(canvas_item_set_self_modulate, RID, const Color &) BIND2(canvas_item_set_self_modulate, RID, const Color &)
BIND2(canvas_item_set_draw_behind_parent, RID, bool) BIND2(canvas_item_set_draw_behind_parent, RID, bool)
BIND2(canvas_item_set_ignore_parent_transform, RID, bool) BIND2(canvas_item_set_use_identity_transform, RID, bool)
BIND6(canvas_item_add_line, RID, const Point2 &, const Point2 &, const Color &, float, bool) BIND6(canvas_item_add_line, RID, const Point2 &, const Point2 &, const Color &, float, bool)
BIND5(canvas_item_add_polyline, RID, const Vector<Point2> &, const Vector<Color> &, float, bool) BIND5(canvas_item_add_polyline, RID, const Vector<Point2> &, const Vector<Color> &, float, bool)

View file

@ -594,7 +594,7 @@ public:
FUNC2(canvas_item_set_self_modulate, RID, const Color &) FUNC2(canvas_item_set_self_modulate, RID, const Color &)
FUNC2(canvas_item_set_draw_behind_parent, RID, bool) FUNC2(canvas_item_set_draw_behind_parent, RID, bool)
FUNC2(canvas_item_set_ignore_parent_transform, RID, bool) FUNC2(canvas_item_set_use_identity_transform, RID, bool)
FUNC6(canvas_item_add_line, RID, const Point2 &, const Point2 &, const Color &, float, bool) FUNC6(canvas_item_add_line, RID, const Point2 &, const Point2 &, const Color &, float, bool)
FUNC5(canvas_item_add_polyline, RID, const Vector<Point2> &, const Vector<Color> &, float, bool) FUNC5(canvas_item_add_polyline, RID, const Vector<Point2> &, const Vector<Color> &, float, bool)

View file

@ -1024,7 +1024,7 @@ public:
virtual void canvas_item_set_self_modulate(RID p_item, const Color &p_color) = 0; virtual void canvas_item_set_self_modulate(RID p_item, const Color &p_color) = 0;
virtual void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable) = 0; virtual void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable) = 0;
virtual void canvas_item_set_ignore_parent_transform(RID p_item, bool p_enable) = 0; virtual void canvas_item_set_use_identity_transform(RID p_item, bool p_enable) = 0;
enum NinePatchAxisMode { enum NinePatchAxisMode {
NINE_PATCH_STRETCH, NINE_PATCH_STRETCH,