Browse Source

Completely revised transforms implementation. Please have a look at the readme and updated transform sample for details.

Michael Ragazzon 6 years ago
parent
commit
40f19fe151

+ 0 - 2
Cmake/FileList.cmake

@@ -195,7 +195,6 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector4.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector4.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vertex.h
-    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ViewState.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/XMLNodeHandler.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/XMLParser.h
 )
@@ -337,7 +336,6 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Variant.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Vector3.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Vector4.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/ViewState.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/WidgetSlider.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/WidgetSliderScroll.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/XMLNodeHandler.cpp

+ 1 - 15
Include/RmlUi/Core/Context.h

@@ -34,7 +34,6 @@
 #include "Traits.h"
 #include "Input.h"
 #include "ScriptInterface.h"
-#include "ViewState.h"
 
 namespace Rml {
 namespace Core {
@@ -73,9 +72,6 @@ public:
 	/// @return The current dimensions of the context.
 	const Vector2i& GetDimensions() const;
 
-	/// Returns the current state of the view.
-	const ViewState& GetViewState() const noexcept;
-
 	/// Changes the size ratio of 'dp' unit to 'px' unit
 	/// @param[in] dp_ratio The new density-independent pixel ratio of the context.
 	void SetDensityIndependentPixelRatio(float density_independent_pixel_ratio);
@@ -197,13 +193,6 @@ public:
 	/// @return True if the event was not consumed (ie, was prevented from propagating by an element), false if it was.
 	bool ProcessMouseWheel(float wheel_delta, int key_modifier_state);
 
-	/// Notifies RmlUi of a change in the projection matrix.
-	/// @param[in] projection The new projection matrix.
-	void ProcessProjectionChange(const Matrix4f &projection);
-	/// Notifies RmlUi of a change in the view matrix.
-	/// @param[in] projection The new view matrix.
-	void ProcessViewChange(const Matrix4f &view);
-
 	/// Gets the context's render interface.
 	/// @return The render interface the context renders through.
 	RenderInterface* GetRenderInterface() const;
@@ -286,9 +275,6 @@ private:
 	Vector2i clip_origin;
 	Vector2i clip_dimensions;
 
-	// The current view state
-	ViewState view_state;
-
 	// Internal callback for when an element is detached or removed from the hierarchy.
 	void OnElementDetach(Element* element);
 	// Internal callback for when a new element gains focus.
@@ -304,7 +290,7 @@ private:
 	// @param[in] ignore_element If set, this element and its descendents will be ignored.
 	// @param[in] element Used internally.
 	// @return The element under the point, or nullptr if nothing is.
-	Element* GetElementAtPoint(const Vector2f& point, const Element* ignore_element = nullptr, Element* element = nullptr);
+	Element* GetElementAtPoint(Vector2f point, const Element* ignore_element = nullptr, Element* element = nullptr);
 
 	// Creates the drag clone from the given element. The old drag clone will be released if
 	// necessary.

+ 8 - 10
Include/RmlUi/Core/Element.h

@@ -37,7 +37,6 @@
 #include "Property.h"
 #include "Types.h"
 #include "Transform.h"
-#include "TransformState.h"
 #include "Tween.h"
 
 #include <memory>
@@ -61,6 +60,7 @@ class PropertiesIteratorView;
 class FontFaceHandle;
 class PropertyDictionary;
 class RenderInterface;
+class TransformState;
 class StyleSheet;
 struct ElementMeta;
 
@@ -222,6 +222,7 @@ public:
 	/// Returns the local style properties, excluding any properties from local class.
 	/// @return The local properties for this element, or nullptr if no properties defined
 	const PropertyMap& GetLocalStyleProperties();
+
 	/// Resolves a property with units of length or percentage to 'px'. Percentages are resolved by scaling the base value.
 	/// @param[in] name The property to resolve the value for.
 	/// @param[in] base_value The value that is scaled by the percentage value, if it is a percentage.
@@ -241,12 +242,10 @@ public:
 
 	/// Returns this element's TransformState
 	const TransformState *GetTransformState() const noexcept;
-	/// Returns the TransformStates that are effective for this element.
-	void 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.
-	/// @param[in] point The point to project.
-	/// @return The projected coordinates.
-	Vector2f Project(const Vector2f& point) const noexcept;
+	/// @param[in-out] point The point to project in, and the resulting projected point out.
+	/// @return True on success, false if transformation matrix is singular.
+	bool Project(Vector2f& point) const noexcept;
 
 	/// Start an animation of the given property on this element.
 	/// If an animation of the same property name exists, it will be replaced.
@@ -628,7 +627,7 @@ private:
 	void DirtyStructure();
 	void UpdateStructure();
 
-	void DirtyTransformState(bool perspective_changed, bool transform_changed, bool parent_transform_changed);
+	void DirtyTransformState(bool perspective_dirty, bool transform_dirty);
 	void UpdateTransformState();
 
 	/// Start an animation, replacing any existing animations of the same property name. If start_value is null, the element's current value is used.
@@ -731,9 +730,8 @@ private:
 
 	// Transform state
 	UniquePtr< TransformState > transform_state;
-	bool transform_state_perspective_dirty;
-	bool transform_state_transform_dirty;
-	bool transform_state_parent_transform_dirty;
+	bool dirty_transform;
+	bool dirty_perspective;
 
 	ElementAnimationList animations;
 	bool dirty_animation;

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

@@ -39,7 +39,6 @@ namespace Core {
 class Context;
 class FontFaceHandle;
 class RenderInterface;
-class ViewState;
 namespace Style { struct ComputedValues; }
 
 /**

+ 4 - 2
Include/RmlUi/Core/Matrix4.h

@@ -423,8 +423,7 @@ class Matrix4
 			{ return MatrixMultiplier< Component, Storage, Storage2 >::Multiply(*this, other); }
 
 		/// Multiplies this matrix by another matrix in place.
-		/// @param[in] other The scalar value to multiply by.
-		/// @return The result of the scale.
+		/// @return The result of the multiplication.
 		inline const ThisType& operator*=(const ThisType& other) noexcept
 			{ *this = *this * other; return *this; }
 		inline const ThisType& operator*=(const TransposeType& other) noexcept
@@ -466,6 +465,9 @@ class Matrix4
 		/// @param f The depth coordinate of the far clipping plane
 		/// @return The specified perspective projection matrix.
 		static ThisType ProjectPerspective(Component l, Component r, Component b, Component t, Component n, Component f) noexcept;
+		/// Create a perspective projection matrix
+		/// @param d The distance to the z-plane
+		static ThisType Perspective(Component d) noexcept;
 
 		/// Return a translation matrix.
 		/// @return A translation matrix.

+ 12 - 0
Include/RmlUi/Core/Matrix4.inl

@@ -518,6 +518,18 @@ Matrix4< Component, Storage > Matrix4< Component, Storage >::ProjectPerspective(
 	);
 }
 
+// Create a perspective projection matrix
+template< typename Component, class Storage>
+Matrix4< Component, Storage > Matrix4< Component, Storage >::Perspective(Component d) noexcept
+{
+	return Matrix4< Component, Storage >::FromRows(
+		Matrix4< Component, Storage >::VectorType(1, 0, 0, 0),
+		Matrix4< Component, Storage >::VectorType(0, 1, 0, 0),
+		Matrix4< Component, Storage >::VectorType(0, 0, 1, 0),
+		Matrix4< Component, Storage >::VectorType(0, 0, -static_cast<Component>(1)/d, 1)
+	);
+}
+
 // Return a translation matrix.
 template< typename Component, class Storage>
 Matrix4< Component, Storage > Matrix4< Component, Storage >::Translate(const Vector3< Component >& v) noexcept

+ 4 - 0
Include/RmlUi/Core/Profiling.h

@@ -48,6 +48,8 @@
 #define RMLUI_ZoneText(x,y)        ZoneText(x,y)
 #define RMLUI_ZoneName(x,y)        ZoneName(x,y)
 
+#define RMLUI_TracyPlot(name,val)  TracyPlot(name,val)
+
 #define RMLUI_FrameMark            FrameMark
 #define RMLUI_FrameMarkNamed(x)    FrameMarkNamed(x)
 #define RMLUI_FrameMarkStart(x)    FrameMarkStart(x)
@@ -68,6 +70,8 @@
 #define RMLUI_ZoneText(x,y)
 #define RMLUI_ZoneName(x,y)
 
+#define RMLUI_TracyPlot(name,val)
+
 #define RMLUI_FrameMark
 #define RMLUI_FrameMarkNamed(x)
 #define RMLUI_FrameMarkStart(x)

+ 0 - 1
Include/RmlUi/Core/TransformPrimitive.h

@@ -334,7 +334,6 @@ struct RMLUICORE_API Primitive
 	void SetIdentity() noexcept;
 
 	bool ResolveTransform(Matrix4f& m, Element& e) const noexcept;
-	bool ResolvePerspective(float &p, Element& e) const noexcept;
 	
 	// Prepares this primitive for interpolation. This must be done before calling InterpolateWith().
 	// Promote units to basic types which can be interpolated, that is, convert 'length -> pixel' for unresolved primitives.

+ 25 - 86
Include/RmlUi/Core/TransformState.h

@@ -35,99 +35,38 @@
 namespace Rml {
 namespace Core {
 
-/**
-	A TransformState captures an element's current perspective and transform settings.
-
-	@author Markus Schöngart
- */
 
 class RMLUICORE_API TransformState
 {
 public:
-	struct Perspective
-	{
-		/// Calculates the projection matrix.
-		Matrix4f GetProjection() const noexcept;
-
-		/// Calculates the clip space coordinates ([-1; 1]³) of a 3D vertex in world space.
-		/// @param[in] point The point in world space coordinates.
-		/// @return The clip space coordinates of the point.
-		Vector3f Project(const Vector3f &point) const noexcept;
-		/// Calculates the world space coordinates of a 3D vertex in clip space ([-1; 1]³).
-		/// @param[in] point The point in clip space coordinates.
-		/// @return The world space coordinates of the point.
-		Vector3f Unproject(const Vector3f &point) const noexcept;
-	
-		float		distance;	// The CSS `perspective:' value
-		Vector2i	view_size;
-		Vector2f	vanish;		// The vanishing point, in [0; 1]²; Only relevant if distance > 0
-	};
-
-	struct LocalPerspective
-	{
-		/// Calculates the projection matrix.
-		Matrix4f GetProjection() const noexcept;
-
-		/// Calculates the clip space coordinates ([-1; 1]³) of a 3D vertex in world space.
-		/// @param[in] point The point in world space coordinates.
-		/// @return The clip space coordinates of the point.
-		Vector3f Project(const Vector3f &point) const noexcept;
-		/// Calculates the world space coordinates of a 3D vertex in clip space ([-1; 1]³).
-		/// @param[in] point The point in clip space coordinates.
-		/// @return The world space coordinates of the point.
-		Vector3f Unproject(const Vector3f &point) const noexcept;
-
-		float		distance;	// The CSS `perspective:' value
-		Vector2i	view_size;
-	};
-
-	TransformState();
-
-	/// Stores a new perspective value
-	void SetPerspective(const Perspective *perspective) noexcept;
-	/// Returns the perspective value
-	bool GetPerspective(Perspective *perspective) const noexcept;
-
-	/// Stores a new local perspective value
-	void SetLocalPerspective(const LocalPerspective *local_perspective) noexcept;
-	/// Returns the local perspective value
-	bool GetLocalPerspective(LocalPerspective *local_perspective) const noexcept;
-
-	/// Stores a new transform matrix
-	void SetTransform(const Matrix4f *transform) noexcept;
-	/// Returns the stored transform matrix
-	bool GetTransform(Matrix4f *transform) const noexcept;
-
-	/// Stores a new recursive parent transform.
-	void SetParentRecursiveTransform(const Matrix4f *parent_recursive_transform) noexcept;
-	/// Returns the stored recursive parent transform matrix
-	bool GetParentRecursiveTransform(Matrix4f *transform) const noexcept;
-
-	/// Transforms a 3D point by the `parent transform' and `transform' matrices stored in this TransformState.
-	/// @param[in] point The point in world space coordinates.
-	/// @return The transformed point in world space coordinates.
-	Vector3f Transform(const Vector3f &point) const noexcept;
-	/// Transforms a 3D point by the inverse `parent transform' and `transform' matrices stored in this TransformState.
-	/// @param[in] point The point in world space coordinates.
-	/// @return The transformed point in world space coordinates.
-	Vector3f Untransform(const Vector3f &point) const noexcept;
-
-	/// Returns the parent's recursive transform multiplied by this transform.
-	bool GetRecursiveTransform(Matrix4f *recursive_transform) const noexcept;
+
+	// Returns true if transform was changed.
+	bool SetTransform(const Matrix4f* in_transform);
+
+	// Returns true if local perspecitve was changed.
+	bool SetLocalPerspective(const Matrix4f* in_perspective);
+
+	const Matrix4f* GetTransform() const;
+	const Matrix4f* GetLocalPerspective() const;
+
+	// Returns a nullptr if there is no transform set, or the transform is singular.
+	const Matrix4f* GetInverseTransform() const;
+
 
 private:
-	// Flags for stored values
-	bool have_perspective;
-	bool have_local_perspective;
-	bool have_parent_recursive_transform;
-	bool have_transform;
-
-	// Stored values
-	float perspective, local_perspective;
-	Vector2i view_size;
-	Vector2f vanish;
-	Matrix4f parent_recursive_transform;
+	bool have_transform = false;
+	bool have_perspective = false;
+	mutable bool have_inverse_transform = false;
+	mutable bool dirty_inverse_transform = false;
+
+	// The transform combines all local transform and perspective properties of the owning element and all ancestors.
 	Matrix4f transform;
+
+	// Local perspective which applies to children of the owning element.
+	Matrix4f local_perspective;
+
+	// The inverse of the transform mainly for projecting points from screen-space to 2d-space, such as used for picking elements.
+	mutable Matrix4f inverse_transform;
 };
 
 }

+ 3 - 0
Include/RmlUi/Core/Vector3.h

@@ -126,6 +126,9 @@ class Vector3
 		/// @return A constant pointer to the first value.
 		inline operator Type*();
 
+		// Cast to Vector2
+		explicit inline operator Vector2< Type >() const;
+
 		// The components of the vector.
 		Type x;
 		Type y;

+ 6 - 0
Include/RmlUi/Core/Vector3.inl

@@ -207,5 +207,11 @@ Vector3< Type >::operator Type* ()
 	return &x;
 }
 
+template < typename Type >
+Vector3< Type >::operator Vector2< Type >() const
+{
+	return Vector2< Type >(x, y);
+}
+
 }
 }

