Browse Source

Implement support for different angle units in transforms. Resolve units of Primitives when adding animation keys.

Michael 7 years ago
parent
commit
d093d96237

+ 10 - 10
Include/Rocket/Core/Matrix4.inl

@@ -539,8 +539,8 @@ template< typename Component, class Storage>
 Matrix4< Component, Storage > Matrix4< Component, Storage >::Rotate(const Vector3< Component >& v, Component angle) throw()
 Matrix4< Component, Storage > Matrix4< Component, Storage >::Rotate(const Vector3< Component >& v, Component angle) throw()
 {
 {
 	Vector3< Component > n = v.Normalise();
 	Vector3< Component > n = v.Normalise();
-	Component Sin = Math::Sin(Math::DegreesToRadians(angle));
-	Component Cos = Math::Cos(Math::DegreesToRadians(angle));
+	Component Sin = Math::Sin(angle);
+	Component Cos = Math::Cos(angle);
 	return Matrix4< Component, Storage >::FromRows(
 	return Matrix4< Component, Storage >::FromRows(
 		Matrix4< Component, Storage >::VectorType(
 		Matrix4< Component, Storage >::VectorType(
 			n.x * n.x * (1 - Cos) +       Cos,
 			n.x * n.x * (1 - Cos) +       Cos,
@@ -567,8 +567,8 @@ Matrix4< Component, Storage > Matrix4< Component, Storage >::Rotate(const Vector
 template< typename Component, class Storage>
 template< typename Component, class Storage>
 Matrix4< Component, Storage > Matrix4< Component, Storage >::RotateX(Component angle) throw()
 Matrix4< Component, Storage > Matrix4< Component, Storage >::RotateX(Component angle) throw()
 {
 {
-	Component Sin = Math::Sin(Math::DegreesToRadians(angle));
-	Component Cos = Math::Cos(Math::DegreesToRadians(angle));
+	Component Sin = Math::Sin(angle);
+	Component Cos = Math::Cos(angle);
 	return Matrix4< Component, Storage >::FromRows(
 	return Matrix4< Component, Storage >::FromRows(
 		Matrix4< Component, Storage >::VectorType(1, 0,    0,   0),
 		Matrix4< Component, Storage >::VectorType(1, 0,    0,   0),
 		Matrix4< Component, Storage >::VectorType(0, Cos, -Sin, 0),
 		Matrix4< Component, Storage >::VectorType(0, Cos, -Sin, 0),
@@ -580,8 +580,8 @@ Matrix4< Component, Storage > Matrix4< Component, Storage >::RotateX(Component a
 template< typename Component, class Storage>
 template< typename Component, class Storage>
 Matrix4< Component, Storage > Matrix4< Component, Storage >::RotateY(Component angle) throw()
 Matrix4< Component, Storage > Matrix4< Component, Storage >::RotateY(Component angle) throw()
 {
 {
-	Component Sin = Math::Sin(Math::DegreesToRadians(angle));
-	Component Cos = Math::Cos(Math::DegreesToRadians(angle));
+	Component Sin = Math::Sin(angle);
+	Component Cos = Math::Cos(angle);
 	return Matrix4< Component, Storage >::FromRows(
 	return Matrix4< Component, Storage >::FromRows(
 		Matrix4< Component, Storage >::VectorType( Cos, 0, Sin, 0),
 		Matrix4< Component, Storage >::VectorType( Cos, 0, Sin, 0),
 		Matrix4< Component, Storage >::VectorType( 0,   1, 0,   0),
 		Matrix4< Component, Storage >::VectorType( 0,   1, 0,   0),
@@ -593,8 +593,8 @@ Matrix4< Component, Storage > Matrix4< Component, Storage >::RotateY(Component a
 template< typename Component, class Storage>
 template< typename Component, class Storage>
 Matrix4< Component, Storage > Matrix4< Component, Storage >::RotateZ(Component angle) throw()
 Matrix4< Component, Storage > Matrix4< Component, Storage >::RotateZ(Component angle) throw()
 {
 {
-	Component Sin = Math::Sin(Math::DegreesToRadians(angle));
-	Component Cos = Math::Cos(Math::DegreesToRadians(angle));
+	Component Sin = Math::Sin(angle);
+	Component Cos = Math::Cos(angle);
 	return Matrix4< Component, Storage >::FromRows(
 	return Matrix4< Component, Storage >::FromRows(
 		Matrix4< Component, Storage >::VectorType(Cos, -Sin, 0, 0),
 		Matrix4< Component, Storage >::VectorType(Cos, -Sin, 0, 0),
 		Matrix4< Component, Storage >::VectorType(Sin,  Cos, 0, 0),
 		Matrix4< Component, Storage >::VectorType(Sin,  Cos, 0, 0),
@@ -607,8 +607,8 @@ Matrix4< Component, Storage > Matrix4< Component, Storage >::RotateZ(Component a
 template< typename Component, class Storage>
 template< typename Component, class Storage>
 Matrix4< Component, Storage > Matrix4< Component, Storage >::Skew(Component angle_x, Component angle_y) throw()
 Matrix4< Component, Storage > Matrix4< Component, Storage >::Skew(Component angle_x, Component angle_y) throw()
 {
 {
-	Component SkewX = Math::Tan(Math::DegreesToRadians(angle_x));
-	Component SkewY = Math::Tan(Math::DegreesToRadians(angle_y));
+	Component SkewX = Math::Tan(angle_x);
+	Component SkewY = Math::Tan(angle_y);
 	return Matrix4< Component, Storage >::FromRows(
 	return Matrix4< Component, Storage >::FromRows(
 		Matrix4< Component, Storage >::VectorType(1,     SkewX, 0, 0),
 		Matrix4< Component, Storage >::VectorType(1,     SkewX, 0, 0),
 		Matrix4< Component, Storage >::VectorType(SkewY, 1,     0, 0),
 		Matrix4< Component, Storage >::VectorType(SkewY, 1,     0, 0),

+ 23 - 11
Include/Rocket/Core/TransformPrimitive.h

@@ -32,6 +32,7 @@
 #include "Types.h"
 #include "Types.h"
 #include "Property.h"
 #include "Property.h"
 #include <variant>
 #include <variant>
+#include <array>
 
 
 namespace Rocket {
 namespace Rocket {
 namespace Core {
 namespace Core {
@@ -53,13 +54,15 @@ struct NumericValue
 	float ResolveHeight(Element& e) const throw();
 	float ResolveHeight(Element& e) const throw();
 	/// Resolve a numeric property value with the element's depth as relative base value.
 	/// Resolve a numeric property value with the element's depth as relative base value.
 	float ResolveDepth(Element& e) const throw();
 	float ResolveDepth(Element& e) const throw();
+	/// 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 throw();
 
 
 	float number;
 	float number;
-	Rocket::Core::Property::Unit unit;
+	Property::Unit unit;
 };
 };
 
 
 
 
-
 template< size_t N >
 template< size_t N >
 struct ResolvedValuesPrimitive
 struct ResolvedValuesPrimitive
 {
 {
@@ -73,6 +76,11 @@ struct ResolvedValuesPrimitive
 		for (size_t i = 0; i < N; ++i) 
 		for (size_t i = 0; i < N; ++i) 
 			this->values[i] = values[i].number;
 			this->values[i] = values[i].number;
 	}
 	}
+	ResolvedValuesPrimitive(const NumericValue* values, std::array<Property::Unit, N> base_units) throw()
+	{
+		for (size_t i = 0; i < N; ++i)
+			this->values[i] = values[i].ResolveAbsoluteUnit(base_units[i]);
+	}
 	
 	
 	float values[N];
 	float values[N];
 };
 };
@@ -155,42 +163,42 @@ struct Scale3D : public ResolvedValuesPrimitive< 3 >
 
 
 struct RotateX : public ResolvedValuesPrimitive< 1 >
 struct RotateX : public ResolvedValuesPrimitive< 1 >
 {
 {
-	RotateX(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
+	RotateX(const NumericValue* values) throw() : ResolvedValuesPrimitive(values, { Property::RAD }) { }
 };
 };
 
 
 struct RotateY : public ResolvedValuesPrimitive< 1 >
 struct RotateY : public ResolvedValuesPrimitive< 1 >
 {
 {
-	RotateY(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) {}
+	RotateY(const NumericValue* values) throw() : ResolvedValuesPrimitive(values, { Property::RAD }) {}
 };
 };
 
 
 struct RotateZ : public ResolvedValuesPrimitive< 1 >
 struct RotateZ : public ResolvedValuesPrimitive< 1 >
 {
 {
-	RotateZ(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
+	RotateZ(const NumericValue* values) throw() : ResolvedValuesPrimitive(values, { Property::RAD }) { }
 };
 };
 
 
 struct Rotate2D : public ResolvedValuesPrimitive< 1 >
 struct Rotate2D : public ResolvedValuesPrimitive< 1 >
 {
 {
-	Rotate2D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
+	Rotate2D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values, { Property::RAD }) { }
 };
 };
 
 
 struct Rotate3D : public ResolvedValuesPrimitive< 4 >
 struct Rotate3D : public ResolvedValuesPrimitive< 4 >
 {
 {
-	Rotate3D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
+	Rotate3D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values, { Property::NUMBER, Property::NUMBER, Property::NUMBER, Property::RAD }) { }
 };
 };
 
 
 struct SkewX : public ResolvedValuesPrimitive< 1 >
 struct SkewX : public ResolvedValuesPrimitive< 1 >
 {
 {
-	SkewX(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
+	SkewX(const NumericValue* values) throw() : ResolvedValuesPrimitive(values, { Property::RAD }) { }
 };
 };
 
 
 struct SkewY : public ResolvedValuesPrimitive< 1 >
 struct SkewY : public ResolvedValuesPrimitive< 1 >
 {
 {
-	SkewY(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
+	SkewY(const NumericValue* values) throw() : ResolvedValuesPrimitive(values, { Property::RAD }) { }
 };
 };
 
 
 struct Skew2D : public ResolvedValuesPrimitive< 2 >
 struct Skew2D : public ResolvedValuesPrimitive< 2 >
 {
 {
-	Skew2D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
+	Skew2D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values, { Property::RAD, Property::RAD }) { }
 };
 };
 
 
 struct Perspective : public UnresolvedValuesPrimitive< 1 >
 struct Perspective : public UnresolvedValuesPrimitive< 1 >
@@ -222,10 +230,14 @@ struct Primitive
 {
 {
 	PrimitiveVariant primitive;
 	PrimitiveVariant primitive;
 
 
-	void SetIdentity() throw();
 	bool ResolveTransform(Matrix4f& m, Element& e) const throw();
 	bool ResolveTransform(Matrix4f& m, Element& e) const throw();
 	bool ResolvePerspective(float &p, Element& e) const throw();
 	bool ResolvePerspective(float &p, Element& e) const throw();
+	
+	// Promote units to basic types which can be interpolated, that is, convert 'length -> pixel' for unresolved primitives.
+	bool ResolveUnits(Element& e) throw();
+
 	bool InterpolateWith(const Primitive& other, float alpha) throw();
 	bool InterpolateWith(const Primitive& other, float alpha) throw();
+	void SetIdentity() throw();
 };
 };
 
 
 
 

+ 8 - 4
Samples/basic/animation/src/main.cpp

@@ -38,8 +38,8 @@
 
 
 // Animations TODO:
 // Animations TODO:
 //  - Jittering on slow margin-left updates
 //  - Jittering on slow margin-left updates
-//  - Proper interpolation of full transform matrices (split into translate/rotate/skew/scale). Also, support interpolation of primitive derivatives without going to full matrices.
-//  - Conversion between different units (resolve matrix units, e.g. to px / deg, when adding animation key).
+//  - Proper interpolation of full transform matrices (split into translate/rotate/skew/scale).
+//  - Support interpolation of primitive derivatives without going to full matrices.
 //  - Better error reporting when submitting invalid animations, check validity on add. Remove animation if invalid.
 //  - Better error reporting when submitting invalid animations, check validity on add. Remove animation if invalid.
 //  - Tweening
 //  - Tweening
 //  - RCSS support? Both @keyframes and transition, maybe.
 //  - RCSS support? Both @keyframes and transition, maybe.
@@ -69,12 +69,16 @@ public:
 
 
 			el = document->GetElementById("start_game");
 			el = document->GetElementById("start_game");
 			auto t0 = TransformRef{ new Transform };
 			auto t0 = TransformRef{ new Transform };
-			auto v0 = Transforms::NumericValue(370.f, Property::DEG);
+			auto v0 = Transforms::NumericValue(100.f, Property::PERCENT);
 			t0->AddPrimitive({ Transforms::Rotate2D{ &v0 } });
 			t0->AddPrimitive({ Transforms::Rotate2D{ &v0 } });
 			//auto t1 = TransformRef{ new Transform };
 			//auto t1 = TransformRef{ new Transform };
 			//auto v1 = Transforms::NumericValue(370.f, Property::DEG);
 			//auto v1 = Transforms::NumericValue(370.f, Property::DEG);
 			//t1->AddPrimitive({ Transforms::Rotate2D{ &v1 } });
 			//t1->AddPrimitive({ Transforms::Rotate2D{ &v1 } });
-			el->Animate("transform", Property(t0, Property::TRANSFORM), 1.3f, -1, true);
+			PropertyDictionary pd;
+			StyleSheetSpecification::ParsePropertyDeclaration(pd, "transform", "rotate(200%)");
+			auto p = pd.GetProperty("transform");
+			el->Animate("transform", *p, 1.3f, -1, true);
+			//el->Animate("transform", Property(t0, Property::TRANSFORM), 1.3f, -1, true);
 
 
 			el = document->GetElementById("help");
 			el = document->GetElementById("help");
 			el->Animate("image-color", Property(Colourb(128, 255, 255, 255), Property::COLOUR), 0.3f, -1, false);
 			el->Animate("image-color", Property(Colourb(128, 255, 255, 255), Property::COLOUR), 0.3f, -1, false);

+ 10 - 3
Source/Core/ElementAnimation.cpp

@@ -269,13 +269,20 @@ bool ElementAnimation::AddKey(float time, const Property & property, Element& el
 	if (property.unit != property_unit)
 	if (property.unit != property_unit)
 		return false;
 		return false;
 
 
-	keys.push_back({ time, property.value });
-
 	if (property.unit == Property::TRANSFORM)
 	if (property.unit == Property::TRANSFORM)
 	{
 	{
-		PrepareTransforms(keys, element);
+		for (auto& primitive : property.value.Get<TransformRef>()->GetPrimitives())
+		{
+			if (!primitive.ResolveUnits(element))
+				return false;
+		}
+
+		if (!PrepareTransforms(keys, element))
+			return false;
 	}
 	}
 
 
+	keys.push_back({ time, property.value });
+
 	return true;
 	return true;
 }
 }
 
 

+ 3 - 3
Source/Core/ElementStyle.cpp

@@ -356,7 +356,7 @@ float ElementStyle::ResolveProperty(const Property* property, float base_value)
 	{
 	{
 		case Property::NUMBER:
 		case Property::NUMBER:
 		case Property::PX:
 		case Property::PX:
-		case Property::DEG:
+		case Property::RAD:
 			return property->value.Get< float >();
 			return property->value.Get< float >();
 
 
 		case Property::PERCENT:
 		case Property::PERCENT:
@@ -369,8 +369,8 @@ float ElementStyle::ResolveProperty(const Property* property, float base_value)
 		case Property::DP:
 		case Property::DP:
 			return property->value.Get< float >() * ElementUtilities::GetDensityIndependentPixelRatio(element);
 			return property->value.Get< float >() * ElementUtilities::GetDensityIndependentPixelRatio(element);
 
 
-		case Property::RAD:
-			return Math::RadiansToDegrees(property->value.Get< float >());
+		case Property::DEG:
+			return Math::DegreesToRadians(property->value.Get< float >());
 	}
 	}
 
 
 	// Values based on pixels-per-inch.
 	// Values based on pixels-per-inch.

+ 1 - 3
Source/Core/PropertyParserNumber.h

@@ -44,9 +44,7 @@ class PropertyParserNumber : public PropertyParser
 public:
 public:
 	enum
 	enum
 	{
 	{
-		ABS_NUMBER =	Property::NUMBER,
-		NUMBER =	Property::NUMBER
-				| Property::PERCENT,
+		NUMBER =	Property::NUMBER,
 		LENGTH =	Property::NUMBER
 		LENGTH =	Property::NUMBER
 				| Property::PERCENT
 				| Property::PERCENT
 				| Property::PX
 				| Property::PX

+ 7 - 8
Source/Core/PropertyParserTransform.cpp

@@ -32,8 +32,7 @@ namespace Rocket {
 namespace Core {
 namespace Core {
 
 
 PropertyParserTransform::PropertyParserTransform()
 PropertyParserTransform::PropertyParserTransform()
-	: abs_number(PropertyParserNumber::ABS_NUMBER),
-	  number(PropertyParserNumber::NUMBER),
+	: number(PropertyParserNumber::NUMBER),
 	  length(PropertyParserNumber::LENGTH),
 	  length(PropertyParserNumber::LENGTH),
 	  angle(PropertyParserNumber::ANGLE)
 	  angle(PropertyParserNumber::ANGLE)
 {
 {
@@ -57,12 +56,12 @@ bool PropertyParserTransform::ParseValue(Property& property, const String& value
 	const PropertyParser* length1[] = { &length };
 	const PropertyParser* length1[] = { &length };
 	const PropertyParser* length2[] = { &length, &length };
 	const PropertyParser* length2[] = { &length, &length };
 	const PropertyParser* length3[] = { &length, &length, &length };
 	const PropertyParser* length3[] = { &length, &length, &length };
-	const PropertyParser* length3angle1[] = { &length, &length, &length, &angle };
+	const PropertyParser* number3angle1[] = { &number, &number, &number, &angle };
 	const PropertyParser* number1[] = { &number };
 	const PropertyParser* number1[] = { &number };
 	const PropertyParser* number2[] = { &number, &number };
 	const PropertyParser* number2[] = { &number, &number };
 	const PropertyParser* number3[] = { &number, &number, &number };
 	const PropertyParser* number3[] = { &number, &number, &number };
-	const PropertyParser* abs_numbers6[] = { &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number };
-	const PropertyParser* abs_numbers16[] = { &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number, &abs_number };
+	const PropertyParser* number6[] = { &number, &number, &number, &number, &number, &number };
+	const PropertyParser* number16[] = { &number, &number, &number, &number, &number, &number, &number, &number, &number, &number, &number, &number, &number, &number, &number, &number };
 	while (strlen(next))
 	while (strlen(next))
 	{
 	{
 		using namespace Transforms;
 		using namespace Transforms;
@@ -72,11 +71,11 @@ bool PropertyParserTransform::ParseValue(Property& property, const String& value
 		{
 		{
 			transform->AddPrimitive({ Perspective(args) });
 			transform->AddPrimitive({ Perspective(args) });
 		}
 		}
-		else if ((bytes_read = Scan(next, "matrix", abs_numbers6, args, 6)))
+		else if ((bytes_read = Scan(next, "matrix", number6, args, 6)))
 		{
 		{
 			transform->AddPrimitive({ Matrix2D(args) });
 			transform->AddPrimitive({ Matrix2D(args) });
 		}
 		}
-		else if ((bytes_read = Scan(next, "matrix3d", abs_numbers16, args, 16)))
+		else if ((bytes_read = Scan(next, "matrix3d", number16, args, 16)))
 		{
 		{
 			transform->AddPrimitive({ Matrix3D(args) });
 			transform->AddPrimitive({ Matrix3D(args) });
 		}
 		}
@@ -141,7 +140,7 @@ bool PropertyParserTransform::ParseValue(Property& property, const String& value
 		{
 		{
 			transform->AddPrimitive({ Rotate2D(args) });
 			transform->AddPrimitive({ Rotate2D(args) });
 		}
 		}
-		else if ((bytes_read = Scan(next, "rotate3d", length3angle1, args, 4)))
+		else if ((bytes_read = Scan(next, "rotate3d", number3angle1, args, 4)))
 		{
 		{
 			transform->AddPrimitive({ Rotate3D(args) });
 			transform->AddPrimitive({ Rotate3D(args) });
 		}
 		}

+ 1 - 1
Source/Core/PropertyParserTransform.h

@@ -67,7 +67,7 @@ private:
 	/// @returns The number of bytes read, if the function call occurs at the beginning of str, 0 otherwise.
 	/// @returns The number of bytes read, if the function call occurs at the beginning of str, 0 otherwise.
 	int Scan(const char* str, const char* keyword, const PropertyParser** parsers, Transforms::NumericValue* args, int nargs) const;
 	int Scan(const char* str, const char* keyword, const PropertyParser** parsers, Transforms::NumericValue* args, int nargs) const;
 
 
-	PropertyParserNumber abs_number, number, length, angle;
+	PropertyParserNumber number, length, angle;
 };
 };
 
 
 }
 }

+ 79 - 3
Source/Core/TransformPrimitive.cpp

@@ -53,20 +53,45 @@ float NumericValue::Resolve(Element& e, float base) const throw()
 
 
 float NumericValue::ResolveWidth(Element& e) const throw()
 float NumericValue::ResolveWidth(Element& e) const throw()
 {
 {
+	if(unit & (Property::PX | Property::NUMBER)) return number;
 	return Resolve(e, e.GetBox().GetSize().x);
 	return Resolve(e, e.GetBox().GetSize().x);
 }
 }
 
 
 float NumericValue::ResolveHeight(Element& e) const throw()
 float NumericValue::ResolveHeight(Element& e) const throw()
 {
 {
+	if (unit & (Property::PX | Property::NUMBER)) return number;
 	return Resolve(e, e.GetBox().GetSize().y);
 	return Resolve(e, e.GetBox().GetSize().y);
 }
 }
 
 
 float NumericValue::ResolveDepth(Element& e) const throw()
 float NumericValue::ResolveDepth(Element& e) const throw()
 {
 {
+	if (unit & (Property::PX | Property::NUMBER)) return number;
 	Vector2f size = e.GetBox().GetSize();
 	Vector2f size = e.GetBox().GetSize();
 	return Resolve(e, Math::Max(size.x, size.y));
 	return Resolve(e, Math::Max(size.x, size.y));
 }
 }
 
 
+float NumericValue::ResolveAbsoluteUnit(Property::Unit base_unit) const throw()
+{
+	switch (base_unit)
+	{
+	case Property::RAD:
+	{
+		switch (unit)
+		{
+		case Property::NUMBER:
+		case Property::DEG:
+			return Math::DegreesToRadians(number);
+		case Property::RAD:
+			return number;
+		case Property::PERCENT:
+			return number * 0.01f * 2.0f * Math::ROCKET_PI;
+			break;
+		}
+	}
+	}
+	return number;
+}
+
 
 
 
 
 
 
@@ -319,7 +344,7 @@ struct InterpolateVisitor
 	template <size_t N>
 	template <size_t N>
 	void Interpolate(UnresolvedValuesPrimitive<N>& p0, const UnresolvedValuesPrimitive<N>& p1)
 	void Interpolate(UnresolvedValuesPrimitive<N>& p0, const UnresolvedValuesPrimitive<N>& p1)
 	{
 	{
-		// TODO: While we have the same type and dimension, we may have different units. Should convert?
+		// Assumes that the underlying units have been resolved (e.g. to pixels)
 		for (size_t i = 0; i < N; i++)
 		for (size_t i = 0; i < N; i++)
 			p0.values[i].number = p0.values[i].number*(1.0f - alpha) + p1.values[i].number * alpha;
 			p0.values[i].number = p0.values[i].number*(1.0f - alpha) + p1.values[i].number * alpha;
 	}
 	}
@@ -336,8 +361,6 @@ struct InterpolateVisitor
 	}
 	}
 };
 };
 
 
-
-
 bool Primitive::InterpolateWith(const Primitive & other, float alpha) throw()
 bool Primitive::InterpolateWith(const Primitive & other, float alpha) throw()
 {
 {
 	if (primitive.index() != other.primitive.index())
 	if (primitive.index() != other.primitive.index())
@@ -349,6 +372,59 @@ bool Primitive::InterpolateWith(const Primitive & other, float alpha) throw()
 }
 }
 
 
 
 
+struct ResolveUnitsVisitor
+{
+	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()(ResolvedValuesPrimitive<N>& p)
+	{
+		// No conversion needed for resolved transforms
+		return true;
+	}
+	bool operator()(Perspective& p)
+	{
+		// Perspective is special and not used for transform animations, ignore.
+		return false;
+	}
+};
+
+
+bool Primitive::ResolveUnits(Element & e) throw()
+{
+	return std::visit(ResolveUnitsVisitor{ e }, primitive);
+}
+
+
+
 
 
 }
 }
 }
 }