|
@@ -31,10 +31,12 @@
|
|
|
#include "Header.h"
|
|
#include "Header.h"
|
|
|
#include "Types.h"
|
|
#include "Types.h"
|
|
|
#include "Property.h"
|
|
#include "Property.h"
|
|
|
|
|
+#include <variant>
|
|
|
|
|
|
|
|
namespace Rocket {
|
|
namespace Rocket {
|
|
|
namespace Core {
|
|
namespace Core {
|
|
|
namespace Transforms {
|
|
namespace Transforms {
|
|
|
|
|
+
|
|
|
|
|
|
|
|
struct NumericValue
|
|
struct NumericValue
|
|
|
{
|
|
{
|
|
@@ -56,282 +58,177 @@ struct NumericValue
|
|
|
Rocket::Core::Property::Unit unit;
|
|
Rocket::Core::Property::Unit unit;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- The Primitive class is the base class of geometric transforms such as rotations, scalings and translations.
|
|
|
|
|
- Instances of this class are added to a Rocket::Core::Transform instance
|
|
|
|
|
- by the Rocket::Core::PropertyParserTransform, which is responsible for
|
|
|
|
|
- parsing the `transform' property.
|
|
|
|
|
|
|
|
|
|
- @author Markus Schöngart
|
|
|
|
|
- @see Rocket::Core::Transform
|
|
|
|
|
- @see Rocket::Core::PropertyParserTransform
|
|
|
|
|
- */
|
|
|
|
|
-class Primitive
|
|
|
|
|
-{
|
|
|
|
|
- public:
|
|
|
|
|
- virtual ~Primitive() { }
|
|
|
|
|
-
|
|
|
|
|
- virtual Primitive* Clone() const = 0;
|
|
|
|
|
-
|
|
|
|
|
- /// Resolve the transformation matrix encoded by the primitive.
|
|
|
|
|
- /// @param m The transformation matrix to resolve the Primitive to.
|
|
|
|
|
- /// @param e The Element which to resolve the Primitive for.
|
|
|
|
|
- /// @return true if the Primitive encodes a transformation.
|
|
|
|
|
- virtual bool ResolveTransform(Matrix4f& m, Element& e) const throw()
|
|
|
|
|
- { return false; }
|
|
|
|
|
- /// Resolve the perspective value encoded by the primitive.
|
|
|
|
|
- /// @param p The perspective value to resolve the Primitive to.
|
|
|
|
|
- /// @param e The Element which to resolve the Primitive for.
|
|
|
|
|
- /// @return true if the Primitive encodes a perspective value.
|
|
|
|
|
- virtual bool ResolvePerspective(float &p, Element& e) const throw()
|
|
|
|
|
- { return false; }
|
|
|
|
|
-};
|
|
|
|
|
|
|
|
|
|
template< size_t N >
|
|
template< size_t N >
|
|
|
-class ResolvedValuesPrimitive : public Primitive
|
|
|
|
|
-{
|
|
|
|
|
- public:
|
|
|
|
|
- ResolvedValuesPrimitive(const NumericValue* values) throw()
|
|
|
|
|
- { for (size_t i = 0; i < N; ++i) this->values[i] = values[i].number; }
|
|
|
|
|
-
|
|
|
|
|
- void InterpolateValues(const ResolvedValuesPrimitive<N>& other, float alpha)
|
|
|
|
|
- {
|
|
|
|
|
- for (size_t i = 0; i < N; i++)
|
|
|
|
|
- values[i] = values[i]*(1.0f - alpha) + other.values[i] * alpha;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- protected:
|
|
|
|
|
- float values[N];
|
|
|
|
|
|
|
+struct ResolvedValuesPrimitive
|
|
|
|
|
+{
|
|
|
|
|
+ ResolvedValuesPrimitive(const float* values) throw()
|
|
|
|
|
+ {
|
|
|
|
|
+ for (size_t i = 0; i < N; ++i)
|
|
|
|
|
+ this->values[i] = values[i];
|
|
|
|
|
+ }
|
|
|
|
|
+ ResolvedValuesPrimitive(const NumericValue* values) throw()
|
|
|
|
|
+ {
|
|
|
|
|
+ for (size_t i = 0; i < N; ++i)
|
|
|
|
|
+ this->values[i] = values[i].number;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ float values[N];
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
template< size_t N >
|
|
template< size_t N >
|
|
|
-class UnresolvedValuesPrimitive : public Primitive
|
|
|
|
|
|
|
+struct UnresolvedValuesPrimitive
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- UnresolvedValuesPrimitive(const NumericValue* values) throw()
|
|
|
|
|
- { memcpy(this->values, values, sizeof(this->values)); }
|
|
|
|
|
-
|
|
|
|
|
- void InterpolateValues(const UnresolvedValuesPrimitive<N>& other, float alpha)
|
|
|
|
|
- {
|
|
|
|
|
- // TODO: Convert between different units?
|
|
|
|
|
- for (size_t i = 0; i < N; i++)
|
|
|
|
|
- values[i].number = values[i].number*(1.0f - alpha) + other.values[i].number * alpha;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- protected:
|
|
|
|
|
- NumericValue values[N];
|
|
|
|
|
|
|
+ UnresolvedValuesPrimitive(const NumericValue* values) throw()
|
|
|
|
|
+ {
|
|
|
|
|
+ memcpy(this->values, values, sizeof(this->values));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ NumericValue values[N];
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class Matrix2D : public ResolvedValuesPrimitive< 6 >
|
|
|
|
|
-{
|
|
|
|
|
- public:
|
|
|
|
|
- Matrix2D(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
|
|
|
|
|
- inline Primitive* Clone() const { return new Matrix2D(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
-};
|
|
|
|
|
|
|
|
|
|
-class Matrix3D : public ResolvedValuesPrimitive< 16 >
|
|
|
|
|
-{
|
|
|
|
|
- public:
|
|
|
|
|
- Matrix3D(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
|
|
|
|
|
- inline Primitive* Clone() const { return new Matrix3D(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
-};
|
|
|
|
|
|
|
|
|
|
-class TranslateX : public UnresolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct Matrix2D : public ResolvedValuesPrimitive< 6 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- TranslateX(const NumericValue* values) throw()
|
|
|
|
|
- : UnresolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new TranslateX(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ Matrix2D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class TranslateY : public UnresolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct Matrix3D : public ResolvedValuesPrimitive< 16 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- TranslateY(const NumericValue* values) throw()
|
|
|
|
|
- : UnresolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new TranslateY(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ Matrix3D(const Matrix4f& matrix) throw() : ResolvedValuesPrimitive(matrix.data()) { }
|
|
|
|
|
+ Matrix3D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class TranslateZ : public UnresolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct TranslateX : public UnresolvedValuesPrimitive< 1 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- TranslateZ(const NumericValue* values) throw()
|
|
|
|
|
- : UnresolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new TranslateZ(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ TranslateX(const NumericValue* values) throw() : UnresolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class Translate2D : public UnresolvedValuesPrimitive< 2 >
|
|
|
|
|
|
|
+struct TranslateY : public UnresolvedValuesPrimitive< 1 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- Translate2D(const NumericValue* values) throw()
|
|
|
|
|
- : UnresolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new Translate2D(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ TranslateY(const NumericValue* values) throw() : UnresolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class Translate3D : public UnresolvedValuesPrimitive< 3 >
|
|
|
|
|
|
|
+struct TranslateZ : public UnresolvedValuesPrimitive< 1 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- Translate3D(const NumericValue* values) throw()
|
|
|
|
|
- : UnresolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new Translate3D(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ TranslateZ(const NumericValue* values) throw() : UnresolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class ScaleX : public ResolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct Translate2D : public UnresolvedValuesPrimitive< 2 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- ScaleX(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new ScaleX(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ Translate2D(const NumericValue* values) throw() : UnresolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class ScaleY : public ResolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct Translate3D : public UnresolvedValuesPrimitive< 3 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- ScaleY(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new ScaleY(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ Translate3D(const NumericValue* values) throw() : UnresolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class ScaleZ : public ResolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct ScaleX : public ResolvedValuesPrimitive< 1 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- ScaleZ(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new ScaleZ(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ ScaleX(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class Scale2D : public ResolvedValuesPrimitive< 2 >
|
|
|
|
|
|
|
+struct ScaleY : public ResolvedValuesPrimitive< 1 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- Scale2D(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new Scale2D(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ ScaleY(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class Scale3D : public ResolvedValuesPrimitive< 3 >
|
|
|
|
|
|
|
+struct ScaleZ : public ResolvedValuesPrimitive< 1 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- Scale3D(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new Scale3D(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ ScaleZ(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class RotateX : public ResolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct Scale2D : public ResolvedValuesPrimitive< 2 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- RotateX(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new RotateX(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ Scale2D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class RotateY : public ResolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct Scale3D : public ResolvedValuesPrimitive< 3 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- RotateY(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new RotateY(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ Scale3D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class RotateZ : public ResolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct RotateX : public ResolvedValuesPrimitive< 1 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- RotateZ(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new RotateZ(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ RotateX(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class Rotate2D : public ResolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct RotateY : public ResolvedValuesPrimitive< 1 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- Rotate2D(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new Rotate2D(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ RotateY(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) {}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class Rotate3D : public ResolvedValuesPrimitive< 4 >
|
|
|
|
|
|
|
+struct RotateZ : public ResolvedValuesPrimitive< 1 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- Rotate3D(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
-
|
|
|
|
|
- inline Primitive* Clone() const { return new Rotate3D(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+ RotateZ(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class SkewX : public ResolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct Rotate2D : public ResolvedValuesPrimitive< 1 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- SkewX(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
|
|
+ Rotate2D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
- inline Primitive* Clone() const { return new SkewX(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+struct Rotate3D : public ResolvedValuesPrimitive< 4 >
|
|
|
|
|
+{
|
|
|
|
|
+ Rotate3D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class SkewY : public ResolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+struct SkewX : public ResolvedValuesPrimitive< 1 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- SkewY(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
|
|
+ SkewX(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
- inline Primitive* Clone() const { return new SkewY(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+struct SkewY : public ResolvedValuesPrimitive< 1 >
|
|
|
|
|
+{
|
|
|
|
|
+ SkewY(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class Skew2D : public ResolvedValuesPrimitive< 2 >
|
|
|
|
|
|
|
+struct Skew2D : public ResolvedValuesPrimitive< 2 >
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- Skew2D(const NumericValue* values) throw()
|
|
|
|
|
- : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
|
|
+ Skew2D(const NumericValue* values) throw() : ResolvedValuesPrimitive(values) { }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
- inline Primitive* Clone() const { return new Skew2D(*this); }
|
|
|
|
|
- bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
|
|
+struct Perspective : public UnresolvedValuesPrimitive< 1 >
|
|
|
|
|
+{
|
|
|
|
|
+ Perspective(const NumericValue* values) throw() : UnresolvedValuesPrimitive(values) { }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class Perspective : public UnresolvedValuesPrimitive< 1 >
|
|
|
|
|
|
|
+
|
|
|
|
|
+using PrimitiveVariant = std::variant<
|
|
|
|
|
+ Matrix2D, Matrix3D,
|
|
|
|
|
+ TranslateX, TranslateY, TranslateZ, Translate2D, Translate3D,
|
|
|
|
|
+ ScaleX, ScaleY, ScaleZ, Scale2D, Scale3D,
|
|
|
|
|
+ RotateX, RotateY, RotateZ, Rotate2D, Rotate3D,
|
|
|
|
|
+ SkewX, SkewY, Skew2D,
|
|
|
|
|
+ Perspective>;
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ The Primitive struct is the base struct of geometric transforms such as rotations, scalings and translations.
|
|
|
|
|
+ Instances of this struct are added to a Rocket::Core::Transform instance
|
|
|
|
|
+ by the Rocket::Core::PropertyParserTransform, which is responsible for
|
|
|
|
|
+ parsing the `transform' property.
|
|
|
|
|
+
|
|
|
|
|
+ @author Markus Schöngart
|
|
|
|
|
+ @see Rocket::Core::Transform
|
|
|
|
|
+ @see Rocket::Core::PropertyParserTransform
|
|
|
|
|
+ */
|
|
|
|
|
+struct Primitive
|
|
|
{
|
|
{
|
|
|
- public:
|
|
|
|
|
- Perspective(const NumericValue* values) throw()
|
|
|
|
|
- : UnresolvedValuesPrimitive(values) { }
|
|
|
|
|
|
|
+ PrimitiveVariant primitive;
|
|
|
|
|
|
|
|
- inline Primitive* Clone() const { return new Perspective(*this); }
|
|
|
|
|
- bool ResolvePerspective(float& p, Element& e) const throw();
|
|
|
|
|
|
|
+ void SetIdentity() throw();
|
|
|
|
|
+ bool ResolveTransform(Matrix4f& m, Element& e) const throw();
|
|
|
|
|
+ bool ResolvePerspective(float &p, Element& e) const throw();
|
|
|
|
|
+ bool InterpolateWith(const Primitive& other, float alpha) throw();
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|