Browse Source

ElementUtilities: Add procedure for determining element bounding box with projection

Michael Ragazzon 2 years ago
parent
commit
76b855652e
2 changed files with 69 additions and 0 deletions
  1. 7 0
      Include/RmlUi/Core/ElementUtilities.h
  2. 62 0
      Source/Core/ElementUtilities.cpp

+ 7 - 0
Include/RmlUi/Core/ElementUtilities.h

@@ -101,6 +101,13 @@ public:
 	/// @param[in] context The context to read the clip region from
 	/// @param[in] context The context to read the clip region from
 	static void ApplyActiveClipRegion(Context* context);
 	static void ApplyActiveClipRegion(Context* context);
 
 
+	/// Returns a rectangle covering the element's area in window coordinate space.
+	/// @param[in] out_rectangle The resulting rectangle covering the projected element's box.
+	/// @param[in] element The element to find the bounding box of.
+	/// @param[in] area The box area to consider.
+	/// @return True on success, otherwise false.
+	static bool GetBoundingBox(Rectanglef& out_rectangle, Element* element, BoxArea area);
+
 	/// Formats the contents of an element. This does not need to be called for ordinary elements, but can be useful
 	/// Formats the contents of an element. This does not need to be called for ordinary elements, but can be useful
 	/// for non-DOM elements of custom elements.
 	/// for non-DOM elements of custom elements.
 	/// @param[in] element The element to lay out.
 	/// @param[in] element The element to lay out.

+ 62 - 0
Source/Core/ElementUtilities.cpp

@@ -279,6 +279,68 @@ void ElementUtilities::ApplyActiveClipRegion(Context* context)
 	}
 	}
 }
 }
 
 
+bool ElementUtilities::GetBoundingBox(Rectanglef& out_rectangle, Element* element, BoxArea box_area)
+{
+	RMLUI_ASSERT(element);
+
+	if (box_area == BoxArea::Auto)
+		box_area = BoxArea::Border;
+
+	// Element bounds in non-transformed space.
+	const Rectanglef bounds = Rectanglef::FromPositionSize(element->GetAbsoluteOffset(box_area), element->GetBox().GetSize(box_area));
+
+	const TransformState* transform_state = element->GetTransformState();
+	const Matrix4f* transform = (transform_state ? transform_state->GetTransform() : nullptr);
+
+	// Early exit in the common case of no transform.
+	if (!transform)
+	{
+		out_rectangle = bounds;
+		return true;
+	}
+
+	Context* context = element->GetContext();
+	if (!context)
+		return false;
+
+	constexpr int num_corners = 4;
+	Vector2f corners[num_corners] = {
+		bounds.TopLeft(),
+		bounds.TopRight(),
+		bounds.BottomRight(),
+		bounds.BottomLeft(),
+	};
+
+	// Transform and project corners to window coordinates.
+	constexpr float z_clip = 10'000.f;
+	const Vector2f window_size = Vector2f(context->GetDimensions());
+	const Matrix4f project = Matrix4f::ProjectOrtho(0.f, window_size.x, 0.f, window_size.y, -z_clip, z_clip);
+	const Matrix4f project_transform = project * (*transform);
+	bool any_vertex_depth_clipped = false;
+
+	for (int i = 0; i < num_corners; i++)
+	{
+		const Vector4f pos_clip_space = project_transform * Vector4f(corners[i].x, corners[i].y, 0, 1);
+		const Vector2f pos_ndc = Vector2f(pos_clip_space.x, pos_clip_space.y) / pos_clip_space.w;
+		const Vector2f pos_viewport = 0.5f * window_size * (pos_ndc + Vector2f(1));
+		corners[i] = pos_viewport;
+		any_vertex_depth_clipped |= !(-pos_clip_space.w <= pos_clip_space.z && pos_clip_space.z <= pos_clip_space.w);
+	}
+
+	// If any part of the box area is outside the depth clip planes we give up finding the bounding box. In this situation a renderer would normally
+	// clip the underlying triangles against the clip planes. We could in principle do the same, but the added complexity does not seem worthwhile for
+	// our use cases.
+	if (any_vertex_depth_clipped)
+		return false;
+
+	// Find the rectangle covering the projected corners.
+	out_rectangle = Rectanglef::FromPosition(corners[0]);
+	for (int i = 1; i < num_corners; i++)
+		out_rectangle.Join(corners[i]);
+
+	return true;
+}
+
 void ElementUtilities::FormatElement(Element* element, Vector2f containing_block)
 void ElementUtilities::FormatElement(Element* element, Vector2f containing_block)
 {
 {
 	LayoutEngine::FormatElement(element, containing_block);
 	LayoutEngine::FormatElement(element, containing_block);