+ 9 - 5
Include/RmlUi/Core/Vector4.h

@@ -52,9 +52,9 @@ class Vector4
 		/// @param[in] y Initial y-value of the vector.
 		/// @param[in] z Initial z-value of the vector.
 		/// @param[in] w Initial omega-value of the vector.
-		inline Vector4(Type x, Type y, Type z, Type w = 1);
+		inline Vector4(Type x, Type y, Type z, Type w);
 		/// Implicit conversion from a 3D Vector.
-		inline Vector4(Vector3< Type > const &v, Type w = 1);
+		inline Vector4(Vector3< Type > const &v, Type w);
 
 		/// Returns the magnitude of the vector.
 		/// @return The computed magnitude.
@@ -125,9 +125,13 @@ class Vector4
 		/// @return A constant pointer to the first value.
 		inline operator Type*();
 
-		/// Auto-cast operator to 3D vector.
-		/// @return Equivalent 3D vector.
-		inline operator Vector3< Type >() const;
+		/// Return a Vector3 after perspective divide
+		inline Vector3< Type > PerspectiveDivide() const;
+
+		/// Cast to Vector3
+		explicit inline operator Vector3< Type >() const;
+		/// Cast to Vector2
+		explicit inline operator Vector2< Type >() const;
 
 		// The components of the vector.
 		Type x;

