Browse Source

Refactor some of the Transform functionality. Move functions that should only be called internally to an internal TransformUtilities. Rename Transforms::Primitive to TransformPrimitive.

Michael Ragazzon 5 years ago
parent
commit
98282c7b2e

+ 3 - 1
CMake/FileList.cmake

@@ -99,6 +99,8 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutRow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutTexture.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureResource.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/TransformState.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/TransformUtilities.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Utilities.h
     ${PROJECT_SOURCE_DIR}/Source/Core/WidgetScroll.h
     ${PROJECT_SOURCE_DIR}/Source/Core/XMLNodeHandlerBody.h
@@ -204,7 +206,6 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Traits.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Transform.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TransformPrimitive.h
-    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TransformState.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Tween.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.inl
@@ -384,6 +385,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Transform.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/TransformPrimitive.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/TransformState.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/TransformUtilities.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Tween.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/TypeConverter.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/URL.cpp

+ 2 - 2
Include/RmlUi/Core/Element.h

@@ -245,8 +245,6 @@ public:
 	/// Returns 'line-height' property value from element's computed values.
 	float GetLineHeight();
 
-	/// Returns this element's TransformState
-	const TransformState *GetTransformState() const noexcept;
 	/// Project a 2D point in pixel coordinates onto the element's plane.
 	/// @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.
@@ -544,6 +542,8 @@ public:
 	ElementDecoration* GetElementDecoration() const;
 	/// Returns the element's scrollbar functionality.
 	ElementScroll* GetElementScroll() const;
+	/// Returns the element's transform state.
+	const TransformState* GetTransformState() const noexcept;
 	/// Returns the data model of this element.
 	DataModel* GetDataModel() const;
 	//@}

+ 21 - 21
Include/RmlUi/Core/Property.h

