|
|
@@ -32,6 +32,7 @@
|
|
|
#include "../../Include/RmlUi/Core/Dictionary.h"
|
|
|
#include "../../Include/RmlUi/Core/PropertyIdSet.h"
|
|
|
#include "../../Include/RmlUi/Core/TransformPrimitive.h"
|
|
|
+#include "../../Include/RmlUi/Core/TransformState.h"
|
|
|
#include <algorithm>
|
|
|
#include <limits>
|
|
|
#include "Clock.h"
|
|
|
@@ -105,7 +106,7 @@ static Pool< ElementMeta > element_meta_chunk_pool(200, true);
|
|
|
|
|
|
/// Constructs a new RmlUi element.
|
|
|
Element::Element(const String& tag) : tag(tag), relative_offset_base(0, 0), relative_offset_position(0, 0), absolute_offset(0, 0), scroll_offset(0, 0), content_offset(0, 0), content_box(0, 0),
|
|
|
-transform_state(), transform_state_perspective_dirty(true), transform_state_transform_dirty(true), transform_state_parent_transform_dirty(true), dirty_animation(false), dirty_transition(false)
|
|
|
+transform_state(), dirty_transform(false), dirty_perspective(false), dirty_animation(false), dirty_transition(false)
|
|
|
{
|
|
|
RMLUI_ASSERT(tag == StringUtilities::ToLower(tag));
|
|
|
parent = nullptr;
|
|
|
@@ -373,7 +374,7 @@ String Element::GetAddress(bool include_pseudo_classes, bool include_parents) co
|
|
|
if (include_parents && parent)
|
|
|
{
|
|
|
address += " < ";
|
|
|
- return address + parent->GetAddress(true, true);
|
|
|
+ return address + parent->GetAddress(include_pseudo_classes, true);
|
|
|
}
|
|
|
else
|
|
|
return address;
|
|
|
@@ -704,184 +705,47 @@ const TransformState *Element::GetTransformState() const noexcept
|
|
|
return transform_state.get();
|
|
|
}
|
|
|
|
|
|
-// Returns the TransformStates that are effective for this element.
|
|
|
-void Element::GetEffectiveTransformState(
|
|
|
- const TransformState **local_perspective,
|
|
|
- const TransformState **perspective,
|
|
|
- const TransformState **transform
|
|
|
-) const noexcept
|
|
|
+// Project a 2D point in pixel coordinates onto the element's plane.
|
|
|
+bool Element::Project(Vector2f& point) const noexcept
|
|
|
{
|
|
|
- if (local_perspective)
|
|
|
- {
|
|
|
- *local_perspective = nullptr;
|
|
|
- }
|
|
|
- if (perspective)
|
|
|
- {
|
|
|
- *perspective = nullptr;
|
|
|
- }
|
|
|
- if (transform)
|
|
|
- {
|
|
|
- *transform = nullptr;
|
|
|
- }
|
|
|
-
|
|
|
- const Element *perspective_node = nullptr, *transform_node = nullptr;
|
|
|
-
|
|
|
- // Find the TransformState to use for unprojecting.
|
|
|
- if (transform_state.get() && transform_state->GetLocalPerspective(nullptr))
|
|
|
- {
|
|
|
- if (local_perspective)
|
|
|
- {
|
|
|
- *local_perspective = transform_state.get();
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- const Element *node = nullptr;
|
|
|
- for (node = parent; node; node = node->parent)
|
|
|
- {
|
|
|
- if (node->transform_state.get() && node->transform_state->GetPerspective(nullptr))
|
|
|
- {
|
|
|
- if (perspective)
|
|
|
- {
|
|
|
- *perspective = node->transform_state.get();
|
|
|
- }
|
|
|
- perspective_node = node;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ if(!transform_state || !transform_state->GetTransform())
|
|
|
+ return true;
|
|
|
|
|
|
- // Find the TransformState to use for transforming.
|
|
|
- const Element *node = nullptr;
|
|
|
- for (node = this; node; node = node->parent)
|
|
|
- {
|
|
|
- if (node->transform_state.get() && node->transform_state->GetRecursiveTransform(nullptr))
|
|
|
- {
|
|
|
- if (transform)
|
|
|
- {
|
|
|
- *transform = node->transform_state.get();
|
|
|
- }
|
|
|
- transform_node = node;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
+ // The input point is in window coordinates. Need to find the projection of the point onto the current element plane,
|
|
|
+ // taking into account the full transform applied to the element.
|
|
|
|
|
|
-// Project a 2D point in pixel coordinates onto the element's plane.
|
|
|
-Vector2f Element::Project(const Vector2f& point) const noexcept
|
|
|
-{
|
|
|
- const Context *context = GetContext();
|
|
|
- if (!context)
|
|
|
+ if (const Matrix4f* inv_transform = transform_state->GetInverseTransform())
|
|
|
{
|
|
|
- return point;
|
|
|
- }
|
|
|
+ // Pick two points forming a line segment perpendicular to the window.
|
|
|
+ Vector4f window_points[2] = {{ point.x, point.y, -10, 1}, { point.x, point.y, 10, 1 }};
|
|
|
|
|
|
- const TransformState *local_perspective, *perspective, *transform;
|
|
|
- GetEffectiveTransformState(&local_perspective, &perspective, &transform);
|
|
|
+ // Project them into the local element space.
|
|
|
+ window_points[0] = *inv_transform * window_points[0];
|
|
|
+ window_points[1] = *inv_transform * window_points[1];
|
|
|
|
|
|
- Vector2i view_pos(0, 0);
|
|
|
- Vector2i view_size = context->GetDimensions();
|
|
|
+ Vector3f local_points[2] = {
|
|
|
+ window_points[0].PerspectiveDivide(),
|
|
|
+ window_points[1].PerspectiveDivide()
|
|
|
+ };
|
|
|
|
|
|
- // Compute the line segment for ray picking, one point on the near and one on the far plane.
|
|
|
- // These need to be in clip space coordinates ([-1; 1]³) so that we an unproject them.
|
|
|
- Vector3f line_segment[2] =
|
|
|
- {
|
|
|
- // When unprojected, the intersection point on the near plane
|
|
|
- Vector3f(
|
|
|
- (point.x - view_pos.x) / (0.5f * view_size.x) - 1.0f,
|
|
|
- (view_size.y - point.y - view_pos.y) / (0.5f * view_size.y) - 1.0f,
|
|
|
- -1
|
|
|
- ),
|
|
|
- // When unprojected, the intersection point on the far plane
|
|
|
- Vector3f(
|
|
|
- (point.x - view_pos.x) / (0.5f * view_size.x) - 1.0f,
|
|
|
- (view_size.y - point.y - view_pos.y) / (0.5f * view_size.y) - 1.0f,
|
|
|
- 1
|
|
|
- )
|
|
|
- };
|
|
|
+ // Construct a ray from the two projected points in the local space of the current element.
|
|
|
+ // Find the intersection with the z=0 plane to produce our destination point.
|
|
|
+ Vector3f ray = local_points[1] - local_points[0];
|
|
|
|
|
|
- // Find the TransformState to use for unprojecting.
|
|
|
- if (local_perspective)
|
|
|
- {
|
|
|
- TransformState::LocalPerspective the_local_perspective;
|
|
|
- local_perspective->GetLocalPerspective(&the_local_perspective);
|
|
|
- line_segment[0] = the_local_perspective.Unproject(line_segment[0]);
|
|
|
- line_segment[1] = the_local_perspective.Unproject(line_segment[1]);
|
|
|
- }
|
|
|
- else if (perspective)
|
|
|
- {
|
|
|
- TransformState::Perspective the_perspective;
|
|
|
- perspective->GetPerspective(&the_perspective);
|
|
|
- line_segment[0] = the_perspective.Unproject(line_segment[0]);
|
|
|
- line_segment[1] = the_perspective.Unproject(line_segment[1]);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- line_segment[0] = context->GetViewState().Unproject(line_segment[0]);
|
|
|
- line_segment[1] = context->GetViewState().Unproject(line_segment[1]);
|
|
|
- }
|
|
|
-
|
|
|
- // Compute three points on the context's corners to define the element's plane.
|
|
|
- // It may seem elegant to base this computation on the element's size, but
|
|
|
- // there are elements with zero length or height.
|
|
|
- Vector3f element_rect[3] =
|
|
|
- {
|
|
|
- // Top-left corner
|
|
|
- Vector3f(0, 0, 0),
|
|
|
- // Top-right corner
|
|
|
- Vector3f((float)view_size.x, 0, 0),
|
|
|
- // Bottom-left corner
|
|
|
- Vector3f(0, (float)view_size.y, 0)
|
|
|
- };
|
|
|
- // Transform by the correct matrix
|
|
|
- if (transform)
|
|
|
- {
|
|
|
- element_rect[0] = transform->Transform(element_rect[0]);
|
|
|
- element_rect[1] = transform->Transform(element_rect[1]);
|
|
|
- element_rect[2] = transform->Transform(element_rect[2]);
|
|
|
- }
|
|
|
-
|
|
|
- Vector3f u = line_segment[0] - line_segment[1];
|
|
|
- Vector3f v = element_rect[1] - element_rect[0];
|
|
|
- Vector3f w = element_rect[2] - element_rect[0];
|
|
|
-
|
|
|
- // Now compute the intersection point of the line segment and the element's rectangle.
|
|
|
- // This is based on the algorithm discussed at Wikipedia
|
|
|
- // (http://en.wikipedia.org/wiki/Line-plane_intersection).
|
|
|
- Matrix4f A = Matrix4f::FromColumns(
|
|
|
- Vector4f(u, 0),
|
|
|
- Vector4f(v, 0),
|
|
|
- Vector4f(w, 0),
|
|
|
- Vector4f(0, 0, 0, 1)
|
|
|
- );
|
|
|
- if (A.Invert())
|
|
|
- {
|
|
|
- Vector3f factors = A * (line_segment[0] - element_rect[0]);
|
|
|
- Vector3f intersection3d = element_rect[0] + v * factors[1] + w * factors[2];
|
|
|
- Vector3f projected;
|
|
|
- if (transform)
|
|
|
- {
|
|
|
- projected = transform->Untransform(intersection3d);
|
|
|
- //RMLUI_ASSERT(fabs(projected.z) < 0.0001);
|
|
|
- }
|
|
|
- else
|
|
|
+ // Only continue if we are not close to parallel with the plane.
|
|
|
+ if(std::abs(ray.z) > 1.0f)
|
|
|
{
|
|
|
- // FIXME: Is this correct?
|
|
|
- projected = intersection3d;
|
|
|
+ // Solving the line equation p = p0 + t*ray for t, knowing that p.z = 0, produces the following.
|
|
|
+ float t = -local_points[0].z / ray.z;
|
|
|
+ Vector3f p = local_points[0] + ray * t;
|
|
|
+
|
|
|
+ point = Vector2f(p.x, p.y);
|
|
|
+ return true;
|
|
|
}
|
|
|
- return Vector2f(projected.x, projected.y);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // The line segment is parallel to the element's plane.
|
|
|
- // Although, mathematically, it could also lie within the plane
|
|
|
- // (yielding infinitely many intersection points), we still
|
|
|
- // return a value that's pretty sure to not match anything,
|
|
|
- // since this case has nothing to do with the user `picking'
|
|
|
- // anything.
|
|
|
- float inf = std::numeric_limits< float >::infinity();
|
|
|
- return Vector2f(-inf, -inf);
|
|
|
}
|
|
|
+
|
|
|
+ // The transformation matrix is either singular, or the ray is parallel to the element's plane.
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
PropertiesIteratorView Element::IterateLocalProperties() const
|
|
|
@@ -1870,7 +1734,7 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
|
|
|
changed_properties.Contains(PropertyId::PerspectiveOriginX) ||
|
|
|
changed_properties.Contains(PropertyId::PerspectiveOriginY))
|
|
|
{
|
|
|
- DirtyTransformState(true, false, false);
|
|
|
+ DirtyTransformState(true, false);
|
|
|
}
|
|
|
|
|
|
// Check for `transform' and `transform-origin' changes
|
|
|
@@ -1879,7 +1743,7 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
|
|
|
changed_properties.Contains(PropertyId::TransformOriginY) ||
|
|
|
changed_properties.Contains(PropertyId::TransformOriginZ))
|
|
|
{
|
|
|
- DirtyTransformState(false, true, false);
|
|
|
+ DirtyTransformState(false, true);
|
|
|
}
|
|
|
|
|
|
// Check for `animation' changes
|
|
|
@@ -2060,6 +1924,10 @@ void Element::SetParent(Element* _parent)
|
|
|
style->DirtyInheritedProperties();
|
|
|
}
|
|
|
|
|
|
+ // The transform state may require recalculation.
|
|
|
+ if (transform_state || (parent && parent->transform_state))
|
|
|
+ DirtyTransformState(true, true);
|
|
|
+
|
|
|
SetOwnerDocument(parent ? parent->GetOwnerDocument() : nullptr);
|
|
|
}
|
|
|
|
|
|
@@ -2070,7 +1938,7 @@ void Element::DirtyOffset()
|
|
|
offset_dirty = true;
|
|
|
|
|
|
if(transform_state)
|
|
|
- DirtyTransformState(true, true, false);
|
|
|
+ DirtyTransformState(true, true);
|
|
|
|
|
|
// Not strictly true ... ?
|
|
|
for (size_t i = 0; i < children.size(); i++)
|
|
|
@@ -2512,125 +2380,105 @@ void Element::AdvanceAnimations()
|
|
|
|
|
|
|
|
|
|
|
|
-void Element::DirtyTransformState(bool perspective_changed, bool transform_changed, bool parent_transform_changed)
|
|
|
+void Element::DirtyTransformState(bool perspective_dirty, bool transform_dirty)
|
|
|
{
|
|
|
- for (size_t i = 0; i < children.size(); ++i)
|
|
|
- {
|
|
|
- children[i]->DirtyTransformState(false, false, transform_changed || parent_transform_changed);
|
|
|
- }
|
|
|
-
|
|
|
- if (perspective_changed)
|
|
|
- {
|
|
|
- this->transform_state_perspective_dirty = true;
|
|
|
- }
|
|
|
- if (transform_changed)
|
|
|
- {
|
|
|
- this->transform_state_transform_dirty = true;
|
|
|
- }
|
|
|
- if (parent_transform_changed)
|
|
|
- {
|
|
|
- this->transform_state_parent_transform_dirty = true;
|
|
|
- }
|
|
|
+ dirty_perspective |= perspective_dirty;
|
|
|
+ dirty_transform |= transform_dirty;
|
|
|
}
|
|
|
|
|
|
|
|
|
void Element::UpdateTransformState()
|
|
|
{
|
|
|
- if (!(transform_state_perspective_dirty || transform_state_transform_dirty || transform_state_parent_transform_dirty))
|
|
|
- {
|
|
|
+ if (!dirty_perspective && !dirty_transform)
|
|
|
return;
|
|
|
- }
|
|
|
|
|
|
const ComputedValues& computed = element_meta->computed_values;
|
|
|
|
|
|
- if (!computed.transform && computed.perspective <= 0)
|
|
|
- {
|
|
|
- transform_state.reset();
|
|
|
- transform_state_perspective_dirty = false;
|
|
|
- transform_state_transform_dirty = false;
|
|
|
- transform_state_parent_transform_dirty = false;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
+ const Vector2f pos = GetAbsoluteOffset(Box::BORDER);
|
|
|
+ const Vector2f size = GetBox().GetSize(Box::BORDER);
|
|
|
+
|
|
|
+ bool perspective_or_transform_changed = false;
|
|
|
|
|
|
- if(transform_state_perspective_dirty || transform_state_transform_dirty)
|
|
|
+ if (dirty_perspective)
|
|
|
{
|
|
|
- Context *context = GetContext();
|
|
|
- Vector2f pos = GetAbsoluteOffset(Box::BORDER);
|
|
|
- Vector2f size = GetBox().GetSize(Box::BORDER);
|
|
|
+ // If perspective is set on this element, then it applies to our children. We just calculate it here,
|
|
|
+ // and let the children's transform update merge it with their transform.
|
|
|
+ bool had_perspective = (transform_state && transform_state->GetLocalPerspective());
|
|
|
|
|
|
+ float distance = computed.perspective;
|
|
|
+ Vector2f vanish = Vector2f(pos.x + size.x * 0.5f, pos.y + size.y * 0.5f);
|
|
|
+ bool have_perspective = false;
|
|
|
|
|
|
- if (transform_state_perspective_dirty)
|
|
|
+ if (distance > 0.0f)
|
|
|
{
|
|
|
- bool have_perspective = false;
|
|
|
- TransformState::Perspective perspective_value;
|
|
|
+ have_perspective = true;
|
|
|
|
|
|
- perspective_value.vanish = Vector2f(pos.x + size.x * 0.5f, pos.y + size.y * 0.5f);
|
|
|
+ // Compute the vanishing point from the perspective origin
|
|
|
+ if (computed.perspective_origin_x.type == Style::PerspectiveOrigin::Percentage)
|
|
|
+ vanish.x = pos.x + computed.perspective_origin_x.value * 0.01f * size.x;
|
|
|
+ else
|
|
|
+ vanish.x = pos.x + computed.perspective_origin_x.value;
|
|
|
|
|
|
- if (computed.perspective > 0.0f)
|
|
|
- {
|
|
|
- have_perspective = true;
|
|
|
+ if (computed.perspective_origin_y.type == Style::PerspectiveOrigin::Percentage)
|
|
|
+ vanish.y = pos.y + computed.perspective_origin_y.value * 0.01f * size.y;
|
|
|
+ else
|
|
|
+ vanish.y = pos.y + computed.perspective_origin_y.value;
|
|
|
+ }
|
|
|
|
|
|
- // Compute the perspective value
|
|
|
- perspective_value.distance = computed.perspective;
|
|
|
+ if (have_perspective)
|
|
|
+ {
|
|
|
+ // Equivalent to: Translate(x,y,0) * Perspective(distance) * Translate(-x,-y,0)
|
|
|
+ Matrix4f perspective = Matrix4f::FromRows(
|
|
|
+ { 1, 0, -vanish.x / distance, 0 },
|
|
|
+ { 0, 1, -vanish.y / distance, 0 },
|
|
|
+ { 0, 0, 1, 0 },
|
|
|
+ { 0, 0, -1 / distance, 1 }
|
|
|
+ );
|
|
|
|
|
|
- // Compute the perspective origin, if necessary
|
|
|
- if (computed.perspective_origin_x.type == Style::PerspectiveOrigin::Percentage)
|
|
|
- perspective_value.vanish.x = pos.x + computed.perspective_origin_x.value * 0.01f * size.x;
|
|
|
- else
|
|
|
- perspective_value.vanish.x = pos.x + computed.perspective_origin_x.value;
|
|
|
+ if (!transform_state)
|
|
|
+ transform_state = std::make_unique<TransformState>();
|
|
|
|
|
|
- if (computed.perspective_origin_y.type == Style::PerspectiveOrigin::Percentage)
|
|
|
- perspective_value.vanish.y = pos.y + computed.perspective_origin_y.value * 0.01f * size.y;
|
|
|
- else
|
|
|
- perspective_value.vanish.y = pos.y + computed.perspective_origin_y.value;
|
|
|
- }
|
|
|
+ perspective_or_transform_changed |= transform_state->SetLocalPerspective(&perspective);
|
|
|
+ }
|
|
|
+ else if (transform_state)
|
|
|
+ transform_state->SetLocalPerspective(nullptr);
|
|
|
|
|
|
- if (have_perspective && context)
|
|
|
- {
|
|
|
- if (!transform_state)
|
|
|
- transform_state.reset(new TransformState);
|
|
|
- perspective_value.view_size = context->GetDimensions();
|
|
|
- transform_state->SetPerspective(&perspective_value);
|
|
|
- }
|
|
|
- else if (transform_state)
|
|
|
- {
|
|
|
- transform_state->SetPerspective(nullptr);
|
|
|
- }
|
|
|
+ perspective_or_transform_changed |= (have_perspective != had_perspective);
|
|
|
|
|
|
- transform_state_perspective_dirty = false;
|
|
|
- }
|
|
|
+ dirty_perspective = false;
|
|
|
+ }
|
|
|
|
|
|
- if (transform_state_transform_dirty)
|
|
|
- {
|
|
|
- bool have_local_perspective = false;
|
|
|
- TransformState::LocalPerspective local_perspective;
|
|
|
|
|
|
- bool have_transform = false;
|
|
|
- Matrix4f transform_value = Matrix4f::Identity();
|
|
|
- Vector3f transform_origin(pos.x + size.x * 0.5f, pos.y + size.y * 0.5f, 0);
|
|
|
+ if (dirty_transform)
|
|
|
+ {
|
|
|
+ // We want to find the combined transform of all our ancestors. It is assumed here that the parent transform is already updated,
|
|
|
+ // so that we only need to consider our local transform and combine it with our parent's transform and perspective matrices.
|
|
|
+ bool had_transform = (transform_state && transform_state->GetTransform());
|
|
|
+
|
|
|
+ bool have_transform = false;
|
|
|
+ Matrix4f transform = Matrix4f::Identity();
|
|
|
|
|
|
- if (computed.transform)
|
|
|
+ if (computed.transform)
|
|
|
+ {
|
|
|
+ // First find the current element's transform
|
|
|
+ const int n = computed.transform->GetNumPrimitives();
|
|
|
+ for (int i = 0; i < n; ++i)
|
|
|
{
|
|
|
- int n = computed.transform->GetNumPrimitives();
|
|
|
- for (int i = 0; i < n; ++i)
|
|
|
- {
|
|
|
- const Transforms::Primitive &primitive = computed.transform->GetPrimitive(i);
|
|
|
-
|
|
|
- if (primitive.ResolvePerspective(local_perspective.distance, *this))
|
|
|
- {
|
|
|
- have_local_perspective = true;
|
|
|
- }
|
|
|
+ const Transforms::Primitive& primitive = computed.transform->GetPrimitive(i);
|
|
|
|
|
|
- Matrix4f matrix;
|
|
|
- if (primitive.ResolveTransform(matrix, *this))
|
|
|
- {
|
|
|
- transform_value *= matrix;
|
|
|
- have_transform = true;
|
|
|
- }
|
|
|
+ Matrix4f matrix;
|
|
|
+ if (primitive.ResolveTransform(matrix, *this))
|
|
|
+ {
|
|
|
+ transform *= matrix;
|
|
|
+ have_transform = true;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
+ if(have_transform)
|
|
|
+ {
|
|
|
// Compute the transform origin
|
|
|
+ Vector3f transform_origin(pos.x + size.x * 0.5f, pos.y + size.y * 0.5f, 0);
|
|
|
+
|
|
|
if (computed.transform_origin_x.type == Style::TransformOrigin::Percentage)
|
|
|
transform_origin.x = pos.x + computed.transform_origin_x.value * size.x * 0.01f;
|
|
|
else
|
|
|
@@ -2642,82 +2490,56 @@ void Element::UpdateTransformState()
|
|
|
transform_origin.y = pos.y + computed.transform_origin_y.value;
|
|
|
|
|
|
transform_origin.z = computed.transform_origin_z;
|
|
|
- }
|
|
|
-
|
|
|
- if (have_local_perspective && context)
|
|
|
- {
|
|
|
- if (!transform_state)
|
|
|
- transform_state.reset(new TransformState);
|
|
|
- local_perspective.view_size = context->GetDimensions();
|
|
|
- transform_state->SetLocalPerspective(&local_perspective);
|
|
|
- }
|
|
|
- else if(transform_state)
|
|
|
- {
|
|
|
- transform_state->SetLocalPerspective(nullptr);
|
|
|
- }
|
|
|
|
|
|
- if (have_transform)
|
|
|
- {
|
|
|
- // TODO: If we're using the global projection matrix
|
|
|
- // (perspective < 0), then scale the coordinates from
|
|
|
- // pixel space to 3D unit space.
|
|
|
-
|
|
|
- // Transform the RmlUi context so that the computed `transform_origin'
|
|
|
- // lies at the coordinate system origin.
|
|
|
- transform_value =
|
|
|
- Matrix4f::Translate(transform_origin)
|
|
|
- * transform_value
|
|
|
- * Matrix4f::Translate(-transform_origin);
|
|
|
-
|
|
|
- if (!transform_state)
|
|
|
- transform_state.reset(new TransformState);
|
|
|
- transform_state->SetTransform(&transform_value);
|
|
|
- }
|
|
|
- else if (transform_state)
|
|
|
- {
|
|
|
- transform_state->SetTransform(nullptr);
|
|
|
+ // Make the transformation apply relative to the transform origin
|
|
|
+ transform = Matrix4f::Translate(transform_origin) * transform * Matrix4f::Translate(-transform_origin);
|
|
|
}
|
|
|
|
|
|
- transform_state_transform_dirty = false;
|
|
|
+ // We may want to include the local offsets here, as suggested by the CSS specs, so that the local transform is applied after the offset I believe
|
|
|
+ // the motivation is. Then we would need to subtract the absolute zero-offsets during geometry submit whenever we have transforms.
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
-
|
|
|
- if (transform_state_parent_transform_dirty)
|
|
|
- {
|
|
|
- // We need to clean up from the top-most to the bottom-most dirt.
|
|
|
- if (parent)
|
|
|
+ if (parent && parent->transform_state)
|
|
|
{
|
|
|
- parent->UpdateTransformState();
|
|
|
- }
|
|
|
+ // Apply the parent's local perspective and transform.
|
|
|
+ // @performance: If we have no local transform and no parent perspective, we can effectively just point to the parent transform instead of copying it.
|
|
|
+ const TransformState& parent_state = *parent->transform_state;
|
|
|
|
|
|
- if (transform_state)
|
|
|
- {
|
|
|
- // Store the parent's new full transform as our parent transform
|
|
|
- Element *node = nullptr;
|
|
|
- Matrix4f parent_transform;
|
|
|
- for (node = parent; node; node = node->parent)
|
|
|
+ if (auto parent_perspective = parent_state.GetLocalPerspective())
|
|
|
{
|
|
|
- if (node->GetTransformState() && node->GetTransformState()->GetRecursiveTransform(&parent_transform))
|
|
|
- {
|
|
|
- transform_state->SetParentRecursiveTransform(&parent_transform);
|
|
|
- break;
|
|
|
- }
|
|
|
+ transform = *parent_perspective * transform;
|
|
|
+ have_transform = true;
|
|
|
}
|
|
|
- if (!node)
|
|
|
+
|
|
|
+ if (auto parent_transform = parent_state.GetTransform())
|
|
|
{
|
|
|
- transform_state->SetParentRecursiveTransform(nullptr);
|
|
|
+ transform = *parent_transform * transform;
|
|
|
+ have_transform = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- transform_state_parent_transform_dirty = false;
|
|
|
+ if (have_transform)
|
|
|
+ {
|
|
|
+ if (!transform_state)
|
|
|
+ transform_state = std::make_unique<TransformState>();
|
|
|
+
|
|
|
+ perspective_or_transform_changed |= transform_state->SetTransform(&transform);
|
|
|
+ }
|
|
|
+ else if (transform_state)
|
|
|
+ transform_state->SetTransform(nullptr);
|
|
|
+
|
|
|
+ perspective_or_transform_changed |= (had_transform != have_transform);
|
|
|
+ }
|
|
|
+
|
|
|
+ // A change in perspective or transform will require an update to children transforms as well.
|
|
|
+ if (perspective_or_transform_changed)
|
|
|
+ {
|
|
|
+ for (size_t i = 0; i < children.size(); i++)
|
|
|
+ children[i]->DirtyTransformState(false, true);
|
|
|
}
|
|
|
|
|
|
- // If we neither have a local perspective, nor a perspective nor a
|
|
|
- // transform, we don't need to keep the large TransformState object
|
|
|
- // around. GetEffectiveTransformState() will then recursively visit
|
|
|
- // parents in order to find a non-trivial TransformState.
|
|
|
- if (transform_state && !transform_state->GetLocalPerspective(nullptr) && !transform_state->GetPerspective(nullptr) && !transform_state->GetTransform(nullptr))
|
|
|
+ // No reason to keep the transform state around if transform and perspective have been removed.
|
|
|
+ if (transform_state && !transform_state->GetTransform() && !transform_state->GetLocalPerspective())
|
|
|
{
|
|
|
transform_state.reset();
|
|
|
}
|