+ 13 - 3
Include/RmlUi/Core/Vector4.inl

@@ -198,13 +198,23 @@ Vector4< Type >::operator Type* ()
 	return &x;
 }
 
-// Auto-cast operator to 3D vector.
-// @return Equivalent 3D vector.
 template < typename Type >
-Vector4< Type >::operator Vector3< Type >() const
+Vector3< Type > Vector4< Type >::PerspectiveDivide() const
 {
 	return Vector3< Type >(x / w, y / w, z / w);
 }
 
+template < typename Type >
+Vector4< Type >::operator Vector3< Type >() const
+{
+	return Vector3< Type >(x, y, z);
+}
+
+template < typename Type >
+Vector4< Type >::operator Vector2< Type >() const
+{
+	return Vector2< Type >(x, y);
+}
+
 }
 }

+ 0 - 87
Include/RmlUi/Core/ViewState.h

@@ -1,87 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2014 Markus Schöngart
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUICOREVIEWSTATE_H
-#define RMLUICOREVIEWSTATE_H
-
-#include "Header.h"
-#include "Types.h"
-
-namespace Rml {
-namespace Core {
-
-/**
-	A ViewState captures the current global projection and view matrices (`camera settings').
-
-	@author Markus Schöngart
- */
-
-class RMLUICORE_API ViewState
-{
-public:
-	ViewState();
-
-	/// Stores a new projection matrix
-	void SetProjection(const Matrix4f *projection) noexcept;
-
-	/// Stores a new view matrix
-	void SetView(const Matrix4f *view) noexcept;
-
-	/// Retrieves the cancellation matrix (projection * view)⁻¹
-	bool GetProjectionViewInv(Matrix4f& projection_view_inv) const noexcept;
-
-	/// Calculates the clip space coordinates ([-1; 1]³) of a 3D vertex in world space.
-	/// @param[in] point The point in world space coordinates.
-	/// @return The clip space coordinates of the point.
-	Vector3f Project(const Vector3f &point) const noexcept;
-	/// Calculates the world space coordinates of a 3D vertex in clip space ([-1; 1]³).
-	/// @param[in] point The point in clip space coordinates.
-	/// @return The world space coordinates of the point.
-	Vector3f Unproject(const Vector3f &point) const noexcept;
-
-private:
-	// Flags for stored values
-	bool have_projection;
-	bool have_view;
-
-	// Flags for cached values
-	mutable bool projection_view_inv_dirty;
-
-	// Stored values
-	Matrix4f projection;
-	Matrix4f view;
-
-	// Cached values
-	mutable Matrix4f projection_view_inv;
-	void UpdateProjectionViewInv() const noexcept;
-};
-
-}
-}
-
-#endif

+ 0 - 7
Samples/basic/directx10/src/ShellRenderInterfaceExtensionsDirectX10_Win32.cpp

@@ -92,13 +92,6 @@ void RenderInterfaceDirectX10::SetViewport(int width, int height)
 		if(m_rmlui_context != nullptr)
 		{
 			((Rml::Core::Context*)m_rmlui_context)->SetDimensions(Rml::Core::Vector2i(width, height));
-			Rml::Core::Matrix4f mat;
-			mat = m_matProjection;
-			mat = mat.Transpose();
-			((Rml::Core::Context*)m_rmlui_context)->ProcessProjectionChange(mat);
-			mat = m_matWorld;
-			mat = mat.Transpose();
-			((Rml::Core::Context*)m_rmlui_context)->ProcessViewChange(mat);
 		}
 	}
 }

+ 15 - 14
Samples/basic/transform/data/transform.rml

@@ -7,8 +7,7 @@ body
 {
 	width: 550px;
 	height: 500px;
-
-	/*transform: rotate3d(0, 1, 0, 30deg);*/
+	/*transform: rotate3d(0,1,0,15deg);*/
 }
 
 /* Hide the window icon. */