@@ -55,38 +55,38 @@ public:
 	{
 		UNKNOWN = 1 << 0,
 
-		KEYWORD = 1 << 1,			// generic keyword; fetch as < int >
+		KEYWORD = 1 << 1,           // generic keyword; fetch as < int >
 
-		STRING = 1 << 2,			// generic string; fetch as < String >
+		STRING = 1 << 2,            // generic string; fetch as < String >
 
 		// Absolute values.
-		NUMBER = 1 << 3,			// number unsuffixed; fetch as < float >
-		PX = 1 << 4,				// number suffixed by 'px'; fetch as < float >
-		DEG = 1 << 5,				// number suffixed by 'deg'; fetch as < float >
-		RAD = 1 << 6,				// number suffixed by 'rad'; fetch as < float >
-		COLOUR = 1 << 7,			// colour; fetch as < Colourb >
-		DP = 1 << 8,				// density-independent pixel; number suffixed by 'dp'; fetch as < float >
+		NUMBER = 1 << 3,            // number unsuffixed; fetch as < float >
+		PX = 1 << 4,                // number suffixed by 'px'; fetch as < float >
+		DEG = 1 << 5,               // number suffixed by 'deg'; fetch as < float >
+		RAD = 1 << 6,               // number suffixed by 'rad'; fetch as < float >
+		COLOUR = 1 << 7,            // colour; fetch as < Colourb >
+		DP = 1 << 8,                // density-independent pixel; number suffixed by 'dp'; fetch as < float >
 		ABSOLUTE_UNIT = NUMBER | PX | DP | DEG | RAD | COLOUR,
 
 		// Relative values.
-		EM = 1 << 9,				// number suffixed by 'em'; fetch as < float >
-		PERCENT = 1 << 10,			// number suffixed by '%'; fetch as < float >
-		REM = 1 << 11,				// number suffixed by 'rem'; fetch as < float >
+		EM = 1 << 9,                // number suffixed by 'em'; fetch as < float >
+		PERCENT = 1 << 10,          // number suffixed by '%'; fetch as < float >
+		REM = 1 << 11,              // number suffixed by 'rem'; fetch as < float >
 		RELATIVE_UNIT = EM | REM | PERCENT,
 
 		// Values based on pixels-per-inch.
-		INCH = 1 << 12,				// number suffixed by 'in'; fetch as < float >
-		CM = 1 << 13,				// number suffixed by 'cm'; fetch as < float >
-		MM = 1 << 14,				// number suffixed by 'mm'; fetch as < float >
-		PT = 1 << 15,				// number suffixed by 'pt'; fetch as < float >
-		PC = 1 << 16,				// number suffixed by 'pc'; fetch as < float >
+		INCH = 1 << 12,             // number suffixed by 'in'; fetch as < float >
+		CM = 1 << 13,               // number suffixed by 'cm'; fetch as < float >
+		MM = 1 << 14,               // number suffixed by 'mm'; fetch as < float >
+		PT = 1 << 15,               // number suffixed by 'pt'; fetch as < float >
+		PC = 1 << 16,               // number suffixed by 'pc'; fetch as < float >
 		PPI_UNIT = INCH | CM | MM | PT | PC,
 
-		TRANSFORM = 1 << 17,			// transform; fetch as < TransformPtr >, may be empty
-		TRANSITION = 1 << 18,           // transition; fetch as < TransitionList >
-		ANIMATION = 1 << 19,            // animation; fetch as < AnimationList >
-		DECORATOR = 1 << 20,            // decorator; fetch as < DecoratorsPtr >
-		FONTEFFECT = 1 << 21,           // font-effect; fetch as < FontEffectsPtr >
+		TRANSFORM = 1 << 17,        // transform; fetch as < TransformPtr >, may be empty
+		TRANSITION = 1 << 18,       // transition; fetch as < TransitionList >
+		ANIMATION = 1 << 19,        // animation; fetch as < AnimationList >
+		DECORATOR = 1 << 20,        // decorator; fetch as < DecoratorsPtr >
+		FONTEFFECT = 1 << 21,       // font-effect; fetch as < FontEffectsPtr >
 
 		LENGTH = PX | DP | PPI_UNIT | EM | REM,
 		LENGTH_PERCENT = LENGTH | PERCENT,

+ 17 - 15
Include/RmlUi/Core/Transform.h

@@ -30,18 +30,20 @@
 #define RMLUI_CORE_TRANSFORM_H
 
 #include "Header.h"
-#include "TransformPrimitive.h"
+#include "Types.h"
 
 namespace Rml {
 
 class Property;
+struct TransformPrimitive;
 
 /**
-	The Transform class holds the information parsed from an element's
-	`transform' property.  It is one of the primitive types that a Variant
-	can assume.  The method `ComputeFinalTransform()' computes the
-	transformation matrix that is to be applied to the current
-	projection/view matrix in order to render the associated element.
+	The Transform class holds the information parsed from an element's `transform' property.
+	
+	The class holds a list of transform primitives making up a complete transformation specification
+	of an element. Each transform instance is relative to the element's parent coordinate system.
+	During the Context::Render call the transforms of the current element and its ancestors will be
+	used to find the final transformation matrix for the global coordinate system.
 
 	@author Markus Schöngart
 	@see Rml::Variant
@@ -50,34 +52,34 @@ class Property;
 class RMLUICORE_API Transform
 {
 public:
-	using Primitives = std::vector< Transforms::Primitive >;
+	using PrimitiveList = std::vector< TransformPrimitive >;
 
 	/// Default constructor, initializes an identity transform
 	Transform();
 
 	/// Construct transform with a list of primitives
-	Transform(std::vector<Transforms::Primitive> primitives);
+	Transform(PrimitiveList primitives);
 
-	/// Helper function to create a Property with TransformPtr from list of primitives
-	static Property MakeProperty(std::vector<Transforms::Primitive> primitives);
+	/// Helper function to create a 'transform' Property from the given list of primitives
+	static Property MakeProperty(PrimitiveList primitives);
 
 	/// Remove all Primitives from this Transform
 	void ClearPrimitives();
 
 	/// Add a Primitive to this Transform
-	void AddPrimitive(const Transforms::Primitive& p);
+	void AddPrimitive(const TransformPrimitive& p);
 
 	/// Return the number of Primitives in this Transform
 	int GetNumPrimitives() const noexcept;
 
 	/// Return the i-th Primitive in this Transform
-	const Transforms::Primitive& GetPrimitive(int i) const noexcept;
+	const TransformPrimitive& GetPrimitive(int i) const noexcept;
 
-	Primitives& GetPrimitives() noexcept { return primitives; }
-	const Primitives& GetPrimitives() const noexcept { return primitives; }
+	PrimitiveList& GetPrimitives() noexcept { return primitives; }
+	const PrimitiveList& GetPrimitives() const noexcept { return primitives; }
 
 private:
-	Primitives primitives;
+	PrimitiveList primitives;
 };
 
 

+ 115 - 189
Include/RmlUi/Core/TransformPrimitive.h

@@ -34,225 +34,190 @@
 #include "Property.h"
 #include <array>
 
-
 namespace Rml {
-
 namespace Transforms {
-	
 
-struct RMLUICORE_API NumericValue
-{
-	/// Non-initializing constructor.
-	NumericValue() noexcept;
-	/// Construct from a float and a Unit.
-	NumericValue(float number, Property::Unit unit) noexcept;
-
-	/// Resolve a numeric property value for an element.
-	float ResolveLengthPercentage(Element& e, float base) const noexcept;
-	/// Resolve a numeric property value with the element's width as relative base value.
-	float ResolveWidth(Element& e) const noexcept;
-	/// Resolve a numeric property value with the element's height as relative base value.
-	float ResolveHeight(Element& e) const noexcept;
-	/// Resolve a numeric property value with the element's depth as relative base value.
-	float ResolveDepth(Element& e) const noexcept;
-	/// Returns the numeric value converted to base_unit, or 'number' if no relationship defined.
-	/// Defined for: {Number, Deg, %} -> Rad
-	float ResolveAbsoluteUnit(Property::Unit base_unit) const noexcept;
-
-	String ToString() const noexcept;
+struct RMLUICORE_API NumericValue {
+	NumericValue() noexcept : number(0.f), unit(Property::UNKNOWN) {}
+	NumericValue(float number, Property::Unit unit) noexcept : number(number), unit(unit) {}
 
 	float number;
 	Property::Unit unit;
 };
 
-
+// A resolved primitive has values that are always independent of an element's properties or layout.
 template< size_t N >
 struct ResolvedPrimitive
 {
-	ResolvedPrimitive(const float* values) noexcept
-	{
-		for (size_t i = 0; i < N; ++i)
-			this->values[i] = values[i];
-	}
-	ResolvedPrimitive(const NumericValue* values) noexcept
-	{
-		for (size_t i = 0; i < N; ++i) 
-			this->values[i] = values[i].number;
-	}
-	ResolvedPrimitive(const NumericValue* values, std::array<Property::Unit, N> base_units) noexcept
-	{
-		for (size_t i = 0; i < N; ++i)
-			this->values[i] = values[i].ResolveAbsoluteUnit(base_units[i]);
-	}
-	ResolvedPrimitive(std::array<NumericValue, N> values, std::array<Property::Unit, N> base_units) noexcept
-	{
-		for (size_t i = 0; i < N; ++i)
-			this->values[i] = values[i].ResolveAbsoluteUnit(base_units[i]);
-	}
-	ResolvedPrimitive(std::array<float, N> values) noexcept : values(values) { }
-	
 	std::array<float, N> values;
+
+protected:
+	ResolvedPrimitive(const float* values) noexcept;
+	ResolvedPrimitive(const NumericValue* values) noexcept;
+	ResolvedPrimitive(const NumericValue* values, std::array<Property::Unit, N> base_units) noexcept;
+	ResolvedPrimitive(std::array<NumericValue, N> values, std::array<Property::Unit, N> base_units) noexcept;
+	ResolvedPrimitive(std::array<float, N> values) noexcept;
 };
 
+// An unresolved primitive may have values that depend on the final layout of a given element, such as its width.
 template< size_t N >
 struct UnresolvedPrimitive
 {
-	UnresolvedPrimitive(const NumericValue* values) noexcept
-	{
-		for (size_t i = 0; i < N; ++i)
-			this->values[i] = values[i];
-	}
-	UnresolvedPrimitive(std::array<NumericValue, N> values) noexcept : values(values) { }
-
 	std::array<NumericValue, N> values;
-};
-
-
 
+protected:
+	UnresolvedPrimitive(const NumericValue* values) noexcept;
+	UnresolvedPrimitive(std::array<NumericValue, N> values) noexcept;
+};
 
 
 struct RMLUICORE_API Matrix2D : public ResolvedPrimitive< 6 >
 {
-	Matrix2D(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
+	Matrix2D(const NumericValue* values) noexcept;
 };
 
 struct RMLUICORE_API Matrix3D : public ResolvedPrimitive< 16 >
 {
-	Matrix3D(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
-	Matrix3D(const Matrix4f& matrix) noexcept : ResolvedPrimitive(matrix.data()) { }
+	Matrix3D(const NumericValue* values) noexcept;
+	Matrix3D(const Matrix4f& matrix) noexcept;
 };
 
 struct RMLUICORE_API TranslateX : public UnresolvedPrimitive< 1 >
 {
-	TranslateX(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
-	TranslateX(float x, Property::Unit unit = Property::PX) noexcept : UnresolvedPrimitive({ NumericValue(x, unit) }) { }
+	TranslateX(const NumericValue* values) noexcept;
+	TranslateX(float x, Property::Unit unit = Property::PX) noexcept;
 };
 
 struct RMLUICORE_API TranslateY : public UnresolvedPrimitive< 1 >
 {
-	TranslateY(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
-	TranslateY(float y, Property::Unit unit = Property::PX) noexcept : UnresolvedPrimitive({ NumericValue(y, unit) }) { }
+	TranslateY(const NumericValue* values) noexcept;
+	TranslateY(float y, Property::Unit unit = Property::PX) noexcept;
 };
 
 struct RMLUICORE_API TranslateZ : public UnresolvedPrimitive< 1 >
 {
-	TranslateZ(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
-	TranslateZ(float z, Property::Unit unit = Property::PX) noexcept : UnresolvedPrimitive({ NumericValue(z, unit) }) { }
+	TranslateZ(const NumericValue* values) noexcept;
+	TranslateZ(float z, Property::Unit unit = Property::PX) noexcept;
 };
 
 struct RMLUICORE_API Translate2D : public UnresolvedPrimitive< 2 >
 {
-	Translate2D(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
-	Translate2D(float x, float y, Property::Unit units = Property::PX) noexcept : UnresolvedPrimitive({ NumericValue(x, units), NumericValue(y, units) }) { }
+	Translate2D(const NumericValue* values) noexcept;
+	Translate2D(float x, float y, Property::Unit units = Property::PX) noexcept;
 };
 
 struct RMLUICORE_API Translate3D : public UnresolvedPrimitive< 3 >
 {
-	Translate3D(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
-	Translate3D(NumericValue x, NumericValue y, NumericValue z) noexcept : UnresolvedPrimitive({ x, y, z }) { }
-	Translate3D(float x, float y, float z, Property::Unit units = Property::PX) noexcept : UnresolvedPrimitive({ NumericValue(x, units), NumericValue(y, units), NumericValue(z, units) }) { }
+	Translate3D(const NumericValue* values) noexcept;
+	Translate3D(NumericValue x, NumericValue y, NumericValue z) noexcept;
+	Translate3D(float x, float y, float z, Property::Unit units = Property::PX) noexcept;
 };
 
 struct RMLUICORE_API ScaleX : public ResolvedPrimitive< 1 >
 {
-	ScaleX(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
-	ScaleX(float value) noexcept : ResolvedPrimitive({ value }) { }
+	ScaleX(const NumericValue* values) noexcept;
+	ScaleX(float value) noexcept;
 };
 
 struct RMLUICORE_API ScaleY : public ResolvedPrimitive< 1 >
 {
-	ScaleY(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
-	ScaleY(float value) noexcept : ResolvedPrimitive({ value }) { }
+	ScaleY(const NumericValue* values) noexcept;
+	ScaleY(float value) noexcept;
 };
 
 struct RMLUICORE_API ScaleZ : public ResolvedPrimitive< 1 >
 {
-	ScaleZ(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
-	ScaleZ(float value) noexcept : ResolvedPrimitive({ value }) { }
+	ScaleZ(const NumericValue* values) noexcept;
+	ScaleZ(float value) noexcept;
 };
 
 struct RMLUICORE_API Scale2D : public ResolvedPrimitive< 2 >
 {
-	Scale2D(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
-	Scale2D(float xy) noexcept : ResolvedPrimitive({ xy, xy }) { }
-	Scale2D(float x, float y) noexcept : ResolvedPrimitive({ x, y }) { }
+	Scale2D(const NumericValue* values) noexcept;
+	Scale2D(float xy) noexcept;
+	Scale2D(float x, float y) noexcept;
 };
 
 struct RMLUICORE_API Scale3D : public ResolvedPrimitive< 3 >
 {
-	Scale3D(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
-	Scale3D(float xyz) noexcept : ResolvedPrimitive({ xyz, xyz, xyz }) { }
-	Scale3D(float x, float y, float z) noexcept : ResolvedPrimitive({ x, y, z }) { }
+	Scale3D(const NumericValue* values) noexcept;
+	Scale3D(float xyz) noexcept;
+	Scale3D(float x, float y, float z) noexcept;
 };
 
 struct RMLUICORE_API RotateX : public ResolvedPrimitive< 1 >
 {
-	RotateX(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) { }
-	RotateX(float angle, Property::Unit unit = Property::DEG) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
+	RotateX(const NumericValue* values) noexcept;
+	RotateX(float angle, Property::Unit unit = Property::DEG) noexcept;
 };
 
 struct RMLUICORE_API RotateY : public ResolvedPrimitive< 1 >
 {
-	RotateY(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) {}
-	RotateY(float angle, Property::Unit unit = Property::DEG) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
+	RotateY(const NumericValue* values) noexcept;
+	RotateY(float angle, Property::Unit unit = Property::DEG) noexcept;
 };
 
 struct RMLUICORE_API RotateZ : public ResolvedPrimitive< 1 >
 {
-	RotateZ(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) { }
-	RotateZ(float angle, Property::Unit unit = Property::DEG) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
+	RotateZ(const NumericValue* values) noexcept;
+	RotateZ(float angle, Property::Unit unit = Property::DEG) noexcept;
 };
 
 struct RMLUICORE_API Rotate2D : public ResolvedPrimitive< 1 >
 {
-	Rotate2D(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) { }
-	Rotate2D(float angle, Property::Unit unit = Property::DEG) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
+	Rotate2D(const NumericValue* values) noexcept;
+	Rotate2D(float angle, Property::Unit unit = Property::DEG) noexcept;
 };
 
 struct RMLUICORE_API Rotate3D : public ResolvedPrimitive< 4 >
 {
-	Rotate3D(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::NUMBER, Property::NUMBER, Property::NUMBER, Property::RAD }) { }
-	Rotate3D(float x, float y, float z, float angle, Property::Unit angle_unit = Property::DEG) noexcept
-		: ResolvedPrimitive({ NumericValue{x, Property::NUMBER}, NumericValue{y, Property::NUMBER}, NumericValue{z, Property::NUMBER}, NumericValue{angle, angle_unit} },
-			{ Property::NUMBER, Property::NUMBER, Property::NUMBER, Property::RAD }) { }
+	Rotate3D(const NumericValue* values) noexcept;
+	Rotate3D(float x, float y, float z, float angle, Property::Unit angle_unit = Property::DEG) noexcept;
 };
 
 struct RMLUICORE_API SkewX : public ResolvedPrimitive< 1 >
 {
-	SkewX(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) { }
-	SkewX(float angle, Property::Unit unit = Property::DEG) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
+	SkewX(const NumericValue* values) noexcept;
+	SkewX(float angle, Property::Unit unit = Property::DEG) noexcept;
 };
 
 struct RMLUICORE_API SkewY : public ResolvedPrimitive< 1 >
 {
-	SkewY(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) { }
-	SkewY(float angle, Property::Unit unit = Property::DEG) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
+	SkewY(const NumericValue* values) noexcept;
+	SkewY(float angle, Property::Unit unit = Property::DEG) noexcept;
 };
 
 struct RMLUICORE_API Skew2D : public ResolvedPrimitive< 2 >
 {
-	Skew2D(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD, Property::RAD }) { }
-	Skew2D(float x, float y, Property::Unit unit = Property::DEG) noexcept : ResolvedPrimitive({NumericValue{ x, unit }, { NumericValue{ y, unit }}}, {Property::RAD, Property::RAD}) { }
+	Skew2D(const NumericValue* values) noexcept;
+	Skew2D(float x, float y, Property::Unit unit = Property::DEG) noexcept;
 };
 
 struct RMLUICORE_API Perspective : public UnresolvedPrimitive< 1 >
 {
-	Perspective(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
+	Perspective(const NumericValue* values) noexcept;
 };
 
-struct DecomposedMatrix4
-{
+struct RMLUICORE_API DecomposedMatrix4 {
 	Vector4f perspective;
 	Vector4f quaternion;
 	Vector3f translation;
 	Vector3f scale;
 	Vector3f skew;
-
-	bool Decompose(const Matrix4f& m);
 };
 
+} // namespace Transforms
 
-struct RMLUICORE_API PrimitiveVariant {
+
+/**
+	The TransformPrimitive struct is the base struct of geometric transforms such as rotations, scalings and translations.
+	Instances of this struct are added to Rml::Transform during parsing of the 'transform' property.
+
+	@author Markus Schöngart
+	@see Rml::Transform
+	@see Rml::PropertyParserTransform
+ */
+struct RMLUICORE_API TransformPrimitive {
 
 	enum Type {
 		MATRIX2D, MATRIX3D,
@@ -263,96 +228,57 @@ struct RMLUICORE_API PrimitiveVariant {
 		PERSPECTIVE, DECOMPOSEDMATRIX4
 	};
 
-	PrimitiveVariant(Type type) : type(type) {}
+	TransformPrimitive(Transforms::Matrix2D          p) : type(MATRIX2D) { matrix_2d = p; }
+	TransformPrimitive(Transforms::Matrix3D          p) : type(MATRIX3D) { matrix_3d = p; }
+	TransformPrimitive(Transforms::TranslateX        p) : type(TRANSLATEX) { translate_x = p; }
+	TransformPrimitive(Transforms::TranslateY        p) : type(TRANSLATEY) { translate_y = p; }
+	TransformPrimitive(Transforms::TranslateZ        p) : type(TRANSLATEZ) { translate_z = p; }
+	TransformPrimitive(Transforms::Translate2D       p) : type(TRANSLATE2D) { translate_2d = p; }
+	TransformPrimitive(Transforms::Translate3D       p) : type(TRANSLATE3D) { translate_3d = p; }
+	TransformPrimitive(Transforms::ScaleX            p) : type(SCALEX) { scale_x = p; }
+	TransformPrimitive(Transforms::ScaleY            p) : type(SCALEY) { scale_y = p; }
+	TransformPrimitive(Transforms::ScaleZ            p) : type(SCALEZ) { scale_z = p; }
+	TransformPrimitive(Transforms::Scale2D           p) : type(SCALE2D) { scale_2d = p; }
+	TransformPrimitive(Transforms::Scale3D           p) : type(SCALE3D) { scale_3d = p; }
+	TransformPrimitive(Transforms::RotateX           p) : type(ROTATEX) { rotate_x = p; }
+	TransformPrimitive(Transforms::RotateY           p) : type(ROTATEY) { rotate_y = p; }
+	TransformPrimitive(Transforms::RotateZ           p) : type(ROTATEZ) { rotate_z = p; }
+	TransformPrimitive(Transforms::Rotate2D          p) : type(ROTATE2D) { rotate_2d = p; }
+	TransformPrimitive(Transforms::Rotate3D          p) : type(ROTATE3D) { rotate_3d = p; }
+	TransformPrimitive(Transforms::SkewX             p) : type(SKEWX) { skew_x = p; }
+	TransformPrimitive(Transforms::SkewY             p) : type(SKEWY) { skew_y = p; }
+	TransformPrimitive(Transforms::Skew2D            p) : type(SKEW2D) { skew_2d = p; }
+	TransformPrimitive(Transforms::Perspective       p) : type(PERSPECTIVE) { perspective = p; }
+	TransformPrimitive(Transforms::DecomposedMatrix4 p) : type(DECOMPOSEDMATRIX4) { decomposed_matrix_4 = p; }
 
 	Type type;
 
 	union {
-		Matrix2D matrix_2d;
-		Matrix3D matrix_3d;
-		TranslateX translate_x;
-		TranslateY translate_y;
-		TranslateZ translate_z;
-		Translate2D translate_2d;
-		Translate3D translate_3d;
-		ScaleX scale_x;
-		ScaleY scale_y;
-		ScaleZ scale_z;
-		Scale2D scale_2d;
-		Scale3D scale_3d;
-		RotateX rotate_x;
-		RotateY rotate_y;
-		RotateZ rotate_z;
-		Rotate2D rotate_2d;
-		Rotate3D rotate_3d;
-		SkewX skew_x;
-		SkewY skew_y;
-		Skew2D skew_2d;
-		Perspective perspective;
-		DecomposedMatrix4 decomposed_matrix_4;
+		Transforms::Matrix2D matrix_2d;
+		Transforms::Matrix3D matrix_3d;
+		Transforms::TranslateX translate_x;
+		Transforms::TranslateY translate_y;
+		Transforms::TranslateZ translate_z;
+		Transforms::Translate2D translate_2d;
+		Transforms::Translate3D translate_3d;
+		Transforms::ScaleX scale_x;
+		Transforms::ScaleY scale_y;
+		Transforms::ScaleZ scale_z;
+		Transforms::Scale2D scale_2d;
+		Transforms::Scale3D scale_3d;
+		Transforms::RotateX rotate_x;
+		Transforms::RotateY rotate_y;
+		Transforms::RotateZ rotate_z;
+		Transforms::Rotate2D rotate_2d;
+		Transforms::Rotate3D rotate_3d;
+		Transforms::SkewX skew_x;
+		Transforms::SkewY skew_y;
+		Transforms::Skew2D skew_2d;
+		Transforms::Perspective perspective;
+		Transforms::DecomposedMatrix4 decomposed_matrix_4;
 	};
 };
 
 
-/**
-	The Primitive struct is the base struct of geometric transforms such as rotations, scalings and translations.
-	Instances of this struct are added to a Rml::Transform instance by the Rml::PropertyParserTransform, which
-	is responsible for parsing the `transform' property.
-
-	@author Markus Schöngart
-	@see Rml::Transform
-	@see Rml::PropertyParserTransform
- */
-struct RMLUICORE_API Primitive
-{
-	PrimitiveVariant primitive;
-
-	Primitive(Matrix2D          p) : primitive(PrimitiveVariant::MATRIX2D) { primitive.matrix_2d = p; }
-	Primitive(Matrix3D          p) : primitive(PrimitiveVariant::MATRIX3D) { primitive.matrix_3d = p; }
-	Primitive(TranslateX        p) : primitive(PrimitiveVariant::TRANSLATEX) { primitive.translate_x = p; }
-	Primitive(TranslateY        p) : primitive(PrimitiveVariant::TRANSLATEY) { primitive.translate_y = p; }
-	Primitive(TranslateZ        p) : primitive(PrimitiveVariant::TRANSLATEZ) { primitive.translate_z = p; }
-	Primitive(Translate2D       p) : primitive(PrimitiveVariant::TRANSLATE2D) { primitive.translate_2d = p; }
-	Primitive(Translate3D       p) : primitive(PrimitiveVariant::TRANSLATE3D) { primitive.translate_3d = p; }
-	Primitive(ScaleX            p) : primitive(PrimitiveVariant::SCALEX) { primitive.scale_x = p; }
-	Primitive(ScaleY            p) : primitive(PrimitiveVariant::SCALEY) { primitive.scale_y = p; }
-	Primitive(ScaleZ            p) : primitive(PrimitiveVariant::SCALEZ) { primitive.scale_z = p; }
-	Primitive(Scale2D           p) : primitive(PrimitiveVariant::SCALE2D) { primitive.scale_2d = p; }
-	Primitive(Scale3D           p) : primitive(PrimitiveVariant::SCALE3D) { primitive.scale_3d = p; }
-	Primitive(RotateX           p) : primitive(PrimitiveVariant::ROTATEX) { primitive.rotate_x = p; }
-	Primitive(RotateY           p) : primitive(PrimitiveVariant::ROTATEY) { primitive.rotate_y = p; }
-	Primitive(RotateZ           p) : primitive(PrimitiveVariant::ROTATEZ) { primitive.rotate_z = p; }
-	Primitive(Rotate2D          p) : primitive(PrimitiveVariant::ROTATE2D) { primitive.rotate_2d = p; }
-	Primitive(Rotate3D          p) : primitive(PrimitiveVariant::ROTATE3D) { primitive.rotate_3d = p; }
-	Primitive(SkewX             p) : primitive(PrimitiveVariant::SKEWX) { primitive.skew_x = p; }
-	Primitive(SkewY             p) : primitive(PrimitiveVariant::SKEWY) { primitive.skew_y = p; }
-	Primitive(Skew2D            p) : primitive(PrimitiveVariant::SKEW2D) { primitive.skew_2d = p; }
-	Primitive(Perspective       p) : primitive(PrimitiveVariant::PERSPECTIVE) { primitive.perspective = p; }
-	Primitive(DecomposedMatrix4 p) : primitive(PrimitiveVariant::DECOMPOSEDMATRIX4) { primitive.decomposed_matrix_4 = p; }
-
-	void SetIdentity() noexcept;
-
-	bool ResolveTransform(Matrix4f& m, 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.
-	// Returns false if the owning transform must to be converted to a DecomposedMatrix4 primitive.
-	bool PrepareForInterpolation(Element& e) noexcept;
-
-	// If primitives do not match, try to convert them to a common generic type, e.g. TranslateX -> Translate3D.
-	// Returns true if they are already the same type or were converted to a common generic type.
-	static bool TryConvertToMatchingGenericType(Primitive& p0, Primitive& p1) noexcept;
-
-	// Interpolate this primitive with another primitive, weighted by alpha [0, 1].
-	// Primitives must be of same type and PrepareForInterpolation() must previously have been called on both.
-	bool InterpolateWith(const Primitive& other, float alpha) noexcept;
-
-	String ToString() const noexcept;
-
-};
-
-
-
-}
 } // namespace Rml
 #endif

+ 1 - 0
Source/Core/DataModel.cpp

@@ -30,6 +30,7 @@
 #include "../../Include/RmlUi/Core/DataController.h"
 #include "../../Include/RmlUi/Core/DataView.h"
 #include "../../Include/RmlUi/Core/Element.h"
+#include <array>
 
 namespace Rml {
 

+ 1 - 0
Source/Core/DecoratorNinePatch.cpp

@@ -31,6 +31,7 @@
 #include "../../Include/RmlUi/Core/Geometry.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
+#include <array>
 
 namespace Rml {
 

+ 6 - 9
Source/Core/Element.cpp

@@ -43,7 +43,6 @@
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
-#include "../../Include/RmlUi/Core/TransformState.h"
 #include "Clock.h"
 #include "ComputeProperty.h"
 #include "ElementAnimation.h"
@@ -59,6 +58,8 @@
 #include "PropertiesIterator.h"
 #include "Pool.h"
 #include "StyleSheetParser.h"
+#include "TransformState.h"
+#include "TransformUtilities.h"
 #include "XMLParseTools.h"
 #include <algorithm>
 #include <cmath>
@@ -2565,14 +2566,10 @@ void Element::UpdateTransformState()
 			const int n = computed.transform->GetNumPrimitives();
 			for (int i = 0; i < n; ++i)
 			{
-				const Transforms::Primitive& primitive = computed.transform->GetPrimitive(i);
-
-				Matrix4f matrix;
-				if (primitive.ResolveTransform(matrix, *this))
-				{
-					transform *= matrix;
-					have_transform = true;
-				}
+				const TransformPrimitive& primitive = computed.transform->GetPrimitive(i);
+				Matrix4f matrix = TransformUtilities::ResolveTransform(primitive, *this);
+				transform *= matrix;
+				have_transform = true;
 			}
 
 			if(have_transform)

+ 19 - 19
Source/Core/ElementAnimation.cpp

@@ -28,6 +28,7 @@
 
 #include "ElementAnimation.h"
 #include "ElementStyle.h"
+#include "TransformUtilities.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
@@ -62,16 +63,15 @@ static bool CombineAndDecompose(Transform& t, Element& e)
 {
 	Matrix4f m = Matrix4f::Identity();
 
-	for (auto& primitive : t.GetPrimitives())
+	for (TransformPrimitive& primitive : t.GetPrimitives())
 	{
-		Matrix4f m_primitive;
-		if (primitive.ResolveTransform(m_primitive, e))
-			m *= m_primitive;
+		Matrix4f m_primitive = TransformUtilities::ResolveTransform(primitive, e);
+		m *= m_primitive;
 	}
 
 	Transforms::DecomposedMatrix4 decomposed;
 
-	if (!decomposed.Decompose(m))
+	if (!TransformUtilities::Decompose(decomposed, m))
 		return false;
 
 	t.ClearPrimitives();
@@ -150,8 +150,8 @@ static Property InterpolateProperties(const Property & p0, const Property& p1, f
 
 		for (size_t i = 0; i < prim0.size(); i++)
 		{
-			Transforms::Primitive p = prim0[i];
-			if (!p.InterpolateWith(prim1[i], alpha))
+			TransformPrimitive p = prim0[i];
+			if (!TransformUtilities::InterpolateWith(p, prim1[i], alpha))
 			{
 				RMLUI_ERRORMSG("Transform primitives can not be interpolated. Were the transforms properly prepared for interpolation?");
 				return Property{ t0, Property::TRANSFORM };
@@ -189,15 +189,15 @@ static PrepareTransformResult PrepareTransformPair(Transform& t0, Transform& t1,
 
 		for (size_t i = 0; i < prims0.size(); i++)
 		{
-			auto p0_type = prims0[i].primitive.type;
-			auto p1_type = prims1[i].primitive.type;
+			auto p0_type = prims0[i].type;
+			auto p1_type = prims1[i].type;
 
 			// See if they are the same or can be converted to a matching generic type.
-			if (Primitive::TryConvertToMatchingGenericType(prims0[i], prims1[i]))
+			if (TransformUtilities::TryConvertToMatchingGenericType(prims0[i], prims1[i]))
 			{
-				if (prims0[i].primitive.type != p0_type)
+				if (prims0[i].type != p0_type)
 					result = PrepareTransformResult((int)result | (int)PrepareTransformResult::ChangedT0);
-				if (prims1[i].primitive.type != p1_type)
+				if (prims1[i].type != p1_type)
 					result = PrepareTransformResult((int)result | (int)PrepareTransformResult::ChangedT1);
 			}
 			else
@@ -239,13 +239,13 @@ static PrepareTransformResult PrepareTransformPair(Transform& t0, Transform& t1,
 
 			for (; i_big < big.size(); i_big++)
 			{
-				auto big_type = big[i_big].primitive.type;
+				auto big_type = big[i_big].type;
 
-				if (Primitive::TryConvertToMatchingGenericType(small[i_small], big[i_big]))
+				if (TransformUtilities::TryConvertToMatchingGenericType(small[i_small], big[i_big]))
 				{
 					// They matched exactly or in their more generic form. One or both primitives may have been converted.
 					match_success = true;
-					if (big[i_big].primitive.type != big_type)
+					if (big[i_big].type != big_type)
 						changed_big = true;
 				}
 
@@ -273,8 +273,8 @@ static PrepareTransformResult PrepareTransformPair(Transform& t0, Transform& t1,
 			{
 				for (size_t i = i0; i < match_index; i++)
 				{
-					Primitive p = big[i];
-					p.SetIdentity();
+					TransformPrimitive p = big[i];
+					TransformUtilities::SetIdentity(p);
 					small.insert(small.begin() + i, p);
 				}
 
@@ -320,9 +320,9 @@ static bool PrepareTransforms(std::vector<AnimationKey>& keys, Element& element,
 		bool must_decompose = false;
 		Transform& transform = *property.value.GetReference<TransformPtr>();
 
-		for (auto& primitive : transform.GetPrimitives())
+		for (TransformPrimitive& primitive : transform.GetPrimitives())
 		{
-			if (!primitive.PrepareForInterpolation(element))
+			if (!TransformUtilities::PrepareForInterpolation(primitive, element))
 			{
 				must_decompose = true;
 				break;

+ 1 - 1
Source/Core/ElementUtilities.cpp

@@ -37,9 +37,9 @@
 #include "../../Include/RmlUi/Core/Factory.h"
 #include "../../Include/RmlUi/Core/FontEngineInterface.h"
 #include "../../Include/RmlUi/Core/RenderInterface.h"
-#include "../../Include/RmlUi/Core/TransformState.h"
 #include "ElementStyle.h"
 #include "LayoutEngine.h"
+#include "TransformState.h"
 #include <queue>
 #include <limits>
 

+ 1 - 0
Source/Core/Elements/ElementProgressBar.cpp

@@ -36,6 +36,7 @@
 #include "../../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../../Include/RmlUi/Core/URL.h"
 #include <algorithm>
+#include <array>
 
 namespace Rml {
 

+ 1 - 0
Source/Core/StyleSheet.cpp

@@ -41,6 +41,7 @@
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/FontEffectInstancer.h"
 #include <algorithm>
+#include <array>
 
 namespace Rml {
 

+ 5 - 5
Source/Core/Transform.cpp

@@ -26,10 +26,10 @@
  *
  */
 
+#include "../../Include/RmlUi/Core/Transform.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
 #include "../../Include/RmlUi/Core/Property.h"
-#include "../../Include/RmlUi/Core/Transform.h"
 
 namespace Rml {
 
@@ -38,12 +38,12 @@ Transform::Transform()
 {
 }
 
-Transform::Transform(std::vector<Transforms::Primitive> primitives) 
+Transform::Transform(PrimitiveList primitives) 
 	: primitives(primitives)
 {
 }
 
-Property Transform::MakeProperty(std::vector<Transforms::Primitive> primitives)
+Property Transform::MakeProperty(PrimitiveList primitives)
 {
 	Property p{ TransformPtr{new Transform{primitives}}, Property::TRANSFORM };
 	p.definition = StyleSheetSpecification::GetProperty(PropertyId::Transform);
@@ -55,7 +55,7 @@ void Transform::ClearPrimitives()
 	primitives.clear();
 }
 
-void Transform::AddPrimitive(const Transforms::Primitive & p)
+void Transform::AddPrimitive(const TransformPrimitive & p)
 {
 	primitives.push_back(p);
 }
@@ -65,7 +65,7 @@ int Transform::GetNumPrimitives() const noexcept
 	return (int)primitives.size();
 }
 
-const Transforms::Primitive & Transform::GetPrimitive(int i) const noexcept 
+const TransformPrimitive & Transform::GetPrimitive(int i) const noexcept 
 {
 	return primitives[i];
 }

+ 89 - 905
Source/Core/TransformPrimitive.cpp

@@ -31,968 +31,152 @@
 #include "../../Include/RmlUi/Core/TypeConverter.h"
 
 namespace Rml {
-
 namespace Transforms {
 
-static Vector3f Combine(const Vector3f& a, const Vector3f& b, float a_scale, float b_scale)
-{
-	Vector3f result;
-	result.x = a_scale * a.x + b_scale * b.x;
-	result.y = a_scale * a.y + b_scale * b.y;
-	result.z = a_scale * a.z + b_scale * b.z;
-	return result;
-}
-
-
-// Interpolate two quaternions a, b with weight alpha [0, 1]
-static Vector4f QuaternionSlerp(const Vector4f& a, const Vector4f& b, float alpha)
-{
-	using namespace Math;
-
-	const float eps = 0.9995f;
-
-	float dot = a.DotProduct(b);
-	dot = Clamp(dot, -1.f, 1.f);
-
-	if (dot > eps)
-		return a;
-
-	float theta = ACos(dot);
-	float w = Sin(alpha * theta) / SquareRoot(1.f - dot * dot);
-	float a_scale = Cos(alpha*theta) - dot * w;
-
-	Vector4f result;
-	for (int i = 0; i < 4; i++)
-	{
-		result[i] = a[i] * a_scale + b[i] * w;
-	}
-
-	return result;
-}
-
-
-
-NumericValue::NumericValue() noexcept
-	: number(), unit(Property::UNKNOWN)
-{
-}
-
-NumericValue::NumericValue(float number, Property::Unit unit) noexcept
-	: number(number), unit(unit)
-{
-}
-
-float NumericValue::ResolveLengthPercentage(Element& e, float base) const noexcept
-{
-	Property prop;
-	prop.value = Variant(number);
-	prop.unit = unit;
-	return e.ResolveNumericProperty(&prop, base);
-}
-
-float NumericValue::ResolveWidth(Element& e) const noexcept
-{
-	if(unit & (Property::PX | Property::NUMBER)) return number;
-	return ResolveLengthPercentage(e, e.GetBox().GetSize(Box::BORDER).x);
-}
 
-float NumericValue::ResolveHeight(Element& e) const noexcept
+/// Returns the numeric value converted to 'base_unit'. Only accepts base units of 'Number' or 'Rad':
+///   'Number' will pass-through the provided value.
+///   'Rad' will convert {Rad, Deg, %} -> Rad.
+static float ResolvePrimitiveAbsoluteValue(NumericValue value, Property::Unit base_unit) noexcept
 {
-	if (unit & (Property::PX | Property::NUMBER)) return number;
-	return ResolveLengthPercentage(e, e.GetBox().GetSize(Box::BORDER).y);
-}
+	RMLUI_ASSERT(base_unit == Property::RAD || base_unit == Property::NUMBER);
 
-float NumericValue::ResolveDepth(Element& e) const noexcept
-{
-	if (unit & (Property::PX | Property::NUMBER)) return number;
-	Vector2f size = e.GetBox().GetSize(Box::BORDER);
-	return ResolveLengthPercentage(e, Math::Max(size.x, size.y));
-}
-
-float NumericValue::ResolveAbsoluteUnit(Property::Unit base_unit) const noexcept
-{
-	if(base_unit == Property::RAD)
+	if (base_unit == Property::RAD)
 	{
-		switch (unit)
+		switch (value.unit)
 		{
-		case Property::NUMBER:
-		case Property::DEG:
-			return Math::DegreesToRadians(number);
 		case Property::RAD:
-			return number;
+			return value.number;
+		case Property::DEG:
+			return Math::DegreesToRadians(value.number);
 		case Property::PERCENT:
-			return number * 0.01f * 2.0f * Math::RMLUI_PI;
+			return value.number * 0.01f * 2.0f * Math::RMLUI_PI;
 		default:
-			break;
+			Log::Message(Log::LT_WARNING, "Trying to pass a non-angle unit to a property expecting an angle.");
 		}
 	}
-	return number;
-}
-
-String NumericValue::ToString() const noexcept
-{
-	Property prop;
-	prop.value = Variant(number);
-	prop.unit = unit;
-	return prop.ToString();
-}
-
-
-
-
-
-
-
-
-
-
-struct ResolveTransformVisitor
-{
-	Matrix4f& m;
-	Element& e;
-
-	bool operator()(const Matrix2D& p)
-	{
-		m = Matrix4f::FromRows(
-			Vector4f(p.values[0], p.values[2], 0, p.values[4]),
-			Vector4f(p.values[1], p.values[3], 0, p.values[5]),
-			Vector4f(0, 0, 1, 0),
-			Vector4f(0, 0, 0, 1)
-		);
-		return true;
-	}
-
-	bool operator()(const Matrix3D& p)
-	{
-		m = Matrix4f::FromColumns(
-			Vector4f(p.values[0], p.values[1], p.values[2], p.values[3]),
-			Vector4f(p.values[4], p.values[5], p.values[6], p.values[7]),
-			Vector4f(p.values[8], p.values[9], p.values[10], p.values[11]),
-			Vector4f(p.values[12], p.values[13], p.values[14], p.values[15])
-		);
-		return true;
-	}
-
-	bool operator()(const TranslateX& p)
-	{
-		m = Matrix4f::TranslateX(p.values[0].ResolveWidth(e));
-		return true;
-	}
-
-	bool operator()(const TranslateY& p)
-	{
-		m = Matrix4f::TranslateY(p.values[0].ResolveHeight(e));
-		return true;
-	}
-
-	bool operator()(const TranslateZ& p)
-	{
-		m = Matrix4f::TranslateZ(p.values[0].ResolveDepth(e));
-		return true;
-	}
-
-	bool operator()(const Translate2D& p)
-	{
-		m = Matrix4f::Translate(
-			p.values[0].ResolveWidth(e),
-			p.values[1].ResolveHeight(e),
-			0
-		);
-		return true;
-	}
-
-	bool operator()(const Translate3D& p)
-	{
-		m = Matrix4f::Translate(
-			p.values[0].ResolveWidth(e),
-			p.values[1].ResolveHeight(e),
-			p.values[2].ResolveDepth(e)
-		);
-		return true;
-	}
-
-	bool operator()(const ScaleX& p)
-	{
-		m = Matrix4f::ScaleX(p.values[0]);
-		return true;
-	}
-
-	bool operator()(const ScaleY& p)
-	{
-		m = Matrix4f::ScaleY(p.values[0]);
-		return true;
-	}
-
-	bool operator()(const ScaleZ& p)
-	{
-		m = Matrix4f::ScaleZ(p.values[0]);
-		return true;
-	}
-
-	bool operator()(const Scale2D& p)
-	{
-		m = Matrix4f::Scale(p.values[0], p.values[1], 1);
-		return true;
-	}
-
-	bool operator()(const Scale3D& p)
-	{
-		m = Matrix4f::Scale(p.values[0], p.values[1], p.values[2]);
-		return true;
-	}
-
-	bool operator()(const RotateX& p)
-	{
-		m = Matrix4f::RotateX(p.values[0]);
-		return true;
-	}
-
-	bool operator()(const RotateY& p)
-	{
-		m = Matrix4f::RotateY(p.values[0]);
-		return true;
-	}
-
-	bool operator()(const RotateZ& p)
-	{
-		m = Matrix4f::RotateZ(p.values[0]);
-		return true;
-	}
-
-	bool operator()(const Rotate2D& p)
-	{
-		m = Matrix4f::RotateZ(p.values[0]);
-		return true;
-	}
-
-	bool operator()(const Rotate3D& p)
-	{
-		m = Matrix4f::Rotate(Vector3f(p.values[0], p.values[1], p.values[2]), p.values[3]);
-		return true;
-	}
-
-	bool operator()(const SkewX& p)
-	{
-		m = Matrix4f::SkewX(p.values[0]);
-		return true;
-	}
-
-	bool operator()(const SkewY& p)
-	{
-		m = Matrix4f::SkewY(p.values[0]);
-		return true;
-	}
-
-	bool operator()(const Skew2D& p)
-	{
-		m = Matrix4f::Skew(p.values[0], p.values[1]);
-		return true;
-	}
-
-	bool operator()(const DecomposedMatrix4& p)
-	{
-		m = Matrix4f::Compose(p.translation, p.scale, p.skew, p.perspective, p.quaternion);
-		return true;
-	}
-	bool operator()(const Perspective& p)
-	{
-		m = Matrix4f::Perspective(p.values[0].ResolveDepth(e));
-		return true;
-	}
-
-
-	bool run(const PrimitiveVariant& primitive)
-	{
-		switch (primitive.type)
-		{
-		case PrimitiveVariant::MATRIX2D: return this->operator()(primitive.matrix_2d);
-		case PrimitiveVariant::MATRIX3D: return this->operator()(primitive.matrix_3d);
-		case PrimitiveVariant::TRANSLATEX: return this->operator()(primitive.translate_x);
-		case PrimitiveVariant::TRANSLATEY: return this->operator()(primitive.translate_y);
-		case PrimitiveVariant::TRANSLATEZ: return this->operator()(primitive.translate_z);
-		case PrimitiveVariant::TRANSLATE2D: return this->operator()(primitive.translate_2d);
-		case PrimitiveVariant::TRANSLATE3D: return this->operator()(primitive.translate_3d);
-		case PrimitiveVariant::SCALEX: return this->operator()(primitive.scale_x);
-		case PrimitiveVariant::SCALEY: return this->operator()(primitive.scale_y);
-		case PrimitiveVariant::SCALEZ: return this->operator()(primitive.scale_z);
-		case PrimitiveVariant::SCALE2D: return this->operator()(primitive.scale_2d);
-		case PrimitiveVariant::SCALE3D: return this->operator()(primitive.scale_3d);
-		case PrimitiveVariant::ROTATEX: return this->operator()(primitive.rotate_x);
-		case PrimitiveVariant::ROTATEY: return this->operator()(primitive.rotate_y);
-		case PrimitiveVariant::ROTATEZ: return this->operator()(primitive.rotate_z);
-		case PrimitiveVariant::ROTATE2D: return this->operator()(primitive.rotate_2d);
-		case PrimitiveVariant::ROTATE3D: return this->operator()(primitive.rotate_3d);
-		case PrimitiveVariant::SKEWX: return this->operator()(primitive.skew_x);
-		case PrimitiveVariant::SKEWY: return this->operator()(primitive.skew_y);
-		case PrimitiveVariant::SKEW2D: return this->operator()(primitive.skew_2d);
-		case PrimitiveVariant::PERSPECTIVE: return this->operator()(primitive.perspective);
-		case PrimitiveVariant::DECOMPOSEDMATRIX4: return this->operator()(primitive.decomposed_matrix_4);
-		default:
-			break;
-		}
-		RMLUI_ASSERT(false);
-		return false;
-	}
-};
-
-
-
-
-
-bool Primitive::ResolveTransform(Matrix4f & m, Element & e) const noexcept
-{
-	ResolveTransformVisitor visitor{ m, e };
-
-	bool result = visitor.run(primitive);
-
-	return result;
-}
-
-struct SetIdentityVisitor
-{
-	template <size_t N>
-	void operator()(ResolvedPrimitive<N>& p)
-	{
-		for (auto& value : p.values)
-			value = 0.0f;
-	}
-	template <size_t N>
-	void operator()(UnresolvedPrimitive<N>& p)
-	{
-		for (auto& value : p.values)
-			value.number = 0.0f;
-	}
-	void operator()(Matrix2D& p)
-	{
-		for (int i = 0; i < 6; i++)
-			p.values[i] = ((i == 0 || i == 3) ? 1.0f : 0.0f);
-	}
-	void operator()(Matrix3D& p)
-	{
-		for (int i = 0; i < 16; i++)
-			p.values[i] = ((i % 5) == 0 ? 1.0f : 0.0f);
-	}
-	void operator()(ScaleX& p)
-	{
-		p.values[0] = 1;
-	}
-	void operator()(ScaleY& p)
-	{
-		p.values[0] = 1;
-	}
-	void operator()(ScaleZ& p)
-	{
-		p.values[0] = 1;
-	}
-	void operator()(Scale2D& p)
-	{
-		p.values[0] = p.values[1] = 1;
-	}
-	void operator()(Scale3D& p)
+	else if (base_unit == Property::NUMBER && value.unit != Property::NUMBER)
 	{
-		p.values[0] = p.values[1] = p.values[2] = 1;
-	}
-	void operator()(DecomposedMatrix4& p)
-	{
-		p.perspective = Vector4f(0, 0, 0, 1);
-		p.quaternion = Vector4f(0, 0, 0, 1);
-		p.translation = Vector3f(0, 0, 0);
-		p.scale = Vector3f(1, 1, 1);
-		p.skew = Vector3f(0, 0, 0);
-	}
-
-
-	void run(PrimitiveVariant& primitive)
-	{
-		switch (primitive.type)
-		{
-		case PrimitiveVariant::MATRIX2D: this->operator()(primitive.matrix_2d); break;
-		case PrimitiveVariant::MATRIX3D: this->operator()(primitive.matrix_3d); break;
-		case PrimitiveVariant::TRANSLATEX: this->operator()(primitive.translate_x); break;
-		case PrimitiveVariant::TRANSLATEY: this->operator()(primitive.translate_y); break;
-		case PrimitiveVariant::TRANSLATEZ: this->operator()(primitive.translate_z); break;
-		case PrimitiveVariant::TRANSLATE2D: this->operator()(primitive.translate_2d); break;
-		case PrimitiveVariant::TRANSLATE3D: this->operator()(primitive.translate_3d); break;
-		case PrimitiveVariant::SCALEX: this->operator()(primitive.scale_x); break;
-		case PrimitiveVariant::SCALEY: this->operator()(primitive.scale_y); break;
-		case PrimitiveVariant::SCALEZ: this->operator()(primitive.scale_z); break;
-		case PrimitiveVariant::SCALE2D: this->operator()(primitive.scale_2d); break;
-		case PrimitiveVariant::SCALE3D: this->operator()(primitive.scale_3d); break;
-		case PrimitiveVariant::ROTATEX: this->operator()(primitive.rotate_x); break;
-		case PrimitiveVariant::ROTATEY: this->operator()(primitive.rotate_y); break;
-		case PrimitiveVariant::ROTATEZ: this->operator()(primitive.rotate_z); break;
-		case PrimitiveVariant::ROTATE2D: this->operator()(primitive.rotate_2d); break;
-		case PrimitiveVariant::ROTATE3D: this->operator()(primitive.rotate_3d); break;
-		case PrimitiveVariant::SKEWX: this->operator()(primitive.skew_x); break;
-		case PrimitiveVariant::SKEWY: this->operator()(primitive.skew_y); break;
-		case PrimitiveVariant::SKEW2D: this->operator()(primitive.skew_2d); break;
-		case PrimitiveVariant::PERSPECTIVE: this->operator()(primitive.perspective); break;
-		case PrimitiveVariant::DECOMPOSEDMATRIX4: this->operator()(primitive.decomposed_matrix_4); break;
-		default:
-			RMLUI_ASSERT(false);
-			break;
-		}
+		Log::Message(Log::LT_WARNING, "A unit was passed to a property which expected a unit-less number.");
 	}
-};
 
-void Primitive::SetIdentity() noexcept
-{
-	SetIdentityVisitor{}.run(primitive);
+	return value.number;
 }
 
 
-
-struct PrepareVisitor
-{
-	Element& e;
-
-	bool operator()(TranslateX& p)
-	{
-		p.values[0] = NumericValue{ p.values[0].ResolveWidth(e), Property::PX };
-		return true;
-	}
-	bool operator()(TranslateY& p)
-	{
-		p.values[0] = NumericValue{ p.values[0].ResolveHeight(e), Property::PX };
-		return true;
-	}
-	bool operator()(TranslateZ& p)
-	{
-		p.values[0] = NumericValue{ p.values[0].ResolveDepth(e), Property::PX };
-		return true;
-	}
-	bool operator()(Translate2D& p)
-	{
-		p.values[0] = NumericValue{ p.values[0].ResolveWidth(e), Property::PX };
-		p.values[1] = NumericValue{ p.values[1].ResolveHeight(e), Property::PX };
-		return true;
-	}
-	bool operator()(Translate3D& p)
-	{
-		p.values[0] = NumericValue{ p.values[0].ResolveWidth(e), Property::PX };
-		p.values[1] = NumericValue{ p.values[1].ResolveHeight(e), Property::PX };
-		p.values[2] = NumericValue{ p.values[2].ResolveDepth(e), Property::PX };
-		return true;
-	}
-	template <size_t N>
-	bool operator()(ResolvedPrimitive<N>& /*p*/)
-	{
-		// No conversion needed for resolved transforms (with some exceptions below)
-		return true;
-	}
-	bool operator()(DecomposedMatrix4& /*p*/)
-	{
-		return true;
-	}
-	bool operator()(Rotate3D& p)
-	{
-		// Rotate3D can be interpolated if and only if their rotation axes point in the same direction.
-		// We normalize the rotation vector here for easy comparison, and return true here. Later on we make the
-		// pair-wise check in 'TryConvertToMatchingGenericType' to see if we need to decompose.
-		Vector3f vec = Vector3f(p.values[0], p.values[1], p.values[2]).Normalise();
-		p.values[0] = vec.x;
-		p.values[1] = vec.y;
-		p.values[2] = vec.z;
-		return true;
-	}
-	bool operator()(Matrix3D& /*p*/)
-	{
-		// Matrices must be decomposed for interpolation
-		return false;
-	}
-	bool operator()(Matrix2D& /*p*/)
-	{
-		// Matrix2D can also be optimized for interpolation, but for now we decompose it to a full DecomposedMatrix4
-		return false;
-	}
-	bool operator()(Perspective& /*p*/)
-	{
-		// Perspective must be decomposed
-		return false;
-	}
-
-	bool run(PrimitiveVariant& primitive)
-	{
-		switch (primitive.type)
-		{
-		case PrimitiveVariant::MATRIX2D: return this->operator()(primitive.matrix_2d);
-		case PrimitiveVariant::MATRIX3D: return this->operator()(primitive.matrix_3d);
-		case PrimitiveVariant::TRANSLATEX: return this->operator()(primitive.translate_x);
-		case PrimitiveVariant::TRANSLATEY: return this->operator()(primitive.translate_y);
-		case PrimitiveVariant::TRANSLATEZ: return this->operator()(primitive.translate_z);
-		case PrimitiveVariant::TRANSLATE2D: return this->operator()(primitive.translate_2d);
-		case PrimitiveVariant::TRANSLATE3D: return this->operator()(primitive.translate_3d);
-		case PrimitiveVariant::SCALEX: return this->operator()(primitive.scale_x);
-		case PrimitiveVariant::SCALEY: return this->operator()(primitive.scale_y);
-		case PrimitiveVariant::SCALEZ: return this->operator()(primitive.scale_z);
-		case PrimitiveVariant::SCALE2D: return this->operator()(primitive.scale_2d);
-		case PrimitiveVariant::SCALE3D: return this->operator()(primitive.scale_3d);
-		case PrimitiveVariant::ROTATEX: return this->operator()(primitive.rotate_x);
-		case PrimitiveVariant::ROTATEY: return this->operator()(primitive.rotate_y);
-		case PrimitiveVariant::ROTATEZ: return this->operator()(primitive.rotate_z);
-		case PrimitiveVariant::ROTATE2D: return this->operator()(primitive.rotate_2d);
-		case PrimitiveVariant::ROTATE3D: return this->operator()(primitive.rotate_3d);
-		case PrimitiveVariant::SKEWX: return this->operator()(primitive.skew_x);
-		case PrimitiveVariant::SKEWY: return this->operator()(primitive.skew_y);
-		case PrimitiveVariant::SKEW2D: return this->operator()(primitive.skew_2d);
-		case PrimitiveVariant::PERSPECTIVE: return this->operator()(primitive.perspective);
-		case PrimitiveVariant::DECOMPOSEDMATRIX4: return this->operator()(primitive.decomposed_matrix_4);
-		default:
-			break;
-		}
-		RMLUI_ASSERT(false);
-		return false;
-	}
-};
-
-
-bool Primitive::PrepareForInterpolation(Element & e) noexcept
+template<size_t N>
+inline ResolvedPrimitive<N>::ResolvedPrimitive(const float* values) noexcept
 {
-	return PrepareVisitor{ e }.run(primitive);
+	for (size_t i = 0; i < N; ++i)
+		this->values[i] = values[i];
 }
 
-
-
-enum class GenericType { None, Scale3D, Translate3D, Rotate3D };
-
-struct GetGenericTypeVisitor
-{
-	GenericType run(const PrimitiveVariant& primitive)
-	{
-		switch (primitive.type)
-		{
-		case PrimitiveVariant::TRANSLATEX:  return GenericType::Translate3D;
-		case PrimitiveVariant::TRANSLATEY:  return GenericType::Translate3D;
-		case PrimitiveVariant::TRANSLATEZ:  return GenericType::Translate3D;
-		case PrimitiveVariant::TRANSLATE2D: return GenericType::Translate3D;
-		case PrimitiveVariant::TRANSLATE3D: return GenericType::Translate3D;
-		case PrimitiveVariant::SCALEX:      return GenericType::Scale3D;
-		case PrimitiveVariant::SCALEY:      return GenericType::Scale3D;
-		case PrimitiveVariant::SCALEZ:      return GenericType::Scale3D;
-		case PrimitiveVariant::SCALE2D:     return GenericType::Scale3D;
-		case PrimitiveVariant::SCALE3D:     return GenericType::Scale3D;
-		case PrimitiveVariant::ROTATEX:     return GenericType::Rotate3D;
-		case PrimitiveVariant::ROTATEY:     return GenericType::Rotate3D;
-		case PrimitiveVariant::ROTATEZ:     return GenericType::Rotate3D;
-		case PrimitiveVariant::ROTATE2D:    return GenericType::Rotate3D;
-		case PrimitiveVariant::ROTATE3D:    return GenericType::Rotate3D;
-		default:
-			break;
-		}
-		return GenericType::None;
-	}
-};
-
-
-struct ConvertToGenericTypeVisitor
-{
-	Translate3D operator()(const TranslateX& p) { return Translate3D{ p.values[0], {0.0f, Property::PX}, {0.0f, Property::PX} }; }
-	Translate3D operator()(const TranslateY& p) { return Translate3D{ {0.0f, Property::PX}, p.values[0], {0.0f, Property::PX} }; }
-	Translate3D operator()(const TranslateZ& p) { return Translate3D{ {0.0f, Property::PX}, {0.0f, Property::PX}, p.values[0] }; }
-	Translate3D operator()(const Translate2D& p) { return Translate3D{ p.values[0], p.values[1], {0.0f, Property::PX} }; }
-	Scale3D operator()(const ScaleX& p) { return Scale3D{ p.values[0], 1.0f, 1.0f }; }
-	Scale3D operator()(const ScaleY& p) { return Scale3D{  1.0f, p.values[0], 1.0f }; }
-	Scale3D operator()(const ScaleZ& p) { return Scale3D{  1.0f, 1.0f, p.values[0] }; }
-	Scale3D operator()(const Scale2D& p) { return Scale3D{ p.values[0], p.values[1], 1.0f }; }
-	Rotate3D operator()(const RotateX& p)  { return Rotate3D{ 1, 0, 0, p.values[0], Property::RAD }; }
-	Rotate3D operator()(const RotateY& p)  { return Rotate3D{ 0, 1, 0, p.values[0], Property::RAD }; }
-	Rotate3D operator()(const RotateZ& p)  { return Rotate3D{ 0, 0, 1, p.values[0], Property::RAD }; }
-	Rotate3D operator()(const Rotate2D& p) { return Rotate3D{ 0, 0, 1, p.values[0], Property::RAD }; }
-
-	template <typename T>
-	PrimitiveVariant operator()(const T& p) { RMLUI_ERROR; return p; }
-
-	PrimitiveVariant run(const PrimitiveVariant& primitive)
-	{
-		PrimitiveVariant result = primitive;
-		switch (primitive.type)
-		{
-		case PrimitiveVariant::TRANSLATEX:  result.type = PrimitiveVariant::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_x);  break;
-		case PrimitiveVariant::TRANSLATEY:  result.type = PrimitiveVariant::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_y);  break;
-		case PrimitiveVariant::TRANSLATEZ:  result.type = PrimitiveVariant::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_z);  break;
-		case PrimitiveVariant::TRANSLATE2D: result.type = PrimitiveVariant::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_2d); break;
-		case PrimitiveVariant::TRANSLATE3D: break;
-		case PrimitiveVariant::SCALEX:      result.type = PrimitiveVariant::SCALE3D;     result.scale_3d     = this->operator()(primitive.scale_x);      break;
-		case PrimitiveVariant::SCALEY:      result.type = PrimitiveVariant::SCALE3D;     result.scale_3d     = this->operator()(primitive.scale_y);      break;
-		case PrimitiveVariant::SCALEZ:      result.type = PrimitiveVariant::SCALE3D;     result.scale_3d     = this->operator()(primitive.scale_z);      break;
-		case PrimitiveVariant::SCALE2D:     result.type = PrimitiveVariant::SCALE3D;     result.scale_3d     = this->operator()(primitive.scale_2d);     break;
-		case PrimitiveVariant::SCALE3D:     break;
-		case PrimitiveVariant::ROTATEX:     result.type = PrimitiveVariant::ROTATE3D;    result.rotate_3d    = this->operator()(primitive.rotate_x);     break;
-		case PrimitiveVariant::ROTATEY:     result.type = PrimitiveVariant::ROTATE3D;    result.rotate_3d    = this->operator()(primitive.rotate_y);     break;
-		case PrimitiveVariant::ROTATEZ:     result.type = PrimitiveVariant::ROTATE3D;    result.rotate_3d    = this->operator()(primitive.rotate_z);     break;
-		case PrimitiveVariant::ROTATE2D:    result.type = PrimitiveVariant::ROTATE3D;    result.rotate_3d    = this->operator()(primitive.rotate_2d);    break;
-		case PrimitiveVariant::ROTATE3D:    break;
-		default:
-			RMLUI_ASSERT(false);
-			break;
-		}
-		return result;
-	}
-};
-
-static bool CanInterpolateRotate3D(const Rotate3D& p0, const Rotate3D& p1)
+template<size_t N>
+inline ResolvedPrimitive<N>::ResolvedPrimitive(const NumericValue* values) noexcept
 {
-	// Rotate3D can only be interpolated if and only if their rotation axes point in the same direction.
-	// Assumes each rotation axis has already been normalized.
-	auto& v0 = p0.values;
-	auto& v1 = p1.values;
-	return v0[0] == v1[0] && v0[1] == v1[1] && v0[2] == v1[2];
+	for (size_t i = 0; i < N; ++i)
+		this->values[i] = values[i].number;
 }
 
-
-bool Primitive::TryConvertToMatchingGenericType(Primitive & p0, Primitive & p1) noexcept
+template<size_t N>
+inline ResolvedPrimitive<N>::ResolvedPrimitive(const NumericValue* values, std::array<Property::Unit, N> base_units) noexcept
 {
-	if (p0.primitive.type == p1.primitive.type)
-	{
-		if(p0.primitive.type == PrimitiveVariant::ROTATE3D && !CanInterpolateRotate3D(p0.primitive.rotate_3d, p1.primitive.rotate_3d))
-			return false;
-
-		return true;
-	}
-
-	GenericType c0 = GetGenericTypeVisitor{}.run(p0.primitive);
-	GenericType c1 = GetGenericTypeVisitor{}.run(p1.primitive);
-
-	if (c0 == c1 && c0 != GenericType::None)
-	{
-		PrimitiveVariant new_p0 = ConvertToGenericTypeVisitor{}.run(p0.primitive);
-		PrimitiveVariant new_p1 = ConvertToGenericTypeVisitor{}.run(p1.primitive);
-		
-		RMLUI_ASSERT(new_p0.type == new_p1.type);
-		
-		if (new_p0.type == PrimitiveVariant::ROTATE3D && !CanInterpolateRotate3D(new_p0.rotate_3d, new_p1.rotate_3d))
-			return false;
-
-		p0.primitive = new_p0;
-		p1.primitive = new_p1;
-
-		return true;
-	}
-
-	return false;
+	for (size_t i = 0; i < N; ++i)
+		this->values[i] = ResolvePrimitiveAbsoluteValue(values[i], base_units[i]);
 }
 
-
-
-struct InterpolateVisitor
-{
-	const PrimitiveVariant& other_variant;
-	float alpha;
-
-	template <size_t N>
-	bool Interpolate(ResolvedPrimitive<N>& p0, const ResolvedPrimitive<N>& p1)
-	{
-		for (size_t i = 0; i < N; i++)
-			p0.values[i] = p0.values[i] * (1.0f - alpha) + p1.values[i] * alpha;
-		return true;
-	}
-	template <size_t N>
-	bool Interpolate(UnresolvedPrimitive<N>& p0, const UnresolvedPrimitive<N>& p1)
-	{
-		// Assumes that the underlying units have been resolved (e.g. to pixels)
-		for (size_t i = 0; i < N; i++)
-			p0.values[i].number = p0.values[i].number*(1.0f - alpha) + p1.values[i].number * alpha;
-		return true;
-	}
-	bool Interpolate(Rotate3D& p0, const Rotate3D& p1)
-	{
-		RMLUI_ASSERT(CanInterpolateRotate3D(p0, p1));
-		// We can only interpolate rotate3d if their rotation axes align. That should be the case if we get here, 
-		// otherwise the generic type matching should decompose them. Thus, we only need to interpolate
-		// the angle value here.
-		p0.values[3] = p0.values[3] * (1.0f - alpha) + p1.values[3] * alpha;
-		return true;
-	}
-	bool Interpolate(Matrix2D& /*p0*/, const Matrix2D& /*p1*/) { return false; /* Error if we get here, see PrepareForInterpolation() */ }
-	bool Interpolate(Matrix3D& /*p0*/, const Matrix3D& /*p1*/) { return false; /* Error if we get here, see PrepareForInterpolation() */ }
-	bool Interpolate(Perspective& /*p0*/, const Perspective& /*p1*/) { return false; /* Error if we get here, see PrepareForInterpolation() */ }
-
-	bool Interpolate(DecomposedMatrix4& p0, const DecomposedMatrix4& p1)
-	{
-		p0.perspective = p0.perspective * (1.0f - alpha) + p1.perspective * alpha;
-		p0.quaternion = QuaternionSlerp(p0.quaternion, p1.quaternion, alpha);
-		p0.translation = p0.translation * (1.0f - alpha) + p1.translation * alpha;
-		p0.scale = p0.scale* (1.0f - alpha) + p1.scale* alpha;
-		p0.skew = p0.skew* (1.0f - alpha) + p1.skew* alpha;
-		return true;
-	}
-
-	bool run(PrimitiveVariant& variant)
-	{
-		RMLUI_ASSERT(variant.type == other_variant.type);
-		switch (variant.type)
-		{
-		case PrimitiveVariant::MATRIX2D: return Interpolate(variant.matrix_2d, other_variant.matrix_2d);
-		case PrimitiveVariant::MATRIX3D: return Interpolate(variant.matrix_3d, other_variant.matrix_3d);
-		case PrimitiveVariant::TRANSLATEX: return Interpolate(variant.translate_x, other_variant.translate_x);
-		case PrimitiveVariant::TRANSLATEY: return Interpolate(variant.translate_y, other_variant.translate_y);
-		case PrimitiveVariant::TRANSLATEZ: return Interpolate(variant.translate_z, other_variant.translate_z);
-		case PrimitiveVariant::TRANSLATE2D: return Interpolate(variant.translate_2d, other_variant.translate_2d);
-		case PrimitiveVariant::TRANSLATE3D: return Interpolate(variant.translate_3d, other_variant.translate_3d);
-		case PrimitiveVariant::SCALEX: return Interpolate(variant.scale_x, other_variant.scale_x);
-		case PrimitiveVariant::SCALEY: return Interpolate(variant.scale_y, other_variant.scale_y);
-		case PrimitiveVariant::SCALEZ: return Interpolate(variant.scale_z, other_variant.scale_z);
-		case PrimitiveVariant::SCALE2D: return Interpolate(variant.scale_2d, other_variant.scale_2d);
-		case PrimitiveVariant::SCALE3D: return Interpolate(variant.scale_3d, other_variant.scale_3d);
-		case PrimitiveVariant::ROTATEX: return Interpolate(variant.rotate_x, other_variant.rotate_x);
-		case PrimitiveVariant::ROTATEY: return Interpolate(variant.rotate_y, other_variant.rotate_y);
-		case PrimitiveVariant::ROTATEZ: return Interpolate(variant.rotate_z, other_variant.rotate_z);
-		case PrimitiveVariant::ROTATE2D: return Interpolate(variant.rotate_2d, other_variant.rotate_2d);
-		case PrimitiveVariant::ROTATE3D: return Interpolate(variant.rotate_3d, other_variant.rotate_3d);
-		case PrimitiveVariant::SKEWX: return Interpolate(variant.skew_x, other_variant.skew_x);
-		case PrimitiveVariant::SKEWY: return Interpolate(variant.skew_y, other_variant.skew_y);
-		case PrimitiveVariant::SKEW2D: return Interpolate(variant.skew_2d, other_variant.skew_2d);
-		case PrimitiveVariant::PERSPECTIVE: return Interpolate(variant.perspective, other_variant.perspective);
-		case PrimitiveVariant::DECOMPOSEDMATRIX4: return Interpolate(variant.decomposed_matrix_4, other_variant.decomposed_matrix_4);
-		default:
-			break;
-		}
-		RMLUI_ASSERT(false);
-		return false;
-	}
-};
-
-
-bool Primitive::InterpolateWith(const Primitive & other, float alpha) noexcept
+template<size_t N>
+inline ResolvedPrimitive<N>::ResolvedPrimitive(std::array<NumericValue, N> values, std::array<Property::Unit, N> base_units) noexcept
 {
-	if (primitive.type != other.primitive.type)
-		return false;
-
-	bool result = InterpolateVisitor{ other.primitive, alpha }.run(primitive);
-
-	return result;
+	for (size_t i = 0; i < N; ++i)
+		this->values[i] = ResolvePrimitiveAbsoluteValue(values[i], base_units[i]);
 }
 
-
-
-
-
 template<size_t N>
-static inline String ToString(const Transforms::ResolvedPrimitive<N>& p, String unit, bool rad_to_deg = false, bool only_unit_on_last_value = false) noexcept {
-	float multiplier = 1.0f;
-	String tmp;
-	String result = "(";
-	for (size_t i = 0; i < N; i++) 
-	{
-		if (only_unit_on_last_value && i < N - 1)
-			multiplier = 1.0f;
-		else if (rad_to_deg) 
-			multiplier = 180.f / Math::RMLUI_PI;
+inline ResolvedPrimitive<N>::ResolvedPrimitive(std::array<float, N> values) noexcept : values(values) { }
 
-		if (TypeConverter<float, String>::Convert(p.values[i] * multiplier, tmp))
-			result += tmp;
-
-		if (!unit.empty() && (!only_unit_on_last_value || (i == N - 1)))
-			result += unit;
-
-		if (i < N - 1)
-			result += ", ";
-	}
-	result += ")";
-	return result;
+template<size_t N>
+inline UnresolvedPrimitive<N>::UnresolvedPrimitive(const NumericValue* values) noexcept
+{
+	for (size_t i = 0; i < N; ++i)
+		this->values[i] = values[i];
 }
 
 template<size_t N>
-static inline String ToString(const Transforms::UnresolvedPrimitive<N> & p) noexcept {
-	String result = "(";
-	for (size_t i = 0; i < N; i++) 
-	{
-		result += p.values[i].ToString();
-		if (i != N - 1) 
-			result += ", ";
-	}
-	result += ")";
-	return result;
-}
+inline UnresolvedPrimitive<N>::UnresolvedPrimitive(std::array<NumericValue, N> values) noexcept : values(values) { }
 
-static inline String ToString(const Transforms::DecomposedMatrix4& p) noexcept {
-	static const DecomposedMatrix4 d{
-		Vector4f(0, 0, 0, 1),
-		Vector4f(0, 0, 0, 1),
-		Vector3f(0, 0, 0),
-		Vector3f(1, 1, 1),
-		Vector3f(0, 0, 0)
-	}; 
-	String tmp;
-	String result;
-	
-	if(p.perspective != d.perspective && TypeConverter< Vector4f, String >::Convert(p.perspective, tmp))
-		result += "perspective(" + tmp + "), ";
-	if (p.quaternion != d.quaternion && TypeConverter< Vector4f, String >::Convert(p.quaternion, tmp))
-		result += "quaternion(" + tmp + "), ";
-	if (p.translation != d.translation && TypeConverter< Vector3f, String >::Convert(p.translation, tmp))
-		result += "translation(" + tmp + "), ";
-	if (p.scale != d.scale && TypeConverter< Vector3f, String >::Convert(p.scale, tmp))
-		result += "scale(" + tmp + "), ";
-	if (p.skew != d.skew && TypeConverter< Vector3f, String >::Convert(p.skew, tmp))
-		result += "skew(" + tmp + "), ";
-
-	if (result.size() > 2)
-		result.resize(result.size() - 2);
-
-	result = "decomposedMatrix3d{ " + result + " }";
-
-	return result;
-}
 
+Matrix2D::Matrix2D(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
 
-String ToString(const Transforms::Matrix2D & p) noexcept { return "matrix" + ToString(static_cast<const Transforms::ResolvedPrimitive< 6 >&>(p), ""); }
-String ToString(const Transforms::Matrix3D & p) noexcept { return "matrix3d" + ToString(static_cast<const Transforms::ResolvedPrimitive< 16 >&>(p), ""); }
-String ToString(const Transforms::TranslateX & p) noexcept { return "translateX" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 1 >&>(p)); }
-String ToString(const Transforms::TranslateY & p) noexcept { return "translateY" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 1 >&>(p)); }
-String ToString(const Transforms::TranslateZ & p) noexcept { return "translateZ" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 1 >&>(p)); }
-String ToString(const Transforms::Translate2D & p) noexcept { return "translate" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 2 >&>(p)); }
-String ToString(const Transforms::Translate3D & p) noexcept { return "translate3d" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 3 >&>(p)); }
-String ToString(const Transforms::ScaleX & p) noexcept { return "scaleX" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), ""); }
-String ToString(const Transforms::ScaleY & p) noexcept { return "scaleY" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), ""); }
-String ToString(const Transforms::ScaleZ & p) noexcept { return "scaleZ" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), ""); }
-String ToString(const Transforms::Scale2D & p) noexcept { return "scale" + ToString(static_cast<const Transforms::ResolvedPrimitive< 2 >&>(p), ""); }
-String ToString(const Transforms::Scale3D & p) noexcept { return "scale3d" + ToString(static_cast<const Transforms::ResolvedPrimitive< 3 >&>(p), ""); }
-String ToString(const Transforms::RotateX & p) noexcept { return "rotateX" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
-String ToString(const Transforms::RotateY & p) noexcept { return "rotateY" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
-String ToString(const Transforms::RotateZ & p) noexcept { return "rotateZ" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
-String ToString(const Transforms::Rotate2D & p) noexcept { return "rotate" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
-String ToString(const Transforms::Rotate3D & p) noexcept { return "rotate3d" + ToString(static_cast<const Transforms::ResolvedPrimitive< 4 >&>(p), "deg", true, true); }
-String ToString(const Transforms::SkewX & p) noexcept { return "skewX" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
-String ToString(const Transforms::SkewY & p) noexcept { return "skewY" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
-String ToString(const Transforms::Skew2D & p) noexcept { return "skew" + ToString(static_cast<const Transforms::ResolvedPrimitive< 2 >&>(p), "deg", true); }
-String ToString(const Transforms::Perspective & p) noexcept { return "perspective" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 1 >&>(p)); }
-
-
-struct ToStringVisitor
-{
-	String run(const PrimitiveVariant& variant)
-	{
-		switch (variant.type)
-		{
-		case PrimitiveVariant::MATRIX2D: return ToString(variant.matrix_2d);
-		case PrimitiveVariant::MATRIX3D: return ToString(variant.matrix_3d);
-		case PrimitiveVariant::TRANSLATEX: return ToString(variant.translate_x);
-		case PrimitiveVariant::TRANSLATEY: return ToString(variant.translate_y);
-		case PrimitiveVariant::TRANSLATEZ: return ToString(variant.translate_z);
-		case PrimitiveVariant::TRANSLATE2D: return ToString(variant.translate_2d);
-		case PrimitiveVariant::TRANSLATE3D: return ToString(variant.translate_3d);
-		case PrimitiveVariant::SCALEX: return ToString(variant.scale_x);
-		case PrimitiveVariant::SCALEY: return ToString(variant.scale_y);
-		case PrimitiveVariant::SCALEZ: return ToString(variant.scale_z);
-		case PrimitiveVariant::SCALE2D: return ToString(variant.scale_2d);
-		case PrimitiveVariant::SCALE3D: return ToString(variant.scale_3d);
-		case PrimitiveVariant::ROTATEX: return ToString(variant.rotate_x);
-		case PrimitiveVariant::ROTATEY: return ToString(variant.rotate_y);
-		case PrimitiveVariant::ROTATEZ: return ToString(variant.rotate_z);
-		case PrimitiveVariant::ROTATE2D: return ToString(variant.rotate_2d);
-		case PrimitiveVariant::ROTATE3D: return ToString(variant.rotate_3d);
-		case PrimitiveVariant::SKEWX: return ToString(variant.skew_x);
-		case PrimitiveVariant::SKEWY: return ToString(variant.skew_y);
-		case PrimitiveVariant::SKEW2D: return ToString(variant.skew_2d);
-		case PrimitiveVariant::PERSPECTIVE: return ToString(variant.perspective);
-		case PrimitiveVariant::DECOMPOSEDMATRIX4: return ToString(variant.decomposed_matrix_4);
-		default:
-			break;
-		}
-		RMLUI_ASSERT(false);
-		return String();
-	}
-};
+Matrix3D::Matrix3D(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
+Matrix3D::Matrix3D(const Matrix4f& matrix) noexcept : ResolvedPrimitive(matrix.data()) { }
 
-String Primitive::ToString() const noexcept
-{
-	String result = ToStringVisitor{}.run(primitive);
-	return result;
-}
+TranslateX::TranslateX(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
+TranslateX::TranslateX(float x, Property::Unit unit) noexcept : UnresolvedPrimitive({ NumericValue(x, unit) }) { }
 
+TranslateY::TranslateY(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
+TranslateY::TranslateY(float y, Property::Unit unit) noexcept : UnresolvedPrimitive({ NumericValue(y, unit) }) { }
 
-bool DecomposedMatrix4::Decompose(const Matrix4f & m)
-{
-	// Follows the procedure given in https://drafts.csswg.org/css-transforms-2/#interpolation-of-3d-matrices
+TranslateZ::TranslateZ(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
+TranslateZ::TranslateZ(float z, Property::Unit unit) noexcept : UnresolvedPrimitive({ NumericValue(z, unit) }) { }
 
-	const float eps = 0.0005f;
+Translate2D::Translate2D(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
+Translate2D::Translate2D(float x, float y, Property::Unit units) noexcept : UnresolvedPrimitive({ NumericValue(x, units), NumericValue(y, units) }) { }
 
-	if (Math::AbsoluteValue(m[3][3]) < eps)
-		return false;
+Translate3D::Translate3D(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
+Translate3D::Translate3D(NumericValue x, NumericValue y, NumericValue z) noexcept : UnresolvedPrimitive({ x, y, z }) { }
+Translate3D::Translate3D(float x, float y, float z, Property::Unit units) noexcept 
+	: UnresolvedPrimitive({ NumericValue(x, units), NumericValue(y, units), NumericValue(z, units) }) { }
 
+ScaleX::ScaleX(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
+ScaleX::ScaleX(float value) noexcept : ResolvedPrimitive({ value }) { }
 
-	// Perspective matrix
-	Matrix4f p = m;
+ScaleY::ScaleY(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
+ScaleY::ScaleY(float value) noexcept : ResolvedPrimitive({ value }) { }
 
-	for (int i = 0; i < 3; i++)
-		p[i][3] = 0;
-	p[3][3] = 1;
+ScaleZ::ScaleZ(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
+ScaleZ::ScaleZ(float value) noexcept : ResolvedPrimitive({ value }) { }
 
-	if (Math::AbsoluteValue(p.Determinant()) < eps)
-		return false;
+Scale2D::Scale2D(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
+Scale2D::Scale2D(float xy) noexcept : ResolvedPrimitive({ xy, xy }) { }
+Scale2D::Scale2D(float x, float y) noexcept : ResolvedPrimitive({ x, y }) { }
 
-	if (m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0)
-	{
-		auto rhs = m.GetColumn(3);
-		Matrix4f p_inv = p;
-		if (!p_inv.Invert())
-			return false;
-		auto& p_inv_trans = p.Transpose();
-		perspective = p_inv_trans * rhs;
-	}
-	else
-	{
-		perspective[0] = perspective[1] = perspective[2] = 0;
-		perspective[3] = 1;
-	}
+Scale3D::Scale3D(const NumericValue* values) noexcept : ResolvedPrimitive(values) { }
+Scale3D::Scale3D(float xyz) noexcept : ResolvedPrimitive({ xyz, xyz, xyz }) { }
+Scale3D::Scale3D(float x, float y, float z) noexcept : ResolvedPrimitive({ x, y, z }) { }
 
-	for (int i = 0; i < 3; i++)
-		translation[i] = m[3][i];
+RotateX::RotateX(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) { }
+RotateX::RotateX(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
 
-	Vector3f row[3];
-	for (int i = 0; i < 3; i++)
-	{
-		row[i][0] = m[i][0];
-		row[i][1] = m[i][1];
-		row[i][2] = m[i][2];
-	}
+RotateY::RotateY(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) {}
+RotateY::RotateY(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
 
-	scale[0] = row[0].Magnitude();
-	row[0] = row[0].Normalise();
+RotateZ::RotateZ(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) { }
+RotateZ::RotateZ(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
 
-	skew[0] = row[0].DotProduct(row[1]);
-	row[1] = Combine(row[1], row[0], 1, -skew[0]);
+Rotate2D::Rotate2D(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) { }
+Rotate2D::Rotate2D(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
 
-	scale[1] = row[1].Magnitude();
-	row[1] = row[1].Normalise();
-	skew[0] /= scale[1];
+Rotate3D::Rotate3D(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::NUMBER, Property::NUMBER, Property::NUMBER, Property::RAD }) { }
+Rotate3D::Rotate3D(float x, float y, float z, float angle, Property::Unit angle_unit) noexcept
+	: ResolvedPrimitive(
+		{ NumericValue{x, Property::NUMBER}, NumericValue{y, Property::NUMBER}, NumericValue{z, Property::NUMBER}, NumericValue{angle, angle_unit} },
+		{ Property::NUMBER, Property::NUMBER, Property::NUMBER, Property::RAD }
+	)
+{ }
 
-	skew[1] = row[0].DotProduct(row[2]);
-	row[2] = Combine(row[2], row[0], 1, -skew[1]);
-	skew[2] = row[1].DotProduct(row[2]);
-	row[2] = Combine(row[2], row[1], 1, -skew[2]);
+SkewX::SkewX(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) { }
+SkewX::SkewX(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
 
-	scale[2] = row[2].Magnitude();
-	row[2] = row[2].Normalise();
-	skew[1] /= scale[2];
-	skew[2] /= scale[2];
-
-	// Check if we need to flip coordinate system
-	auto pdum3 = row[1].CrossProduct(row[2]);
-	if (row[0].DotProduct(pdum3) < 0.0f)
-	{
-		for (int i = 0; i < 3; i++)
-		{
-			scale[i] *= -1.f;
-			row[i] *= -1.f;
-		}
-	}
+SkewY::SkewY(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD }) { }
+SkewY::SkewY(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({ NumericValue{ angle, unit } }, { Property::RAD }) { }
 
-	quaternion[0] = 0.5f * Math::SquareRoot(Math::Max(1.f + row[0][0] - row[1][1] - row[2][2], 0.0f));
-	quaternion[1] = 0.5f * Math::SquareRoot(Math::Max(1.f - row[0][0] + row[1][1] - row[2][2], 0.0f));
-	quaternion[2] = 0.5f * Math::SquareRoot(Math::Max(1.f - row[0][0] - row[1][1] + row[2][2], 0.0f));
-	quaternion[3] = 0.5f * Math::SquareRoot(Math::Max(1.f + row[0][0] + row[1][1] + row[2][2], 0.0f));
+Skew2D::Skew2D(const NumericValue* values) noexcept : ResolvedPrimitive(values, { Property::RAD, Property::RAD }) { }
+Skew2D::Skew2D(float x, float y, Property::Unit unit) noexcept 
+	: ResolvedPrimitive({ NumericValue{ x, unit }, { NumericValue{ y, unit }} }, { Property::RAD, Property::RAD }) { }
 
-	if (row[2][1] > row[1][2])
-		quaternion[0] *= -1.f;
-	if (row[0][2] > row[2][0])
-		quaternion[1] *= -1.f;
-	if (row[1][0] > row[0][1])
-		quaternion[2] *= -1.f;
+Perspective::Perspective(const NumericValue* values) noexcept : UnresolvedPrimitive(values) { }
 
-	return true;
-}
 
 }
-} // namespace Rml
+} // namespace Rml

+ 1 - 1
Source/Core/TransformState.cpp

@@ -26,7 +26,7 @@
  *
  */
 
-#include "../../Include/RmlUi/Core/TransformState.h"
+#include "TransformState.h"
 
 namespace Rml {
 

+ 3 - 3
Include/RmlUi/Core/TransformState.h → Source/Core/TransformState.h

@@ -29,12 +29,12 @@
 #ifndef RMLUI_CORE_TRANSFORMSTATE_H
 #define RMLUI_CORE_TRANSFORMSTATE_H
 
-#include "Header.h"
-#include "Types.h"
+#include "../../Include/RmlUi/Core/Header.h"
+#include "../../Include/RmlUi/Core/Types.h"
 
 namespace Rml {
 
-class RMLUICORE_API TransformState
+class TransformState
 {
 public:
 

+ 924 - 0
Source/Core/TransformUtilities.cpp

@@ -0,0 +1,924 @@
+/*
+ * 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 "TransformUtilities.h"
+#include "../../Include/RmlUi/Core/Element.h"
+#include "../../Include/RmlUi/Core/TransformPrimitive.h"
+
+namespace Rml {
+
+using namespace Transforms;
+
+static Vector3f Combine(const Vector3f& a, const Vector3f& b, float a_scale, float b_scale)
+{
+	Vector3f result;
+	result.x = a_scale * a.x + b_scale * b.x;
+	result.y = a_scale * a.y + b_scale * b.y;
+	result.z = a_scale * a.z + b_scale * b.z;
+	return result;
+}
+
+
+// Interpolate two quaternions a, b with weight alpha [0, 1]
+static Vector4f QuaternionSlerp(const Vector4f& a, const Vector4f& b, float alpha)
+{
+	using namespace Math;
+
+	const float eps = 0.9995f;
+
+	float dot = a.DotProduct(b);
+	dot = Clamp(dot, -1.f, 1.f);
+
+	if (dot > eps)
+		return a;
+
+	float theta = ACos(dot);
+	float w = Sin(alpha * theta) / SquareRoot(1.f - dot * dot);
+	float a_scale = Cos(alpha * theta) - dot * w;
+
+	Vector4f result;
+	for (int i = 0; i < 4; i++)
+	{
+		result[i] = a[i] * a_scale + b[i] * w;
+	}
+
+	return result;
+}
+
+/// Resolve a numeric property value for an element.
+static inline float ResolveLengthPercentage(NumericValue value, Element& e, float base) noexcept
+{
+	Property prop;
+	prop.value = Variant(value.number);
+	prop.unit = value.unit;
+	return e.ResolveNumericProperty(&prop, base);
+}
+
+/// Resolve a numeric property value with the element's width as relative base value.
+static inline float ResolveWidth(NumericValue value, Element& e) noexcept
+{
+	if (value.unit & (Property::PX | Property::NUMBER)) return value.number;
+	return ResolveLengthPercentage(value, e, e.GetBox().GetSize(Box::BORDER).x);
+}
+
+/// Resolve a numeric property value with the element's height as relative base value.
+static inline float ResolveHeight(NumericValue value, Element& e) noexcept
+{
+	if (value.unit & (Property::PX | Property::NUMBER)) return value.number;
+	return ResolveLengthPercentage(value, e, e.GetBox().GetSize(Box::BORDER).y);
+}
+
+/// Resolve a numeric property value with the element's depth as relative base value.
+static inline float ResolveDepth(NumericValue value, Element& e) noexcept
+{
+	if (value.unit & (Property::PX | Property::NUMBER)) return value.number;
+	Vector2f size = e.GetBox().GetSize(Box::BORDER);
+	return ResolveLengthPercentage(value, e, Math::Max(size.x, size.y));
+}
+
+static inline String ToString(NumericValue value) noexcept
+{
+	Property prop;
+	prop.value = Variant(value.number);
+	prop.unit = value.unit;
+	return prop.ToString();
+}
+
+struct SetIdentityVisitor
+{
+	template <size_t N>
+	void operator()(Transforms::ResolvedPrimitive<N>& p)
+	{
+		for (auto& value : p.values)
+			value = 0.0f;
+	}
+	template <size_t N>
+	void operator()(Transforms::UnresolvedPrimitive<N>& p)
+	{
+		for (auto& value : p.values)
+			value.number = 0.0f;
+	}
+	void operator()(Transforms::Matrix2D& p)
+	{
+		for (int i = 0; i < 6; i++)
+			p.values[i] = ((i == 0 || i == 3) ? 1.0f : 0.0f);
+	}
+	void operator()(Transforms::Matrix3D& p)
+	{
+		for (int i = 0; i < 16; i++)
+			p.values[i] = ((i % 5) == 0 ? 1.0f : 0.0f);
+	}
+	void operator()(Transforms::ScaleX& p)
+	{
+		p.values[0] = 1;
+	}
+	void operator()(Transforms::ScaleY& p)
+	{
+		p.values[0] = 1;
+	}
+	void operator()(Transforms::ScaleZ& p)
+	{
+		p.values[0] = 1;
+	}
+	void operator()(Transforms::Scale2D& p)
+	{
+		p.values[0] = p.values[1] = 1;
+	}
+	void operator()(Transforms::Scale3D& p)
+	{
+		p.values[0] = p.values[1] = p.values[2] = 1;
+	}
+	void operator()(Transforms::DecomposedMatrix4& p)
+	{
+		p.perspective = Vector4f(0, 0, 0, 1);
+		p.quaternion = Vector4f(0, 0, 0, 1);
+		p.translation = Vector3f(0, 0, 0);
+		p.scale = Vector3f(1, 1, 1);
+		p.skew = Vector3f(0, 0, 0);
+	}
+
+
+	void run(TransformPrimitive& primitive)
+	{
+		switch (primitive.type)
+		{
+		case TransformPrimitive::MATRIX2D: this->operator()(primitive.matrix_2d); break;
+		case TransformPrimitive::MATRIX3D: this->operator()(primitive.matrix_3d); break;
+		case TransformPrimitive::TRANSLATEX: this->operator()(primitive.translate_x); break;
+		case TransformPrimitive::TRANSLATEY: this->operator()(primitive.translate_y); break;
+		case TransformPrimitive::TRANSLATEZ: this->operator()(primitive.translate_z); break;
+		case TransformPrimitive::TRANSLATE2D: this->operator()(primitive.translate_2d); break;
+		case TransformPrimitive::TRANSLATE3D: this->operator()(primitive.translate_3d); break;
+		case TransformPrimitive::SCALEX: this->operator()(primitive.scale_x); break;
+		case TransformPrimitive::SCALEY: this->operator()(primitive.scale_y); break;
+		case TransformPrimitive::SCALEZ: this->operator()(primitive.scale_z); break;
+		case TransformPrimitive::SCALE2D: this->operator()(primitive.scale_2d); break;
+		case TransformPrimitive::SCALE3D: this->operator()(primitive.scale_3d); break;
+		case TransformPrimitive::ROTATEX: this->operator()(primitive.rotate_x); break;
+		case TransformPrimitive::ROTATEY: this->operator()(primitive.rotate_y); break;
+		case TransformPrimitive::ROTATEZ: this->operator()(primitive.rotate_z); break;
+		case TransformPrimitive::ROTATE2D: this->operator()(primitive.rotate_2d); break;
+		case TransformPrimitive::ROTATE3D: this->operator()(primitive.rotate_3d); break;
+		case TransformPrimitive::SKEWX: this->operator()(primitive.skew_x); break;
+		case TransformPrimitive::SKEWY: this->operator()(primitive.skew_y); break;
+		case TransformPrimitive::SKEW2D: this->operator()(primitive.skew_2d); break;
+		case TransformPrimitive::PERSPECTIVE: this->operator()(primitive.perspective); break;
+		case TransformPrimitive::DECOMPOSEDMATRIX4: this->operator()(primitive.decomposed_matrix_4); break;
+		default:
+			RMLUI_ASSERT(false);
+			break;
+		}
+	}
+};
+
+void TransformUtilities::SetIdentity(TransformPrimitive& p) noexcept
+{
+	SetIdentityVisitor{}.run(p);
+}
+
+
+struct ResolveTransformVisitor
+{
+	Matrix4f& m;
+	Element& e;
+
+	void operator()(const Transforms::Matrix2D& p)
+	{
+		m = Matrix4f::FromRows(
+			Vector4f(p.values[0], p.values[2], 0, p.values[4]),
+			Vector4f(p.values[1], p.values[3], 0, p.values[5]),
+			Vector4f(0, 0, 1, 0),
+			Vector4f(0, 0, 0, 1)
+		);
+	}
+
+	void operator()(const Transforms::Matrix3D& p)
+	{
+		m = Matrix4f::FromColumns(
+			Vector4f(p.values[0], p.values[1], p.values[2], p.values[3]),
+			Vector4f(p.values[4], p.values[5], p.values[6], p.values[7]),
+			Vector4f(p.values[8], p.values[9], p.values[10], p.values[11]),
+			Vector4f(p.values[12], p.values[13], p.values[14], p.values[15])
+		);
+	}
+
+	void operator()(const Transforms::TranslateX& p)
+	{
+		m = Matrix4f::TranslateX(ResolveWidth(p.values[0], e));
+	}
+
+	void operator()(const Transforms::TranslateY& p)
+	{
+		m = Matrix4f::TranslateY(ResolveHeight(p.values[0], e));
+	}
+
+	void operator()(const Transforms::TranslateZ& p)
+	{
+		m = Matrix4f::TranslateZ(ResolveDepth(p.values[0], e));
+	}
+
+	void operator()(const Transforms::Translate2D& p)
+	{
+		m = Matrix4f::Translate(
+			ResolveWidth(p.values[0], e),
+			ResolveHeight(p.values[1], e),
+			0
+		);
+	}
+
+	void operator()(const Transforms::Translate3D& p)
+	{
+		m = Matrix4f::Translate(
+			ResolveWidth(p.values[0], e),
+			ResolveHeight(p.values[1], e),
+			ResolveDepth(p.values[2], e)
+		);
+	}
+
+	void operator()(const Transforms::ScaleX& p)
+	{
+		m = Matrix4f::ScaleX(p.values[0]);
+	}
+
+	void operator()(const Transforms::ScaleY& p)
+	{
+		m = Matrix4f::ScaleY(p.values[0]);
+	}
+
+	void operator()(const Transforms::ScaleZ& p)
+	{
+		m = Matrix4f::ScaleZ(p.values[0]);
+	}
+
+	void operator()(const Transforms::Scale2D& p)
+	{
+		m = Matrix4f::Scale(p.values[0], p.values[1], 1);
+	}
+
+	void operator()(const Transforms::Scale3D& p)
+	{
+		m = Matrix4f::Scale(p.values[0], p.values[1], p.values[2]);
+	}
+
+	void operator()(const Transforms::RotateX& p)
+	{
+		m = Matrix4f::RotateX(p.values[0]);
+	}
+
+	void operator()(const Transforms::RotateY& p)
+	{
+		m = Matrix4f::RotateY(p.values[0]);
+	}
+
+	void operator()(const Transforms::RotateZ& p)
+	{
+		m = Matrix4f::RotateZ(p.values[0]);
+	}
+
+	void operator()(const Transforms::Rotate2D& p)
+	{
+		m = Matrix4f::RotateZ(p.values[0]);
+	}
+
+	void operator()(const Transforms::Rotate3D& p)
+	{
+		m = Matrix4f::Rotate(Vector3f(p.values[0], p.values[1], p.values[2]), p.values[3]);
+	}
+
+	void operator()(const Transforms::SkewX& p)
+	{
+		m = Matrix4f::SkewX(p.values[0]);
+	}
+
+	void operator()(const Transforms::SkewY& p)
+	{
+		m = Matrix4f::SkewY(p.values[0]);
+	}
+
+	void operator()(const Transforms::Skew2D& p)
+	{
+		m = Matrix4f::Skew(p.values[0], p.values[1]);
+	}
+
+	void operator()(const Transforms::DecomposedMatrix4& p)
+	{
+		m = Matrix4f::Compose(p.translation, p.scale, p.skew, p.perspective, p.quaternion);
+	}
+	void operator()(const Transforms::Perspective& p)
+	{
+		m = Matrix4f::Perspective(ResolveDepth(p.values[0], e));
+	}
+
+
+	void run(const TransformPrimitive& primitive)
+	{
+		switch (primitive.type)
+		{
+		case TransformPrimitive::MATRIX2D: this->operator()(primitive.matrix_2d); break;
+		case TransformPrimitive::MATRIX3D: this->operator()(primitive.matrix_3d); break;
+		case TransformPrimitive::TRANSLATEX: this->operator()(primitive.translate_x); break;
+		case TransformPrimitive::TRANSLATEY: this->operator()(primitive.translate_y); break;
+		case TransformPrimitive::TRANSLATEZ: this->operator()(primitive.translate_z); break;
+		case TransformPrimitive::TRANSLATE2D: this->operator()(primitive.translate_2d); break;
+		case TransformPrimitive::TRANSLATE3D: this->operator()(primitive.translate_3d); break;
+		case TransformPrimitive::SCALEX: this->operator()(primitive.scale_x); break;
+		case TransformPrimitive::SCALEY: this->operator()(primitive.scale_y); break;
+		case TransformPrimitive::SCALEZ: this->operator()(primitive.scale_z); break;
+		case TransformPrimitive::SCALE2D: this->operator()(primitive.scale_2d); break;
+		case TransformPrimitive::SCALE3D: this->operator()(primitive.scale_3d); break;
+		case TransformPrimitive::ROTATEX: this->operator()(primitive.rotate_x); break;
+		case TransformPrimitive::ROTATEY: this->operator()(primitive.rotate_y); break;
+		case TransformPrimitive::ROTATEZ: this->operator()(primitive.rotate_z); break;
+		case TransformPrimitive::ROTATE2D: this->operator()(primitive.rotate_2d); break;
+		case TransformPrimitive::ROTATE3D: this->operator()(primitive.rotate_3d); break;
+		case TransformPrimitive::SKEWX: this->operator()(primitive.skew_x); break;
+		case TransformPrimitive::SKEWY: this->operator()(primitive.skew_y); break;
+		case TransformPrimitive::SKEW2D: this->operator()(primitive.skew_2d); break;
+		case TransformPrimitive::PERSPECTIVE: this->operator()(primitive.perspective); break;
+		case TransformPrimitive::DECOMPOSEDMATRIX4: this->operator()(primitive.decomposed_matrix_4); break;
+		}
+	}
+};
+
+Matrix4f TransformUtilities::ResolveTransform(const TransformPrimitive& p, Element& e) noexcept
+{
+	Matrix4f m;
+	ResolveTransformVisitor visitor{ m, e };
+	visitor.run(p);
+	return m;
+}
+
+
+
+
+struct PrepareVisitor
+{
+	Element& e;
+
+	bool operator()(TranslateX& p)
+	{
+		p.values[0] = NumericValue{ ResolveWidth(p.values[0], e), Property::PX };
+		return true;
+	}
+	bool operator()(TranslateY& p)
+	{
+		p.values[0] = NumericValue{ ResolveHeight(p.values[0], e), Property::PX };
+		return true;
+	}
+	bool operator()(TranslateZ& p)
+	{
+		p.values[0] = NumericValue{ ResolveDepth(p.values[0], e), Property::PX };
+		return true;
+	}
+	bool operator()(Translate2D& p)
+	{
+		p.values[0] = NumericValue{ ResolveWidth(p.values[0], e), Property::PX };
+		p.values[1] = NumericValue{ ResolveHeight(p.values[1], e), Property::PX };
+		return true;
+	}
+	bool operator()(Translate3D& p)
+	{
+		p.values[0] = NumericValue{ ResolveWidth(p.values[0], e), Property::PX };
+		p.values[1] = NumericValue{ ResolveHeight(p.values[1], e), Property::PX };
+		p.values[2] = NumericValue{ ResolveDepth(p.values[2], e), Property::PX };
+		return true;
+	}
+	template <size_t N>
+	bool operator()(ResolvedPrimitive<N>& /*p*/)
+	{
+		// No conversion needed for resolved transforms (with some exceptions below)
+		return true;
+	}
+	bool operator()(DecomposedMatrix4& /*p*/)
+	{
+		return true;
+	}
+	bool operator()(Rotate3D& p)
+	{
+		// Rotate3D can be interpolated if and only if their rotation axes point in the same direction.
+		// We normalize the rotation vector here for easy comparison, and return true here. Later on we make the
+		// pair-wise check in 'TryConvertToMatchingGenericType' to see if we need to decompose.
+		Vector3f vec = Vector3f(p.values[0], p.values[1], p.values[2]).Normalise();
+		p.values[0] = vec.x;
+		p.values[1] = vec.y;
+		p.values[2] = vec.z;
+		return true;
+	}
+	bool operator()(Matrix3D& /*p*/)
+	{
+		// Matrices must be decomposed for interpolation
+		return false;
+	}
+	bool operator()(Matrix2D& /*p*/)
+	{
+		// Matrix2D can also be optimized for interpolation, but for now we decompose it to a full DecomposedMatrix4
+		return false;
+	}
+	bool operator()(Perspective& /*p*/)
+	{
+		// Perspective must be decomposed
+		return false;
+	}
+
+	bool run(TransformPrimitive& primitive)
+	{
+		switch (primitive.type)
+		{
+		case TransformPrimitive::MATRIX2D: return this->operator()(primitive.matrix_2d);
+		case TransformPrimitive::MATRIX3D: return this->operator()(primitive.matrix_3d);
+		case TransformPrimitive::TRANSLATEX: return this->operator()(primitive.translate_x);
+		case TransformPrimitive::TRANSLATEY: return this->operator()(primitive.translate_y);
+		case TransformPrimitive::TRANSLATEZ: return this->operator()(primitive.translate_z);
+		case TransformPrimitive::TRANSLATE2D: return this->operator()(primitive.translate_2d);
+		case TransformPrimitive::TRANSLATE3D: return this->operator()(primitive.translate_3d);
+		case TransformPrimitive::SCALEX: return this->operator()(primitive.scale_x);
+		case TransformPrimitive::SCALEY: return this->operator()(primitive.scale_y);
+		case TransformPrimitive::SCALEZ: return this->operator()(primitive.scale_z);
+		case TransformPrimitive::SCALE2D: return this->operator()(primitive.scale_2d);
+		case TransformPrimitive::SCALE3D: return this->operator()(primitive.scale_3d);
+		case TransformPrimitive::ROTATEX: return this->operator()(primitive.rotate_x);
+		case TransformPrimitive::ROTATEY: return this->operator()(primitive.rotate_y);
+		case TransformPrimitive::ROTATEZ: return this->operator()(primitive.rotate_z);
+		case TransformPrimitive::ROTATE2D: return this->operator()(primitive.rotate_2d);
+		case TransformPrimitive::ROTATE3D: return this->operator()(primitive.rotate_3d);
+		case TransformPrimitive::SKEWX: return this->operator()(primitive.skew_x);
+		case TransformPrimitive::SKEWY: return this->operator()(primitive.skew_y);
+		case TransformPrimitive::SKEW2D: return this->operator()(primitive.skew_2d);
+		case TransformPrimitive::PERSPECTIVE: return this->operator()(primitive.perspective);
+		case TransformPrimitive::DECOMPOSEDMATRIX4: return this->operator()(primitive.decomposed_matrix_4);
+		default:
+			break;
+		}
+		RMLUI_ASSERT(false);
+		return false;
+	}
+};
+
+bool TransformUtilities::PrepareForInterpolation(TransformPrimitive& p, Element& e) noexcept
+{
+	return PrepareVisitor{ e }.run(p);
+}
+
+
+
+
+enum class GenericType { None, Scale3D, Translate3D, Rotate3D };
+
+struct GetGenericTypeVisitor
+{
+	GenericType run(const TransformPrimitive& primitive)
+	{
+		switch (primitive.type)
+		{
+		case TransformPrimitive::TRANSLATEX:  return GenericType::Translate3D;
+		case TransformPrimitive::TRANSLATEY:  return GenericType::Translate3D;
+		case TransformPrimitive::TRANSLATEZ:  return GenericType::Translate3D;
+		case TransformPrimitive::TRANSLATE2D: return GenericType::Translate3D;
+		case TransformPrimitive::TRANSLATE3D: return GenericType::Translate3D;
+		case TransformPrimitive::SCALEX:      return GenericType::Scale3D;
+		case TransformPrimitive::SCALEY:      return GenericType::Scale3D;
+		case TransformPrimitive::SCALEZ:      return GenericType::Scale3D;
+		case TransformPrimitive::SCALE2D:     return GenericType::Scale3D;
+		case TransformPrimitive::SCALE3D:     return GenericType::Scale3D;
+		case TransformPrimitive::ROTATEX:     return GenericType::Rotate3D;
+		case TransformPrimitive::ROTATEY:     return GenericType::Rotate3D;
+		case TransformPrimitive::ROTATEZ:     return GenericType::Rotate3D;
+		case TransformPrimitive::ROTATE2D:    return GenericType::Rotate3D;
+		case TransformPrimitive::ROTATE3D:    return GenericType::Rotate3D;
+		default:
+			break;
+		}
+		return GenericType::None;
+	}
+};
+
+
+struct ConvertToGenericTypeVisitor
+{
+	Translate3D operator()(const TranslateX& p) { return Translate3D{ p.values[0], {0.0f, Property::PX}, {0.0f, Property::PX} }; }
+	Translate3D operator()(const TranslateY& p) { return Translate3D{ {0.0f, Property::PX}, p.values[0], {0.0f, Property::PX} }; }
+	Translate3D operator()(const TranslateZ& p) { return Translate3D{ {0.0f, Property::PX}, {0.0f, Property::PX}, p.values[0] }; }
+	Translate3D operator()(const Translate2D& p) { return Translate3D{ p.values[0], p.values[1], {0.0f, Property::PX} }; }
+	Scale3D operator()(const ScaleX& p) { return Scale3D{ p.values[0], 1.0f, 1.0f }; }
+	Scale3D operator()(const ScaleY& p) { return Scale3D{ 1.0f, p.values[0], 1.0f }; }
+	Scale3D operator()(const ScaleZ& p) { return Scale3D{ 1.0f, 1.0f, p.values[0] }; }
+	Scale3D operator()(const Scale2D& p) { return Scale3D{ p.values[0], p.values[1], 1.0f }; }
+	Rotate3D operator()(const RotateX& p) { return Rotate3D{ 1, 0, 0, p.values[0], Property::RAD }; }
+	Rotate3D operator()(const RotateY& p) { return Rotate3D{ 0, 1, 0, p.values[0], Property::RAD }; }
+	Rotate3D operator()(const RotateZ& p) { return Rotate3D{ 0, 0, 1, p.values[0], Property::RAD }; }
+	Rotate3D operator()(const Rotate2D& p) { return Rotate3D{ 0, 0, 1, p.values[0], Property::RAD }; }
+
+	template <typename T>
+	TransformPrimitive operator()(const T& p) { RMLUI_ERROR; return p; }
+
+	TransformPrimitive run(const TransformPrimitive& primitive)
+	{
+		TransformPrimitive result = primitive;
+		switch (primitive.type)
+		{
+		case TransformPrimitive::TRANSLATEX:  result.type = TransformPrimitive::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_x);  break;
+		case TransformPrimitive::TRANSLATEY:  result.type = TransformPrimitive::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_y);  break;
+		case TransformPrimitive::TRANSLATEZ:  result.type = TransformPrimitive::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_z);  break;
+		case TransformPrimitive::TRANSLATE2D: result.type = TransformPrimitive::TRANSLATE3D; result.translate_3d = this->operator()(primitive.translate_2d); break;
+		case TransformPrimitive::TRANSLATE3D: break;
+		case TransformPrimitive::SCALEX:      result.type = TransformPrimitive::SCALE3D;     result.scale_3d = this->operator()(primitive.scale_x);      break;
+		case TransformPrimitive::SCALEY:      result.type = TransformPrimitive::SCALE3D;     result.scale_3d = this->operator()(primitive.scale_y);      break;
+		case TransformPrimitive::SCALEZ:      result.type = TransformPrimitive::SCALE3D;     result.scale_3d = this->operator()(primitive.scale_z);      break;
+		case TransformPrimitive::SCALE2D:     result.type = TransformPrimitive::SCALE3D;     result.scale_3d = this->operator()(primitive.scale_2d);     break;
+		case TransformPrimitive::SCALE3D:     break;
+		case TransformPrimitive::ROTATEX:     result.type = TransformPrimitive::ROTATE3D;    result.rotate_3d = this->operator()(primitive.rotate_x);     break;
+		case TransformPrimitive::ROTATEY:     result.type = TransformPrimitive::ROTATE3D;    result.rotate_3d = this->operator()(primitive.rotate_y);     break;
+		case TransformPrimitive::ROTATEZ:     result.type = TransformPrimitive::ROTATE3D;    result.rotate_3d = this->operator()(primitive.rotate_z);     break;
+		case TransformPrimitive::ROTATE2D:    result.type = TransformPrimitive::ROTATE3D;    result.rotate_3d = this->operator()(primitive.rotate_2d);    break;
+		case TransformPrimitive::ROTATE3D:    break;
+		default:
+			RMLUI_ASSERT(false);
+			break;
+		}
+		return result;
+	}
+};
+
+static bool CanInterpolateRotate3D(const Rotate3D& p0, const Rotate3D& p1)
+{
+	// Rotate3D can only be interpolated if and only if their rotation axes point in the same direction.
+	// Assumes each rotation axis has already been normalized.
+	auto& v0 = p0.values;
+	auto& v1 = p1.values;
+	return v0[0] == v1[0] && v0[1] == v1[1] && v0[2] == v1[2];
+}
+
+
+bool TransformUtilities::TryConvertToMatchingGenericType(TransformPrimitive& p0, TransformPrimitive& p1) noexcept
+{
+	if (p0.type == p1.type)
+	{
+		if (p0.type == TransformPrimitive::ROTATE3D && !CanInterpolateRotate3D(p0.rotate_3d, p1.rotate_3d))
+			return false;
+
+		return true;
+	}
+
+	GenericType c0 = GetGenericTypeVisitor{}.run(p0);
+	GenericType c1 = GetGenericTypeVisitor{}.run(p1);
+
+	if (c0 == c1 && c0 != GenericType::None)
+	{
+		TransformPrimitive new_p0 = ConvertToGenericTypeVisitor{}.run(p0);
+		TransformPrimitive new_p1 = ConvertToGenericTypeVisitor{}.run(p1);
+
+		RMLUI_ASSERT(new_p0.type == new_p1.type);
+
+		if (new_p0.type == TransformPrimitive::ROTATE3D && !CanInterpolateRotate3D(new_p0.rotate_3d, new_p1.rotate_3d))
+			return false;
+
+		p0 = new_p0;
+		p1 = new_p1;
+
+		return true;
+	}
+
+	return false;
+}
+
+
+
+
+
+struct InterpolateVisitor
+{
+	const TransformPrimitive& other_variant;
+	float alpha;
+
+	template <size_t N>
+	bool Interpolate(ResolvedPrimitive<N>& p0, const ResolvedPrimitive<N>& p1)
+	{
+		for (size_t i = 0; i < N; i++)
+			p0.values[i] = p0.values[i] * (1.0f - alpha) + p1.values[i] * alpha;
+		return true;
+	}
+	template <size_t N>
+	bool Interpolate(UnresolvedPrimitive<N>& p0, const UnresolvedPrimitive<N>& p1)
+	{
+		// Assumes that the underlying units have been resolved (e.g. to pixels)
+		for (size_t i = 0; i < N; i++)
+			p0.values[i].number = p0.values[i].number * (1.0f - alpha) + p1.values[i].number * alpha;
+		return true;
+	}
+	bool Interpolate(Rotate3D& p0, const Rotate3D& p1)
+	{
+		RMLUI_ASSERT(CanInterpolateRotate3D(p0, p1));
+		// We can only interpolate rotate3d if their rotation axes align. That should be the case if we get here, 
+		// otherwise the generic type matching should decompose them. Thus, we only need to interpolate
+		// the angle value here.
+		p0.values[3] = p0.values[3] * (1.0f - alpha) + p1.values[3] * alpha;
+		return true;
+	}
+	bool Interpolate(Matrix2D& /*p0*/, const Matrix2D& /*p1*/) { RMLUI_ERROR; return false; /* Error if we get here, see PrepareForInterpolation() */ }
+	bool Interpolate(Matrix3D& /*p0*/, const Matrix3D& /*p1*/) { RMLUI_ERROR; return false; /* Error if we get here, see PrepareForInterpolation() */ }
+	bool Interpolate(Perspective& /*p0*/, const Perspective& /*p1*/) { RMLUI_ERROR; return false; /* Error if we get here, see PrepareForInterpolation() */ }
+
+	bool Interpolate(DecomposedMatrix4& p0, const DecomposedMatrix4& p1)
+	{
+		p0.perspective = p0.perspective * (1.0f - alpha) + p1.perspective * alpha;
+		p0.quaternion = QuaternionSlerp(p0.quaternion, p1.quaternion, alpha);
+		p0.translation = p0.translation * (1.0f - alpha) + p1.translation * alpha;
+		p0.scale = p0.scale * (1.0f - alpha) + p1.scale * alpha;
+		p0.skew = p0.skew * (1.0f - alpha) + p1.skew * alpha;
+		return true;
+	}
+
+	bool run(TransformPrimitive& variant)
+	{
+		RMLUI_ASSERT(variant.type == other_variant.type);
+		switch (variant.type)
+		{
+		case TransformPrimitive::MATRIX2D: return Interpolate(variant.matrix_2d, other_variant.matrix_2d);
+		case TransformPrimitive::MATRIX3D: return Interpolate(variant.matrix_3d, other_variant.matrix_3d);
+		case TransformPrimitive::TRANSLATEX: return Interpolate(variant.translate_x, other_variant.translate_x);
+		case TransformPrimitive::TRANSLATEY: return Interpolate(variant.translate_y, other_variant.translate_y);
+		case TransformPrimitive::TRANSLATEZ: return Interpolate(variant.translate_z, other_variant.translate_z);
+		case TransformPrimitive::TRANSLATE2D: return Interpolate(variant.translate_2d, other_variant.translate_2d);
+		case TransformPrimitive::TRANSLATE3D: return Interpolate(variant.translate_3d, other_variant.translate_3d);
+		case TransformPrimitive::SCALEX: return Interpolate(variant.scale_x, other_variant.scale_x);
+		case TransformPrimitive::SCALEY: return Interpolate(variant.scale_y, other_variant.scale_y);
+		case TransformPrimitive::SCALEZ: return Interpolate(variant.scale_z, other_variant.scale_z);
+		case TransformPrimitive::SCALE2D: return Interpolate(variant.scale_2d, other_variant.scale_2d);
+		case TransformPrimitive::SCALE3D: return Interpolate(variant.scale_3d, other_variant.scale_3d);
+		case TransformPrimitive::ROTATEX: return Interpolate(variant.rotate_x, other_variant.rotate_x);
+		case TransformPrimitive::ROTATEY: return Interpolate(variant.rotate_y, other_variant.rotate_y);
+		case TransformPrimitive::ROTATEZ: return Interpolate(variant.rotate_z, other_variant.rotate_z);
+		case TransformPrimitive::ROTATE2D: return Interpolate(variant.rotate_2d, other_variant.rotate_2d);
+		case TransformPrimitive::ROTATE3D: return Interpolate(variant.rotate_3d, other_variant.rotate_3d);
+		case TransformPrimitive::SKEWX: return Interpolate(variant.skew_x, other_variant.skew_x);
+		case TransformPrimitive::SKEWY: return Interpolate(variant.skew_y, other_variant.skew_y);
+		case TransformPrimitive::SKEW2D: return Interpolate(variant.skew_2d, other_variant.skew_2d);
+		case TransformPrimitive::PERSPECTIVE: return Interpolate(variant.perspective, other_variant.perspective);
+		case TransformPrimitive::DECOMPOSEDMATRIX4: return Interpolate(variant.decomposed_matrix_4, other_variant.decomposed_matrix_4);
+		}
+		RMLUI_ASSERT(false);
+		return false;
+	}
+};
+
+bool TransformUtilities::InterpolateWith(TransformPrimitive& target, const TransformPrimitive& other, float alpha) noexcept
+{
+	if (target.type != other.type)
+		return false;
+
+	bool result = InterpolateVisitor{ other, alpha }.run(target);
+	return result;
+}
+
+
+template<size_t N>
+static inline String ToString(const Transforms::ResolvedPrimitive<N>& p, String unit, bool rad_to_deg = false, bool only_unit_on_last_value = false) noexcept {
+	float multiplier = 1.0f;
+	String tmp;
+	String result = "(";
+	for (size_t i = 0; i < N; i++)
+	{
+		if (only_unit_on_last_value && i < N - 1)
+			multiplier = 1.0f;
+		else if (rad_to_deg)
+			multiplier = 180.f / Math::RMLUI_PI;
+
+		if (TypeConverter<float, String>::Convert(p.values[i] * multiplier, tmp))
+			result += tmp;
+
+		if (!unit.empty() && (!only_unit_on_last_value || (i == N - 1)))
+			result += unit;
+
+		if (i < N - 1)
+			result += ", ";
+	}
+	result += ")";
+	return result;
+}
+
+template<size_t N>
+static inline String ToString(const Transforms::UnresolvedPrimitive<N>& p) noexcept {
+	String result = "(";
+	for (size_t i = 0; i < N; i++)
+	{
+		result += ToString(p.values[i]);
+		if (i != N - 1)
+			result += ", ";
+	}
+	result += ")";
+	return result;
+}
+
+static inline String ToString(const Transforms::DecomposedMatrix4& p) noexcept {
+	static const Transforms::DecomposedMatrix4 d{
+		Vector4f(0, 0, 0, 1),
+		Vector4f(0, 0, 0, 1),
+		Vector3f(0, 0, 0),
+		Vector3f(1, 1, 1),
+		Vector3f(0, 0, 0)
+	};
+	String tmp;
+	String result;
+
+	if (p.perspective != d.perspective && TypeConverter< Vector4f, String >::Convert(p.perspective, tmp))
+		result += "perspective(" + tmp + "), ";
+	if (p.quaternion != d.quaternion && TypeConverter< Vector4f, String >::Convert(p.quaternion, tmp))
+		result += "quaternion(" + tmp + "), ";
+	if (p.translation != d.translation && TypeConverter< Vector3f, String >::Convert(p.translation, tmp))
+		result += "translation(" + tmp + "), ";
+	if (p.scale != d.scale && TypeConverter< Vector3f, String >::Convert(p.scale, tmp))
+		result += "scale(" + tmp + "), ";
+	if (p.skew != d.skew && TypeConverter< Vector3f, String >::Convert(p.skew, tmp))
+		result += "skew(" + tmp + "), ";
+
+	if (result.size() > 2)
+		result.resize(result.size() - 2);
+
+	result = "decomposedMatrix3d{ " + result + " }";
+
+	return result;
+}
+
+static inline String ToString(const Transforms::Matrix2D& p) noexcept { return "matrix" + ToString(static_cast<const Transforms::ResolvedPrimitive< 6 >&>(p), ""); }
+static inline String ToString(const Transforms::Matrix3D& p) noexcept { return "matrix3d" + ToString(static_cast<const Transforms::ResolvedPrimitive< 16 >&>(p), ""); }
+static inline String ToString(const Transforms::TranslateX& p) noexcept { return "translateX" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 1 >&>(p)); }
+static inline String ToString(const Transforms::TranslateY& p) noexcept { return "translateY" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 1 >&>(p)); }
+static inline String ToString(const Transforms::TranslateZ& p) noexcept { return "translateZ" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 1 >&>(p)); }
+static inline String ToString(const Transforms::Translate2D& p) noexcept { return "translate" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 2 >&>(p)); }
+static inline String ToString(const Transforms::Translate3D& p) noexcept { return "translate3d" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 3 >&>(p)); }
+static inline String ToString(const Transforms::ScaleX& p) noexcept { return "scaleX" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), ""); }
+static inline String ToString(const Transforms::ScaleY& p) noexcept { return "scaleY" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), ""); }
+static inline String ToString(const Transforms::ScaleZ& p) noexcept { return "scaleZ" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), ""); }
+static inline String ToString(const Transforms::Scale2D& p) noexcept { return "scale" + ToString(static_cast<const Transforms::ResolvedPrimitive< 2 >&>(p), ""); }
+static inline String ToString(const Transforms::Scale3D& p) noexcept { return "scale3d" + ToString(static_cast<const Transforms::ResolvedPrimitive< 3 >&>(p), ""); }
+static inline String ToString(const Transforms::RotateX& p) noexcept { return "rotateX" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
+static inline String ToString(const Transforms::RotateY& p) noexcept { return "rotateY" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
+static inline String ToString(const Transforms::RotateZ& p) noexcept { return "rotateZ" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
+static inline String ToString(const Transforms::Rotate2D& p) noexcept { return "rotate" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
+static inline String ToString(const Transforms::Rotate3D& p) noexcept { return "rotate3d" + ToString(static_cast<const Transforms::ResolvedPrimitive< 4 >&>(p), "deg", true, true); }
+static inline String ToString(const Transforms::SkewX& p) noexcept { return "skewX" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
+static inline String ToString(const Transforms::SkewY& p) noexcept { return "skewY" + ToString(static_cast<const Transforms::ResolvedPrimitive< 1 >&>(p), "deg", true); }
+static inline String ToString(const Transforms::Skew2D& p) noexcept { return "skew" + ToString(static_cast<const Transforms::ResolvedPrimitive< 2 >&>(p), "deg", true); }
+static inline String ToString(const Transforms::Perspective& p) noexcept { return "perspective" + ToString(static_cast<const Transforms::UnresolvedPrimitive< 1 >&>(p)); }
+
+struct ToStringVisitor
+{
+	String run(const TransformPrimitive& variant)
+	{
+		switch (variant.type)
+		{
+		case TransformPrimitive::MATRIX2D: return ToString(variant.matrix_2d);
+		case TransformPrimitive::MATRIX3D: return ToString(variant.matrix_3d);
+		case TransformPrimitive::TRANSLATEX: return ToString(variant.translate_x);
+		case TransformPrimitive::TRANSLATEY: return ToString(variant.translate_y);
+		case TransformPrimitive::TRANSLATEZ: return ToString(variant.translate_z);
+		case TransformPrimitive::TRANSLATE2D: return ToString(variant.translate_2d);
+		case TransformPrimitive::TRANSLATE3D: return ToString(variant.translate_3d);
+		case TransformPrimitive::SCALEX: return ToString(variant.scale_x);
+		case TransformPrimitive::SCALEY: return ToString(variant.scale_y);
+		case TransformPrimitive::SCALEZ: return ToString(variant.scale_z);
+		case TransformPrimitive::SCALE2D: return ToString(variant.scale_2d);
+		case TransformPrimitive::SCALE3D: return ToString(variant.scale_3d);
+		case TransformPrimitive::ROTATEX: return ToString(variant.rotate_x);
+		case TransformPrimitive::ROTATEY: return ToString(variant.rotate_y);
+		case TransformPrimitive::ROTATEZ: return ToString(variant.rotate_z);
+		case TransformPrimitive::ROTATE2D: return ToString(variant.rotate_2d);
+		case TransformPrimitive::ROTATE3D: return ToString(variant.rotate_3d);
+		case TransformPrimitive::SKEWX: return ToString(variant.skew_x);
+		case TransformPrimitive::SKEWY: return ToString(variant.skew_y);
+		case TransformPrimitive::SKEW2D: return ToString(variant.skew_2d);
+		case TransformPrimitive::PERSPECTIVE: return ToString(variant.perspective);
+		case TransformPrimitive::DECOMPOSEDMATRIX4: return ToString(variant.decomposed_matrix_4);
+		default:
+			break;
+		}
+		RMLUI_ASSERT(false);
+		return String();
+	}
+};
+
+String TransformUtilities::ToString(const TransformPrimitive& p) noexcept
+{
+	String result = ToStringVisitor{}.run(p);
+	return result;
+}
+
+
+bool TransformUtilities::Decompose(Transforms::DecomposedMatrix4& d, const Matrix4f& m) noexcept
+{
+	// Follows the procedure given in https://drafts.csswg.org/css-transforms-2/#interpolation-of-3d-matrices
+
+	const float eps = 0.0005f;
+
+	if (Math::AbsoluteValue(m[3][3]) < eps)
+		return false;
+
+
+	// Perspective matrix
+	Matrix4f p = m;
+
+	for (int i = 0; i < 3; i++)
+		p[i][3] = 0;
+	p[3][3] = 1;
+
+	if (Math::AbsoluteValue(p.Determinant()) < eps)
+		return false;
+
+	if (m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0)
+	{
+		auto rhs = m.GetColumn(3);
+		Matrix4f p_inv = p;
+		if (!p_inv.Invert())
+			return false;
+		auto& p_inv_trans = p.Transpose();
+		d.perspective = p_inv_trans * rhs;
+	}
+	else
+	{
+		d.perspective[0] = d.perspective[1] = d.perspective[2] = 0;
+		d.perspective[3] = 1;
+	}
+
+	for (int i = 0; i < 3; i++)
+		d.translation[i] = m[3][i];
+
+	Vector3f row[3];
+	for (int i = 0; i < 3; i++)
+	{
+		row[i][0] = m[i][0];
+		row[i][1] = m[i][1];
+		row[i][2] = m[i][2];
+	}
+
+	d.scale[0] = row[0].Magnitude();
+	row[0] = row[0].Normalise();
+
+	d.skew[0] = row[0].DotProduct(row[1]);
+	row[1] = Combine(row[1], row[0], 1, -d.skew[0]);
+
+	d.scale[1] = row[1].Magnitude();
+	row[1] = row[1].Normalise();
+	d.skew[0] /= d.scale[1];
+
+	d.skew[1] = row[0].DotProduct(row[2]);
+	row[2] = Combine(row[2], row[0], 1, -d.skew[1]);
+	d.skew[2] = row[1].DotProduct(row[2]);
+	row[2] = Combine(row[2], row[1], 1, -d.skew[2]);
+
+	d.scale[2] = row[2].Magnitude();
+	row[2] = row[2].Normalise();
+	d.skew[2] /= d.scale[2];
+	d.skew[1] /= d.scale[2];
+
+	// Check if we need to flip coordinate system
+	auto pdum3 = row[1].CrossProduct(row[2]);
+	if (row[0].DotProduct(pdum3) < 0.0f)
+	{
+		for (int i = 0; i < 3; i++)
+		{
+			d.scale[i] *= -1.f;
+			row[i] *= -1.f;
+		}
+	}
+
+	d.quaternion[0] = 0.5f * Math::SquareRoot(Math::Max(1.f + row[0][0] - row[1][1] - row[2][2], 0.0f));
+	d.quaternion[1] = 0.5f * Math::SquareRoot(Math::Max(1.f - row[0][0] + row[1][1] - row[2][2], 0.0f));
+	d.quaternion[2] = 0.5f * Math::SquareRoot(Math::Max(1.f - row[0][0] - row[1][1] + row[2][2], 0.0f));
+	d.quaternion[3] = 0.5f * Math::SquareRoot(Math::Max(1.f + row[0][0] + row[1][1] + row[2][2], 0.0f));
+
+	if (row[2][1] > row[1][2])
+		d.quaternion[0] *= -1.f;
+	if (row[0][2] > row[2][0])
+		d.quaternion[1] *= -1.f;
+	if (row[1][0] > row[0][1])
+		d.quaternion[2] *= -1.f;
+
+	return true;
+}
+
+} // namespace Rml

+ 71 - 0
Source/Core/TransformUtilities.h

@@ -0,0 +1,71 @@
+/*
+ * 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 RMLUI_CORE_TRANSFORMUTILITIES_H
+#define RMLUI_CORE_TRANSFORMUTILITIES_H
+
+#include "../../Include/RmlUi/Core/Header.h"
+#include "../../Include/RmlUi/Core/Types.h"
+
+namespace Rml {
+
+struct TransformPrimitive;
+namespace Transforms { struct DecomposedMatrix4; }
+
+
+namespace TransformUtilities
+{
+	// Set the primitive to its identity value.
+	void SetIdentity(TransformPrimitive& primitive) noexcept;
+
+	// Resolve the primitive into a transformation matrix, given the current element properties and layout.
+	Matrix4f ResolveTransform(const TransformPrimitive& primitive, Element& e) noexcept;
+	
+	// Prepares the 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.
+	// Returns false if the owning transform must to be converted to a DecomposedMatrix4 primitive.
+	bool PrepareForInterpolation(TransformPrimitive& primitive, Element& e) noexcept;
+
+	// If primitives do not match, try to convert them to a common generic type, e.g. TranslateX -> Translate3D.
+	// Returns true if they are already the same type or were converted to a common generic type.
+	bool TryConvertToMatchingGenericType(TransformPrimitive& p0, TransformPrimitive& p1) noexcept;
+
+	// Interpolate the target primitive with another primitive, weighted by alpha [0, 1].
+	// Primitives must be of the same type, and PrepareForInterpolation() must previously have been called on both.
+	bool InterpolateWith(TransformPrimitive& target, const TransformPrimitive& other, float alpha) noexcept;
+
+	// Decompose a Matrix4 into its decomposed components.
+	// Returns true on success, or false if the matrix is singular.
+	bool Decompose(Transforms::DecomposedMatrix4& decomposed_matrix, const Matrix4f& matrix) noexcept;
+
+	String ToString(const TransformPrimitive& primitive) noexcept;
+}
+
+
+} // namespace Rml
+#endif

+ 4 - 2
Source/Core/TypeConverter.cpp

@@ -30,6 +30,8 @@
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/Animation.h"
 #include "../../Include/RmlUi/Core/Transform.h"
+#include "../../Include/RmlUi/Core/TransformPrimitive.h"
+#include "TransformUtilities.h"
 
 namespace Rml {
 
@@ -44,10 +46,10 @@ bool TypeConverter<TransformPtr, String>::Convert(const TransformPtr& src, Strin
 	if (src)
 	{
 		dest.clear();
-		const Transform::Primitives& primitives = src->GetPrimitives();
+		const Transform::PrimitiveList& primitives = src->GetPrimitives();
 		for (size_t i = 0; i < primitives.size(); i++)
 		{
-			dest += primitives[i].ToString();
+			dest += TransformUtilities::ToString(primitives[i]);
 			if (i != primitives.size() - 1) 
 				dest += ' ';
 		}