@@ -19,8 +18,8 @@ div#title_bar div#icon
 
 div#title_bar:hover
 {
-	transform: rotate3d(1, 0, 0, 25) scale(1.1);
-	transform-origin: center bottom;
+	transform: scale(1.2) skew(-30deg, 0deg) ;
+	transform-origin: left bottom;
 }
 
 spacer
@@ -28,13 +27,9 @@ spacer
 	display: inline-block;
 	width: 25px;
 }
-scrollbarvertical sliderbar,scrollbarvertical sliderbar
-{
-	/*transform: translateZ(0px);*/
-}
 scrollbarvertical sliderbar:hover,scrollbarvertical sliderbar:active
 {
-	/*transform: translateZ(10px);*/
+	transform: scale(1.3, 1.0);
 }
 
 
@@ -68,11 +63,13 @@ scrollbarvertical sliderbar:hover,scrollbarvertical sliderbar:active
 .cube {
 	width: 100%;
 	height: 100%;
-	backface-visibility: visible;
+	position: relative;
+	transform: translateZ(50px);
 	perspective-origin: 150% 150%;
 }
 
 .face {
+	left: 50px; top: 50px;
 	display: block;
 	position: absolute;
 	width: 100px;
@@ -89,6 +86,9 @@ scrollbarvertical sliderbar:hover,scrollbarvertical sliderbar:active
 	background: rgba(0, 0, 0, 160);
 	transform: translateZ(50px);
 }
+.front:hover {
+	background: rgba(255, 255, 0, 120);
+}
 .back {
 	background: rgba(0, 255, 0, 255);
 	color: black;
@@ -113,7 +113,6 @@ scrollbarvertical sliderbar:hover,scrollbarvertical sliderbar:active
 </style>
 </head>
 <body template="window">
-
 <div class="container">
 	<div class="cube pers650">
 		<div class="face back">6</div>
@@ -123,7 +122,7 @@ scrollbarvertical sliderbar:hover,scrollbarvertical sliderbar:active
 		<div class="face right">2</div>
 		<div class="face front">1</div>
 	</div>
-	Perspective: 650px.
+	perspective: 650px;
 </div>
 
 <p>Press 'space' to toggle rotation.</p>
@@ -148,6 +147,8 @@ augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet,
 consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut
 laoreet dolore magna aliquam erat volutpat.</p>
 
+<button style="width: 220px;">A wild button appears!</button>
+
 <p>Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit
 lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure
 dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore
@@ -162,7 +163,7 @@ laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis
 nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea
 commodo consequat.</p>
 
-<div class="container">
+<div class="container" style="clip: none;">
 	<div class="cube pers250">
 		<div class="face back">6</div>
 		<div class="face top">5</div>
@@ -171,7 +172,7 @@ commodo consequat.</p>
 		<div class="face right">2</div>
 		<div class="face front">1</div>
 	</div>
-	Perspective: 250px.
+	perspective: 250px; clip: none;
 </div>
 
 <p>Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse

+ 8 - 21
Samples/basic/transform/src/main.cpp

@@ -61,29 +61,16 @@ public:
 
 	void SetPerspective(float distance)
 	{
-		if (document)
-		{
-			std::stringstream s;
-			s << distance << "px";
-			document->SetProperty("perspective", s.str().c_str());
-		}
-	}
-
-	void SetPerspectiveOrigin(float x, float y)
-	{
-		if (document)
-		{
-			std::stringstream s;
-			s << x * 100 << "%" << " " << y * 100 << "%";
-			document->SetProperty("perspective-origin", s.str().c_str());
-		}
+		perspective = distance;
 	}
 
 	void SetRotation(float degrees)
 	{
-		if (document)
+		if(document)
 		{
 			std::stringstream s;
+			if (perspective > 0)
+				s << "perspective(" << perspective << "px) ";
 			s << "rotate3d(0.0, 1.0, 0.0, " << degrees << ")";
 			document->SetProperty("transform", s.str().c_str());
 		}
@@ -107,6 +94,7 @@ public:
 	}
 
 private:
+	float perspective = 0;
 	Rml::Core::ElementDocument *document;
 };
 
@@ -157,7 +145,7 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))
 #endif
 
 	constexpr int width = 1600;
-	constexpr int height = 1000;
+	constexpr int height = 950;
 
 	ShellRenderInterfaceOpenGL opengl_renderer;
 	shell_renderer = &opengl_renderer;
@@ -195,16 +183,15 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))
 
 	Shell::LoadFonts("assets/");
 
-	window_1 = new DemoWindow("Orthographic transform", Rml::Core::Vector2f(120, 200), context);
+	window_1 = new DemoWindow("Orthographic transform", Rml::Core::Vector2f(120, 180), context);
 	if (window_1)
 	{
 		context->GetRootElement()->AddEventListener(Rml::Core::EventId::Keydown, window_1);
 	}
-	window_2 = new DemoWindow("Perspective transform", Rml::Core::Vector2f(900, 200), context);
+	window_2 = new DemoWindow("Perspective transform", Rml::Core::Vector2f(900, 180), context);
 	if (window_2)
 	{
 		window_2->SetPerspective(800);
-		window_2->SetPerspectiveOrigin(0.5, 0.75);
 	}
 
 	Shell::EventLoop(GameLoop);

+ 0 - 2
Samples/shell/src/macosx/ShellRenderInterfaceExtensionsOpenGL_MacOSX.cpp

@@ -59,8 +59,6 @@ void ShellRenderInterfaceOpenGL::SetViewport(int width, int height)
 		if(m_rmlui_context != nullptr)
 		{
 			((Rml::Core::Context*)m_rmlui_context)->SetDimensions(Rml::Core::Vector2i(width, height));
-			((Rml::Core::Context*)m_rmlui_context)->ProcessProjectionChange(projection);
-			((Rml::Core::Context*)m_rmlui_context)->ProcessViewChange(view);
 		}
 	}
 }

+ 0 - 2
Samples/shell/src/win32/ShellRenderInterfaceExtensionsOpenGL_Win32.cpp

@@ -57,8 +57,6 @@ void ShellRenderInterfaceOpenGL::SetViewport(int width, int height)
 		if(m_rmlui_context != nullptr)
 		{
 			((Rml::Core::Context*)m_rmlui_context)->SetDimensions(Rml::Core::Vector2i(width, height));
-			((Rml::Core::Context*)m_rmlui_context)->ProcessProjectionChange(projection);
-			((Rml::Core::Context*)m_rmlui_context)->ProcessViewChange(view);
 		}
 	}
 }

+ 0 - 2
Samples/shell/src/x11/ShellRenderInterfaceExtensionsOpenGL_X11.cpp

@@ -59,8 +59,6 @@ void ShellRenderInterfaceOpenGL::SetViewport(int width, int height)
 		if(m_rmlui_context != nullptr)
 		{
 			((Rml::Core::Context*)m_rmlui_context)->SetDimensions(Rml::Core::Vector2i(width, height));
-			((Rml::Core::Context*)m_rmlui_context)->ProcessProjectionChange(projection);
-			((Rml::Core::Context*)m_rmlui_context)->ProcessViewChange(view);
 		}
 	}
 }

+ 10 - 32
Source/Core/Context.cpp

@@ -41,7 +41,7 @@ namespace Core {
 
 const float DOUBLE_CLICK_TIME = 0.5f;
 
-Context::Context(const String& name) : name(name), dimensions(0, 0), density_independent_pixel_ratio(1.0f), mouse_position(0, 0), clip_origin(-1, -1), clip_dimensions(-1, -1), view_state()
+Context::Context(const String& name) : name(name), dimensions(0, 0), density_independent_pixel_ratio(1.0f), mouse_position(0, 0), clip_origin(-1, -1), clip_dimensions(-1, -1)
 {
 	instancer = nullptr;
 
@@ -121,10 +121,6 @@ void Context::SetDimensions(const Vector2i& _dimensions)
 		}
 		
 		clip_dimensions = dimensions;
-
-
-		// TODO: Ensure the user calls ProcessProjectionChange() before
-		// the next rendering phase.
 	}
 }
 
@@ -134,13 +130,6 @@ const Vector2i& Context::GetDimensions() const
 	return dimensions;
 }
 
-
-// Returns the current state of the view.
-const ViewState& Context::GetViewState() const noexcept
-{
-	return view_state;
-}
-
 void Context::SetDensityIndependentPixelRatio(float _density_independent_pixel_ratio)
 {
 	if (density_independent_pixel_ratio != _density_independent_pixel_ratio)
@@ -753,18 +742,6 @@ bool Context::ProcessMouseWheel(float wheel_delta, int key_modifier_state)
 	return true;
 }
 
-// Notifies RmlUi of a change in the projection matrix.
-void Context::ProcessProjectionChange(const Matrix4f &projection)
-{
-	view_state.SetProjection(&projection);
-}
-
-// Notifies RmlUi of a change in the view matrix.
-void Context::ProcessViewChange(const Matrix4f &view)
-{
-	view_state.SetView(&view);
-}
-
 // Gets the context's render interface.
 RenderInterface* Context::GetRenderInterface() const
 {
@@ -1012,7 +989,7 @@ void Context::UpdateHoverChain(const Dictionary& parameters, const Dictionary& d
 }
 
 // Returns the youngest descendent of the given element which is under the given point in screen coodinates.
-Element* Context::GetElementAtPoint(const Vector2f& point, const Element* ignore_element, Element* element)
+Element* Context::GetElementAtPoint(Vector2f point, const Element* ignore_element, Element* element)
 {
 	if (element == nullptr)
 	{
@@ -1067,23 +1044,24 @@ Element* Context::GetElementAtPoint(const Vector2f& point, const Element* ignore
 		}
 	}
 
-	// Ignore elements whose pointer events are disabled
+	// Ignore elements whose pointer events are disabled.
 	if (element->GetComputedValues().pointer_events == Style::PointerEvents::None)
 		return nullptr;
 
-	Vector2f projected_point = element->Project(point);
+	// Projection may fail if we have a singular transformation matrix.
+	bool projection_result = element->Project(point);
 
 	// Check if the point is actually within this element.
-	bool within_element = element->IsPointWithinElement(projected_point);
+	bool within_element = (projection_result && element->IsPointWithinElement(point));
 	if (within_element)
 	{
 		Vector2i clip_origin, clip_dimensions;
 		if (ElementUtilities::GetClippingRegion(clip_origin, clip_dimensions, element))
 		{
-			within_element = projected_point.x >= clip_origin.x &&
-							 projected_point.y >= clip_origin.y &&
-							 projected_point.x <= (clip_origin.x + clip_dimensions.x) &&
-							 projected_point.y <= (clip_origin.y + clip_dimensions.y);
+			within_element = point.x >= clip_origin.x &&
+							 point.y >= clip_origin.y &&
+							 point.x <= (clip_origin.x + clip_dimensions.x) &&
+							 point.y <= (clip_origin.y + clip_dimensions.y);
 		}
 	}
 

+ 0 - 8
Source/Core/Core.cpp

@@ -202,14 +202,6 @@ Context* CreateContext(const String& name, const Vector2i& dimensions, RenderInt
 		new_context->render_interface = render_interface;
 
 	new_context->SetDimensions(dimensions);
-	if (dimensions.x > 0 && dimensions.y > 0)
-	{
-		// install an orthographic projection, by default
-		Matrix4f P = Matrix4f::ProjectOrtho(0, (float)dimensions.x, (float)dimensions.y, 0, -1, 1);
-		new_context->ProcessProjectionChange(P);
-		// install an identity view, by default
-		new_context->ProcessViewChange(Matrix4f::Identity());
-	}
 
 	Context* new_context_raw = new_context.get();
 	contexts[name] = std::move(new_context);

+ 144 - 322
Source/Core/Element.cpp

@@ -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();
 	}

+ 35 - 135
Source/Core/ElementUtilities.cpp

@@ -27,22 +27,47 @@
  */
 
 #include "precompiled.h"
+#include "../../Include/RmlUi/Core.h"
+#include "../../Include/RmlUi/Core/TransformState.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include <queue>
 #include <limits>
 #include "FontFaceHandle.h"
 #include "LayoutEngine.h"
-#include "../../Include/RmlUi/Core.h"
-#include "../../Include/RmlUi/Core/TransformPrimitive.h"
 #include "ElementStyle.h"
 
 namespace Rml {
 namespace Core {
 
 // Builds and sets the box for an element.
-static void SetBox(Element* element);
+static void SetBox(Element* element)
+{
+	Element* parent = element->GetParentNode();
+	RMLUI_ASSERT(parent != nullptr);
+
+	Vector2f containing_block = parent->GetBox().GetSize();
+	containing_block.x -= parent->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
+	containing_block.y -= parent->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL);
+
+	Box box;
+	LayoutEngine::BuildBox(box, containing_block, element);
+
+	if (element->GetComputedValues().height.type != Style::Height::Auto)
+		box.SetContent(Vector2f(box.GetSize().x, containing_block.y));
+
+	element->SetBox(box);
+}
+
 // Positions an element relative to an offset parent.
-static void SetElementOffset(Element* element, const Vector2f& offset);
+static void SetElementOffset(Element* element, const Vector2f& offset)
+{
+	Vector2f relative_offset = element->GetParentNode()->GetBox().GetPosition(Box::CONTENT);
+	relative_offset += offset;
+	relative_offset.x += element->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
+	relative_offset.y += element->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+
+	element->SetOffset(relative_offset, element->GetParentNode());
+}
 
 Element* ElementUtilities::GetElementById(Element* root_element, const String& id)
 {
@@ -322,150 +347,25 @@ bool ElementUtilities::PositionElement(Element* element, const Vector2f& offset,
 	return true;
 }
 
-// Builds and sets the box for an element.
-static void SetBox(Element* element)
-{
-	Element* parent = element->GetParentNode();
-	RMLUI_ASSERT(parent != nullptr);
-
-	Vector2f containing_block = parent->GetBox().GetSize();
-	containing_block.x -= parent->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
-	containing_block.y -= parent->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL);
-
-	Box box;
-	LayoutEngine::BuildBox(box, containing_block, element);
-
-	if (element->GetComputedValues().height.type != Style::Height::Auto)
-		box.SetContent(Vector2f(box.GetSize().x, containing_block.y));
-
-	element->SetBox(box);
-}
-
-// Positions an element relative to an offset parent.
-static void SetElementOffset(Element* element, const Vector2f& offset)
-{
-	Vector2f relative_offset = element->GetParentNode()->GetBox().GetPosition(Box::CONTENT);
-	relative_offset += offset;
-	relative_offset.x += element->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-	relative_offset.y += element->GetBox().GetEdge(Box::MARGIN, Box::TOP);
-
-	element->SetOffset(relative_offset, element->GetParentNode());
-}
-
 // Applies an element's `perspective' and `transform' properties.
 bool ElementUtilities::ApplyTransform(Element &element, bool apply)
 {
-	Context *context = element.GetContext();
-	if (!context)
-	{
-		return false;
-	}
-
 	RenderInterface *render_interface = element.GetRenderInterface();
 	if (!render_interface)
-	{
 		return false;
-	}
 
-	const TransformState *local_perspective, *perspective, *transform;
-	element.GetEffectiveTransformState(&local_perspective, &perspective, &transform);
-
-	bool have_perspective = false;
-	float perspective_distance = 0.0f;
-	Matrix4f the_projection;
-	if (local_perspective)
-	{
-		TransformState::LocalPerspective the_local_perspective;
-		local_perspective->GetLocalPerspective(&the_local_perspective);
-		have_perspective = true;
-		perspective_distance = the_local_perspective.distance;
-		the_projection = the_local_perspective.GetProjection();
-	}
-	else if (perspective)
+	if(auto state = element.GetTransformState())
 	{
-		TransformState::Perspective the_perspective;
-		perspective->GetPerspective(&the_perspective);
-		have_perspective = true;
-		perspective_distance = the_perspective.distance;
-		the_projection = the_perspective.GetProjection();
-	}
-
-	bool have_transform = false;
-	Matrix4f the_transform;
-	if (transform)
-	{
-		transform->GetRecursiveTransform(&the_transform);
-		have_transform = true;
-	}
-
-	if (have_perspective && perspective_distance >= 0)
-	{
-		// If we are to apply a custom projection, then we need to cancel the global one first.
-		Matrix4f global_pv_inv;
-		bool have_global_pv_inv = context->GetViewState().GetProjectionViewInv(global_pv_inv);
-
-		if (have_global_pv_inv && have_transform)
+		if(auto transform = state->GetTransform())
 		{
 			if (apply)
-			{
-				render_interface->PushTransform(global_pv_inv * the_projection * the_transform);
-			}
+				render_interface->PushTransform(*transform);
 			else
-			{
-				render_interface->PopTransform(global_pv_inv * the_projection * the_transform);
-			}
-			return true;
-		}
-		else if (have_global_pv_inv)
-		{
-			if (apply)
-			{
-				render_interface->PushTransform(global_pv_inv * the_projection);
-			}
-			else
-			{
-				render_interface->PopTransform(global_pv_inv * the_projection);
-			}
-			return true;
-		}
-		else if (have_transform)
-		{
-			// The context has not received Process(Projection|View)Change() calls.
-			// Assume we don't really need to cancel.
-			if (apply)
-			{
-				render_interface->PushTransform(the_transform);
-			}
-			else
-			{
-				render_interface->PopTransform(the_transform);
-			}
-			return true;
-		}
-		else
-		{
-			return false;
-		}
-	}
-	else
-	{
-		if (have_transform)
-		{
-			if (apply)
-			{
-				render_interface->PushTransform(the_transform);
-			}
-			else
-			{
-				render_interface->PopTransform(the_transform);
-			}
-			return true;
-		}
-		else
-		{
-			return false;
+				render_interface->PopTransform(*transform);
 		}
 	}
+
+	return true;
 }
 
 // Unapplies an element's `perspective' and `transform' properties.

+ 10 - 3
Source/Core/Event.cpp

@@ -168,9 +168,16 @@ void Event::ProjectMouse(Element* element)
 			return;
 		}
 
-		Vector2f new_pos = element->Project(mouse_screen_position);
-		*mouse_x = new_pos.x;
-		*mouse_y = new_pos.y;
+		Vector2f projected_position = mouse_screen_position;
+
+		// Not sure how best to handle the case where the projection fails.
+		if (element->Project(projected_position))
+		{
+			*mouse_x = projected_position.x;
+			*mouse_y = projected_position.y;
+		}
+		else
+			StopPropagation();
 	}
 }
 

+ 0 - 1
Source/Core/Transform.cpp

@@ -29,7 +29,6 @@
 #include "precompiled.h"
 #include "../../Include/RmlUi/Core/Transform.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
-#include "../../Include/RmlUi/Core/ViewState.h"
 #include "../../Include/RmlUi/Core/Property.h"
 
 namespace Rml {

+ 2 - 16
Source/Core/TransformPrimitive.cpp

@@ -300,7 +300,8 @@ struct ResolveTransformVisitor
 	}
 	bool operator()(const Perspective& p)
 	{
-		return false;
+		m = Matrix4f::Perspective(p.values[0].ResolveDepth(e));
+		return true;
 	}
 
 
@@ -351,21 +352,6 @@ bool Primitive::ResolveTransform(Matrix4f & m, Element & e) const noexcept
 	return result;
 }
 
-bool Primitive::ResolvePerspective(float & p, Element & e) const noexcept
-{
-	bool result = false;
-
-	if (primitive.type == PrimitiveVariant::PERSPECTIVE)
-	{
-
-		p = primitive.perspective.values[0].ResolveDepth(e);
-		result = true;
-	}
-
-	return result;
-}
-
-
 struct SetIdentityVisitor
 {
 	template <size_t N>

+ 34 - 283
Source/Core/TransformState.cpp

@@ -32,314 +32,65 @@
 namespace Rml {
 namespace Core {
 
-Matrix4f TransformState::Perspective::GetProjection() const noexcept
+bool TransformState::SetTransform(const Matrix4f* in_transform)
 {
-	float depth = (float)Math::Max(view_size.x, view_size.y);
-
-	if (distance == 0)
-	{
-		return Matrix4f::ProjectOrtho(
-			-0.5f * view_size.x,
-			+0.5f * view_size.x,
-			+0.5f * view_size.y,
-			-0.5f * view_size.y,
-			-1.0f * depth,
-			+1.0f * depth
-		) * Matrix4f::Translate(
-			-0.5f * view_size.x,
-			-0.5f * view_size.y,
-			0
-		);
-	}
-	else if (distance > 0)
-	{
-		float far = distance + 1.0f * depth;
-		const float FAR_NEAR_RATIO = 256.0f;
-		float near = Math::Max(1.0f, far / FAR_NEAR_RATIO);
-		float scale = near / distance;
-		return Matrix4f::ProjectPerspective(
-			(-vanish.x) * scale,
-			(view_size.x - vanish.x) * scale,
-			(view_size.y - vanish.y) * scale,
-			(-vanish.y) * scale,
-			near,
-			far
-		) * Matrix4f::Translate(
-			-vanish.x,
-			-vanish.y,
-			-distance
-		);
-	}
-	else /* if (distance < 0) */
-	{
-		return Matrix4f::Identity();
-	}
-}
-
-Vector3f TransformState::Perspective::Project(const Vector3f &point) const noexcept
-{
-	if (distance < 0)
-	{
-		return point;
-	}
-	else /* if (distance >= 0) */
-	{
-		return GetProjection() * point;
-	}
-}
-
-Vector3f TransformState::Perspective::Unproject(const Vector3f &point) const noexcept
-{
-	if (distance < 0)
-	{
-		return point;
-	}
-	else /* if (distance >= 0) */
-	{
-		Matrix4f projection_inv = GetProjection();
-		projection_inv.Invert();
-		return projection_inv * point;
-	}
-}
-
-Matrix4f TransformState::LocalPerspective::GetProjection() const noexcept
-{
-	float depth = (float)Math::Max(view_size.x, view_size.y);
-
-	if (distance == 0)
-	{
-		return Matrix4f::ProjectOrtho(
-			-0.5f * view_size.x,
-			+0.5f * view_size.x,
-			+0.5f * view_size.y,
-			-0.5f * view_size.y,
-			-0.5f * depth,
-			+0.5f * depth
-		) * Matrix4f::Translate(
-			-0.5f * view_size.x,
-			-0.5f * view_size.y,
-			0
-		);
-	}
-	else if (distance > 0)
-	{
-		return Matrix4f::ProjectPerspective(
-			(0.f - 0.5f) * view_size.x,
-			(1.f - 0.5f) * view_size.x,
-			(1.f - 0.5f) * view_size.y,
-			(0.f - 0.5f) * view_size.y,
-			distance,
-			distance + depth
-		) * Matrix4f::Translate(
-			-0.5f * view_size.x,
-			-0.5f * view_size.y,
-			-distance - 0.5f * depth
-		);
-	}
-	else /* if (distance < 0) */
-	{
-		return Matrix4f::Identity();
-	}
-}
-
-Vector3f TransformState::LocalPerspective::Project(const Vector3f &point) const noexcept
-{
-	if (distance < 0)
-	{
-		return point;
-	}
-	else /* if (distance >= 0) */
-	{
-		return GetProjection() * point;
-	}
-}
-
-Vector3f TransformState::LocalPerspective::Unproject(const Vector3f &point) const noexcept
-{
-	if (distance < 0)
-	{
-		return point;
-	}
-	else /* if (distance >= 0) */
-	{
-		Matrix4f projection_inv = GetProjection();
-		projection_inv.Invert();
-		return projection_inv * point;
-	}
-}
-
-TransformState::TransformState()
-	: have_perspective(false), have_local_perspective(false),
-	  have_parent_recursive_transform(false), have_transform(false)
-{
-}
-
-void TransformState::SetPerspective(const Perspective *perspective) noexcept
-{
-	if (perspective)
+	bool is_changed = (have_transform != (bool)in_transform);
+	if (in_transform)
 	{
-		this->perspective = perspective->distance;
-		this->view_size = perspective->view_size;
-		this->vanish = perspective->vanish;
-	}
-
-	have_perspective = perspective != 0;
-}
-
-bool TransformState::GetPerspective(Perspective *perspective) const noexcept
-{
-	if (have_perspective && perspective)
-	{
-		perspective->distance = this->perspective;
-		perspective->view_size = this->view_size;
-		perspective->vanish = this->vanish;
-	}
-
-	return have_perspective;
-}
-
-void TransformState::SetLocalPerspective(const LocalPerspective *local_perspective) noexcept
-{
-	if (local_perspective)
-	{
-		this->local_perspective = local_perspective->distance;
-		this->view_size = local_perspective->view_size;
-	}
-
-	have_local_perspective = local_perspective != 0;
-}
-
-bool TransformState::GetLocalPerspective(LocalPerspective *local_perspective) const noexcept
-{
-	if (have_local_perspective && local_perspective)
-	{
-		local_perspective->distance = this->local_perspective;
-		local_perspective->view_size = this->view_size;
+		is_changed |= (have_transform && transform != *in_transform);
+		transform = *in_transform;
+		have_transform = true;
 	}
+	else
+		have_transform = false;
+	
+	if (is_changed)
+		dirty_inverse_transform = true;
 
-	return have_local_perspective;
+	return is_changed;
 }
-
-void TransformState::SetTransform(const Matrix4f *transform) noexcept
+bool TransformState::SetLocalPerspective(const Matrix4f* in_perspective)
 {
-	if (transform)
-	{
-		this->transform = *transform;
-	}
+	bool is_changed = (have_perspective != (bool)in_perspective);
 
-	have_transform = transform != 0;
-}
-
-bool TransformState::GetTransform(Matrix4f *transform) const noexcept
-{
-	if (have_transform)
+	if (in_perspective)
 	{
-		if (transform)
-		{
-			*transform = this->transform;
-		}
-
-		return true;
+		is_changed |= (have_perspective && local_perspective != *in_perspective);
+		local_perspective = *in_perspective;
+		have_perspective = true;
 	}
 	else
-	{
-		return false;
-	}
-}
-
-void TransformState::SetParentRecursiveTransform(const Matrix4f *parent_recursive_transform) noexcept
-{
-	if (parent_recursive_transform)
-	{
-		this->parent_recursive_transform = *parent_recursive_transform;
-	}
+		have_perspective = false;
 
-	have_parent_recursive_transform = parent_recursive_transform != 0;
+	return is_changed;
 }
 
-bool TransformState::GetParentRecursiveTransform(Matrix4f *transform) const noexcept
+const Matrix4f* TransformState::GetTransform() const
 {
-	if (have_parent_recursive_transform)
-	{
-		if (transform)
-		{
-			*transform = this->parent_recursive_transform;
-		}
-
-		return true;
-	}
-	else
-	{
-		return false;
-	}
+	return have_transform ? &transform : nullptr;
 }
 
-Vector3f TransformState::Transform(const Vector3f &point) const noexcept
+const Matrix4f* TransformState::GetLocalPerspective() const
 {
-	if (have_parent_recursive_transform && have_transform)
-	{
-		return parent_recursive_transform * (transform * point);
-	}
-	else if (have_parent_recursive_transform)
-	{
-		return parent_recursive_transform * point;
-	}
-	else if (have_transform)
-	{
-		return transform * point;
-	}
-	else
-	{
-		return point;
-	}
+	return have_perspective ? &local_perspective : nullptr;
 }
 
-Vector3f TransformState::Untransform(const Vector3f &point) const noexcept
+const Matrix4f* TransformState::GetInverseTransform() const
 {
-	Matrix4f transform_inv;
+	if (!have_transform)
+		return nullptr;
 
-	if (have_parent_recursive_transform && have_transform)
-	{
-		transform_inv = parent_recursive_transform * transform;
-		transform_inv.Invert();
-	}
-	else if (have_parent_recursive_transform)
+	if (dirty_inverse_transform)
 	{
-		transform_inv = parent_recursive_transform;
-		transform_inv.Invert();
+		inverse_transform = transform;
+		have_inverse_transform = inverse_transform.Invert();
+		dirty_inverse_transform = false;
 	}
-	else if (have_transform)
-	{
-		transform_inv = transform;
-		transform_inv.Invert();
-	}
-	else
-	{
-		return point;
-	}
-
-	return transform_inv * point;
-}
 
-bool TransformState::GetRecursiveTransform(Matrix4f *recursive_transform) const noexcept
-{
-	if (recursive_transform)
-	{
-		if (have_parent_recursive_transform && have_transform)
-		{
-			*recursive_transform = parent_recursive_transform * transform;
-		}
-		else if (have_parent_recursive_transform)
-		{
-			*recursive_transform = parent_recursive_transform;
-		}
-		else if (have_transform)
-		{
-			*recursive_transform = transform;
-		}
-	}
+	if (have_inverse_transform)
+		return &inverse_transform;
 
-	return have_parent_recursive_transform || have_transform;
+	return nullptr;
 }
 
 }

+ 0 - 147
Source/Core/ViewState.cpp

@@ -1,147 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2014 Markus Schöngart
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "precompiled.h"
-#include "../../Include/RmlUi/Core/ViewState.h"
-
-namespace Rml {
-namespace Core {
-
-ViewState::ViewState()
-	: have_projection(false), have_view(false), projection_view_inv_dirty(true),
-	  projection(), view()
-{
-}
-
-void ViewState::SetProjection(const Matrix4f *projection) noexcept
-{
-	if (projection)
-	{
-		this->projection = *projection;
-	}
-
-	have_projection = projection != 0;
-	projection_view_inv_dirty = true;
-}
-
-void ViewState::SetView(const Matrix4f *view) noexcept
-{
-	if (view)
-	{
-		this->view = *view;
-	}
-
-	have_view = view != 0;
-	projection_view_inv_dirty = true;
-}
-
-bool ViewState::GetProjectionViewInv(Matrix4f& projection_view_inv) const noexcept
-{
-	if (have_projection || have_view)
-	{
-		if (projection_view_inv_dirty)
-		{
-			UpdateProjectionViewInv();
-		}
-
-		projection_view_inv = this->projection_view_inv;
-
-		return true;
-	}
-	else
-	{
-		return false;
-	}
-}
-
-Vector3f ViewState::Project(const Vector3f &point) const noexcept
-{
-	if (have_projection && have_view)
-	{
-		return projection * (view * point);
-	}
-	else if (have_projection)
-	{
-		return projection * point;
-	}
-	else if (have_view)
-	{
-		return view * point;
-	}
-	else
-	{
-		return point;
-	}
-}
-
-Vector3f ViewState::Unproject(const Vector3f &point) const noexcept
-{
-	if (have_projection || have_view)
-	{
-		if (projection_view_inv_dirty)
-		{
-			UpdateProjectionViewInv();
-		}
-
-		return projection_view_inv * point;
-	}
-	else
-	{
-		return point;
-	}
-}
-
-void ViewState::UpdateProjectionViewInv() const noexcept
-{
-	RMLUI_ASSERT(projection_view_inv_dirty);
-
-	if (have_projection && have_view)
-	{
-		projection_view_inv = projection * view;
-		projection_view_inv.Invert();
-	}
-	else if (have_projection)
-	{
-		projection_view_inv = projection;
-		projection_view_inv.Invert();
-	}
-	else if (have_view)
-	{
-		projection_view_inv = view;
-		projection_view_inv.Invert();
-	}
-	else
-	{
-		projection_view_inv = Matrix4f::Identity();
-	}
-
-	projection_view_inv_dirty = false;
-}
-
-}
-}

+ 11 - 0
readme.md

@@ -227,6 +227,17 @@ template<typename T> using UniquePtr = std::unique_ptr<T>;
 template<typename T> using SharedPtr = std::shared_ptr<T>;
 ```
 
+### Improved transforms
+
+The inner workings of transforms have been completely revised, resulting in increased performance, simplified API, closer compliance to the CSS specs, and reduced complexity of the relevant parts of the library.
+
+Some relevant changes for users:
+- Removed the need for users to set the view and projection matrices they use outside the library.
+- The `perspective` property now applies to the element's children, as in CSS.
+- The transform function `perspective()` behaves like in CSS. It applies a perspective projection to the current element.
+- Chaining transforms and perspectives now provides more expected results. As opposed to CSS, we don't flatten transforms.
+- Have a look at the updated transforms sample for some fun with 3d boxes.
+
 
 ### Other changes