mirror of
https://github.com/brl/mutter.git
synced 2024-11-22 08:00:42 -05:00
clutter: Use graphene to interpolate matrixes
This commit is contained in:
parent
a127360df1
commit
82382ddc2d
@ -126,65 +126,15 @@ clutter_matrix_progress (const GValue *a,
|
|||||||
{
|
{
|
||||||
const ClutterMatrix *matrix1 = g_value_get_boxed (a);
|
const ClutterMatrix *matrix1 = g_value_get_boxed (a);
|
||||||
const ClutterMatrix *matrix2 = g_value_get_boxed (b);
|
const ClutterMatrix *matrix2 = g_value_get_boxed (b);
|
||||||
graphene_point3d_t scale1 = GRAPHENE_POINT3D_INIT (1.f, 1.f, 1.f);
|
graphene_matrix_t m1, m2, m;
|
||||||
float shear1[3] = { 0.f, 0.f, 0.f };
|
|
||||||
graphene_point3d_t rotate1 = GRAPHENE_POINT3D_INIT_ZERO;
|
|
||||||
graphene_point3d_t translate1 = GRAPHENE_POINT3D_INIT_ZERO;
|
|
||||||
ClutterVertex4 perspective1 = { 0.f, 0.f, 0.f, 0.f };
|
|
||||||
graphene_point3d_t scale2 = GRAPHENE_POINT3D_INIT (1.f, 1.f, 1.f);
|
|
||||||
float shear2[3] = { 0.f, 0.f, 0.f };
|
|
||||||
graphene_point3d_t rotate2 = GRAPHENE_POINT3D_INIT_ZERO;
|
|
||||||
graphene_point3d_t translate2 = GRAPHENE_POINT3D_INIT_ZERO;
|
|
||||||
ClutterVertex4 perspective2 = { 0.f, 0.f, 0.f, 0.f };
|
|
||||||
graphene_point3d_t scale_res = GRAPHENE_POINT3D_INIT (1.f, 1.f, 1.f);
|
|
||||||
float shear_res = 0.f;
|
|
||||||
graphene_point3d_t rotate_res = GRAPHENE_POINT3D_INIT_ZERO;
|
|
||||||
graphene_point3d_t translate_res = GRAPHENE_POINT3D_INIT_ZERO;
|
|
||||||
ClutterVertex4 perspective_res = { 0.f, 0.f, 0.f, 0.f };
|
|
||||||
ClutterMatrix res;
|
ClutterMatrix res;
|
||||||
|
|
||||||
clutter_matrix_init_identity (&res);
|
cogl_matrix_to_graphene_matrix (matrix1, &m1);
|
||||||
|
cogl_matrix_to_graphene_matrix (matrix2, &m2);
|
||||||
|
|
||||||
_clutter_util_matrix_decompose (matrix1,
|
graphene_matrix_interpolate (&m1, &m2, progress, &m);
|
||||||
&scale1, shear1, &rotate1, &translate1,
|
|
||||||
&perspective1);
|
|
||||||
_clutter_util_matrix_decompose (matrix2,
|
|
||||||
&scale2, shear2, &rotate2, &translate2,
|
|
||||||
&perspective2);
|
|
||||||
|
|
||||||
/* perspective */
|
graphene_matrix_to_cogl_matrix (&m, &res);
|
||||||
_clutter_util_vertex4_interpolate (&perspective1, &perspective2, progress, &perspective_res);
|
|
||||||
res.wx = perspective_res.x;
|
|
||||||
res.wy = perspective_res.y;
|
|
||||||
res.wz = perspective_res.z;
|
|
||||||
res.ww = perspective_res.w;
|
|
||||||
|
|
||||||
/* translation */
|
|
||||||
graphene_point3d_interpolate (&translate1, &translate2, progress, &translate_res);
|
|
||||||
cogl_matrix_translate (&res, translate_res.x, translate_res.y, translate_res.z);
|
|
||||||
|
|
||||||
/* rotation */
|
|
||||||
graphene_point3d_interpolate (&rotate1, &rotate2, progress, &rotate_res);
|
|
||||||
cogl_matrix_rotate (&res, rotate_res.x, 1.0f, 0.0f, 0.0f);
|
|
||||||
cogl_matrix_rotate (&res, rotate_res.y, 0.0f, 1.0f, 0.0f);
|
|
||||||
cogl_matrix_rotate (&res, rotate_res.z, 0.0f, 0.0f, 1.0f);
|
|
||||||
|
|
||||||
/* skew */
|
|
||||||
shear_res = shear1[2] + (shear2[2] - shear1[2]) * progress; /* YZ */
|
|
||||||
if (shear_res != 0.f)
|
|
||||||
_clutter_util_matrix_skew_yz (&res, shear_res);
|
|
||||||
|
|
||||||
shear_res = shear1[1] + (shear2[1] - shear1[1]) * progress; /* XZ */
|
|
||||||
if (shear_res != 0.f)
|
|
||||||
_clutter_util_matrix_skew_xz (&res, shear_res);
|
|
||||||
|
|
||||||
shear_res = shear1[0] + (shear2[0] - shear1[0]) * progress; /* XY */
|
|
||||||
if (shear_res != 0.f)
|
|
||||||
_clutter_util_matrix_skew_xy (&res, shear_res);
|
|
||||||
|
|
||||||
/* scale */
|
|
||||||
graphene_point3d_interpolate (&scale1, &scale2, progress, &scale_res);
|
|
||||||
cogl_matrix_scale (&res, scale_res.x, scale_res.y, scale_res.z);
|
|
||||||
|
|
||||||
g_value_set_boxed (retval, &res);
|
g_value_set_boxed (retval, &res);
|
||||||
|
|
||||||
|
@ -274,12 +274,6 @@ struct _ClutterVertex4
|
|||||||
float w;
|
float w;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
|
||||||
_clutter_util_vertex4_interpolate (const ClutterVertex4 *a,
|
|
||||||
const ClutterVertex4 *b,
|
|
||||||
double progress,
|
|
||||||
ClutterVertex4 *res);
|
|
||||||
|
|
||||||
#define CLUTTER_MATRIX_INIT_IDENTITY { \
|
#define CLUTTER_MATRIX_INIT_IDENTITY { \
|
||||||
1.0f, 0.0f, 0.0f, 0.0f, \
|
1.0f, 0.0f, 0.0f, 0.0f, \
|
||||||
0.0f, 1.0f, 0.0f, 0.0f, \
|
0.0f, 1.0f, 0.0f, 0.0f, \
|
||||||
@ -287,22 +281,6 @@ _clutter_util_vertex4_interpolate (const ClutterVertex4 *a,
|
|||||||
0.0f, 0.0f, 0.0f, 1.0f, \
|
0.0f, 0.0f, 0.0f, 1.0f, \
|
||||||
}
|
}
|
||||||
|
|
||||||
float _clutter_util_matrix_determinant (const ClutterMatrix *matrix);
|
|
||||||
|
|
||||||
void _clutter_util_matrix_skew_xy (ClutterMatrix *matrix,
|
|
||||||
float factor);
|
|
||||||
void _clutter_util_matrix_skew_xz (ClutterMatrix *matrix,
|
|
||||||
float factor);
|
|
||||||
void _clutter_util_matrix_skew_yz (ClutterMatrix *matrix,
|
|
||||||
float factor);
|
|
||||||
|
|
||||||
gboolean _clutter_util_matrix_decompose (const ClutterMatrix *src,
|
|
||||||
graphene_point3d_t *scale_p,
|
|
||||||
float shear_p[3],
|
|
||||||
graphene_point3d_t *rotate_p,
|
|
||||||
graphene_point3d_t *translate_p,
|
|
||||||
ClutterVertex4 *perspective_p);
|
|
||||||
|
|
||||||
typedef struct _ClutterPlane
|
typedef struct _ClutterPlane
|
||||||
{
|
{
|
||||||
graphene_vec3_t v0;
|
graphene_vec3_t v0;
|
||||||
|
@ -210,310 +210,6 @@ _clutter_util_rectangle_intersection (const cairo_rectangle_int_t *src1,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float
|
|
||||||
_clutter_util_matrix_determinant (const ClutterMatrix *matrix)
|
|
||||||
{
|
|
||||||
return matrix->xw * matrix->yz * matrix->zy * matrix->wz
|
|
||||||
- matrix->xz * matrix->yw * matrix->zy * matrix->wz
|
|
||||||
- matrix->xw * matrix->yy * matrix->zz * matrix->wz
|
|
||||||
+ matrix->xy * matrix->yw * matrix->zz * matrix->wz
|
|
||||||
+ matrix->xz * matrix->yy * matrix->zw * matrix->wz
|
|
||||||
- matrix->xy * matrix->yz * matrix->zw * matrix->wz
|
|
||||||
- matrix->xw * matrix->yz * matrix->zx * matrix->wy
|
|
||||||
+ matrix->xz * matrix->yw * matrix->zx * matrix->wy
|
|
||||||
+ matrix->xw * matrix->yx * matrix->zz * matrix->wy
|
|
||||||
- matrix->xx * matrix->yw * matrix->zz * matrix->wy
|
|
||||||
- matrix->xz * matrix->yx * matrix->zw * matrix->wy
|
|
||||||
+ matrix->xx * matrix->yz * matrix->zw * matrix->wy
|
|
||||||
+ matrix->xw * matrix->yy * matrix->zx * matrix->wz
|
|
||||||
- matrix->xy * matrix->yw * matrix->zx * matrix->wz
|
|
||||||
- matrix->xw * matrix->yx * matrix->zy * matrix->wz
|
|
||||||
+ matrix->xx * matrix->yw * matrix->zy * matrix->wz
|
|
||||||
+ matrix->xy * matrix->yx * matrix->zw * matrix->wz
|
|
||||||
- matrix->xx * matrix->yy * matrix->zw * matrix->wz
|
|
||||||
- matrix->xz * matrix->yy * matrix->zx * matrix->ww
|
|
||||||
+ matrix->xy * matrix->yz * matrix->zx * matrix->ww
|
|
||||||
+ matrix->xz * matrix->yx * matrix->zy * matrix->ww
|
|
||||||
- matrix->xx * matrix->yz * matrix->zy * matrix->ww
|
|
||||||
- matrix->xy * matrix->yx * matrix->zz * matrix->ww
|
|
||||||
+ matrix->xx * matrix->yy * matrix->zz * matrix->ww;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_clutter_util_matrix_transpose_vector4_transform (const ClutterMatrix *matrix,
|
|
||||||
const ClutterVertex4 *point,
|
|
||||||
ClutterVertex4 *res)
|
|
||||||
{
|
|
||||||
res->x = matrix->xx * point->x
|
|
||||||
+ matrix->xy * point->y
|
|
||||||
+ matrix->xz * point->z
|
|
||||||
+ matrix->xw * point->w;
|
|
||||||
|
|
||||||
res->y = matrix->yx * point->x
|
|
||||||
+ matrix->yy * point->y
|
|
||||||
+ matrix->yz * point->z
|
|
||||||
+ matrix->yw * point->w;
|
|
||||||
|
|
||||||
res->z = matrix->zx * point->x
|
|
||||||
+ matrix->zy * point->y
|
|
||||||
+ matrix->zz * point->z
|
|
||||||
+ matrix->zw * point->w;
|
|
||||||
|
|
||||||
res->w = matrix->wz * point->x
|
|
||||||
+ matrix->wy * point->w
|
|
||||||
+ matrix->wz * point->z
|
|
||||||
+ matrix->ww * point->w;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
_clutter_util_matrix_skew_xy (ClutterMatrix *matrix,
|
|
||||||
float factor)
|
|
||||||
{
|
|
||||||
matrix->yx += matrix->xx * factor;
|
|
||||||
matrix->yy += matrix->xy * factor;
|
|
||||||
matrix->yz += matrix->xz * factor;
|
|
||||||
matrix->yw += matrix->xw * factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
_clutter_util_matrix_skew_xz (ClutterMatrix *matrix,
|
|
||||||
float factor)
|
|
||||||
{
|
|
||||||
matrix->zx += matrix->xx * factor;
|
|
||||||
matrix->zy += matrix->xy * factor;
|
|
||||||
matrix->zz += matrix->xz * factor;
|
|
||||||
matrix->zw += matrix->xw * factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
_clutter_util_matrix_skew_yz (ClutterMatrix *matrix,
|
|
||||||
float factor)
|
|
||||||
{
|
|
||||||
matrix->zx += matrix->yx * factor;
|
|
||||||
matrix->zy += matrix->yy * factor;
|
|
||||||
matrix->zz += matrix->yz * factor;
|
|
||||||
matrix->zw += matrix->yw * factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_clutter_util_vertex_combine (const graphene_point3d_t *a,
|
|
||||||
const graphene_point3d_t *b,
|
|
||||||
double ascl,
|
|
||||||
double bscl,
|
|
||||||
graphene_point3d_t *res)
|
|
||||||
{
|
|
||||||
res->x = (ascl * a->x) + (bscl * b->x);
|
|
||||||
res->y = (ascl * a->y) + (bscl * b->y);
|
|
||||||
res->z = (ascl * a->z) + (bscl * b->z);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
_clutter_util_vertex4_interpolate (const ClutterVertex4 *a,
|
|
||||||
const ClutterVertex4 *b,
|
|
||||||
double progress,
|
|
||||||
ClutterVertex4 *res)
|
|
||||||
{
|
|
||||||
res->x = a->x + (b->x - a->x) * progress;
|
|
||||||
res->y = a->y + (b->y - a->y) * progress;
|
|
||||||
res->z = a->z + (b->z - a->z) * progress;
|
|
||||||
res->w = a->w + (b->w - a->w) * progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*< private >
|
|
||||||
* clutter_util_matrix_decompose:
|
|
||||||
* @src: the matrix to decompose
|
|
||||||
* @scale_p: (out caller-allocates): return location for a vertex containing
|
|
||||||
* the scaling factors
|
|
||||||
* @shear_p: (out) (array length=3): return location for an array of 3
|
|
||||||
* elements containing the skew factors (XY, XZ, and YZ respectively)
|
|
||||||
* @rotate_p: (out caller-allocates): return location for a vertex containing
|
|
||||||
* the Euler angles
|
|
||||||
* @translate_p: (out caller-allocates): return location for a vertex
|
|
||||||
* containing the translation vector
|
|
||||||
* @perspective_p: (out caller-allocates: return location for a 4D vertex
|
|
||||||
* containing the perspective
|
|
||||||
*
|
|
||||||
* Decomposes a #ClutterMatrix into the transformations that compose it.
|
|
||||||
*
|
|
||||||
* This code is based on the matrix decomposition algorithm as published in
|
|
||||||
* the CSS Transforms specification by the W3C CSS working group, available
|
|
||||||
* at http://www.w3.org/TR/css3-transforms/.
|
|
||||||
*
|
|
||||||
* The algorithm, in turn, is based on the "unmatrix" method published in
|
|
||||||
* "Graphics Gems II, edited by Jim Arvo", which is available at:
|
|
||||||
* http://tog.acm.org/resources/GraphicsGems/gemsii/unmatrix.c
|
|
||||||
*
|
|
||||||
* Return value: %TRUE if the decomposition was successful, and %FALSE
|
|
||||||
* if the matrix is singular
|
|
||||||
*/
|
|
||||||
gboolean
|
|
||||||
_clutter_util_matrix_decompose (const ClutterMatrix *src,
|
|
||||||
graphene_point3d_t *scale_p,
|
|
||||||
float shear_p[3],
|
|
||||||
graphene_point3d_t *rotate_p,
|
|
||||||
graphene_point3d_t *translate_p,
|
|
||||||
ClutterVertex4 *perspective_p)
|
|
||||||
{
|
|
||||||
CoglMatrix matrix = *src;
|
|
||||||
CoglMatrix perspective;
|
|
||||||
ClutterVertex4 vertex_tmp;
|
|
||||||
graphene_point3d_t row[3], pdum;
|
|
||||||
int i, j;
|
|
||||||
|
|
||||||
#define XY_SHEAR 0
|
|
||||||
#define XZ_SHEAR 1
|
|
||||||
#define YZ_SHEAR 2
|
|
||||||
#define MAT(m,r,c) ((float *)(m))[(c) * 4 + (r)]
|
|
||||||
|
|
||||||
/* normalize the matrix */
|
|
||||||
if (matrix.ww == 0.f)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
for (i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
for (j = 0; j < 4; j++)
|
|
||||||
{
|
|
||||||
MAT (&matrix, j, i) /= MAT (&matrix, 3, 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* perspective is used to solve for perspective, but it also provides
|
|
||||||
* an easy way to test for singularity of the upper 3x3 component
|
|
||||||
*/
|
|
||||||
perspective = matrix;
|
|
||||||
|
|
||||||
/* transpose */
|
|
||||||
MAT (&perspective, 3, 0) = 0.f;
|
|
||||||
MAT (&perspective, 3, 1) = 0.f;
|
|
||||||
MAT (&perspective, 3, 2) = 0.f;
|
|
||||||
MAT (&perspective, 3, 3) = 1.f;
|
|
||||||
|
|
||||||
if (_clutter_util_matrix_determinant (&perspective) == 0.f)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
if (MAT (&matrix, 3, 0) != 0.f ||
|
|
||||||
MAT (&matrix, 3, 1) != 0.f ||
|
|
||||||
MAT (&matrix, 3, 2) != 0.f)
|
|
||||||
{
|
|
||||||
CoglMatrix perspective_inv;
|
|
||||||
ClutterVertex4 p;
|
|
||||||
|
|
||||||
vertex_tmp.x = MAT (&matrix, 3, 0);
|
|
||||||
vertex_tmp.y = MAT (&matrix, 3, 1);
|
|
||||||
vertex_tmp.z = MAT (&matrix, 3, 2);
|
|
||||||
vertex_tmp.w = MAT (&matrix, 3, 3);
|
|
||||||
|
|
||||||
/* solve the equation by inverting perspective... */
|
|
||||||
cogl_matrix_get_inverse (&perspective, &perspective_inv);
|
|
||||||
|
|
||||||
/* ... and multiplying vertex_tmp by the inverse */
|
|
||||||
_clutter_util_matrix_transpose_vector4_transform (&perspective_inv,
|
|
||||||
&vertex_tmp,
|
|
||||||
&p);
|
|
||||||
|
|
||||||
*perspective_p = p;
|
|
||||||
|
|
||||||
/* clear the perspective part */
|
|
||||||
MAT (&matrix, 3, 0) = 0.0f;
|
|
||||||
MAT (&matrix, 3, 1) = 0.0f;
|
|
||||||
MAT (&matrix, 3, 2) = 0.0f;
|
|
||||||
MAT (&matrix, 3, 3) = 1.0f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* no perspective */
|
|
||||||
perspective_p->x = 0.0f;
|
|
||||||
perspective_p->y = 0.0f;
|
|
||||||
perspective_p->z = 0.0f;
|
|
||||||
perspective_p->w = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* translation */
|
|
||||||
translate_p->x = MAT (&matrix, 0, 3);
|
|
||||||
MAT (&matrix, 0, 3) = 0.f;
|
|
||||||
translate_p->y = MAT (&matrix, 1, 3);
|
|
||||||
MAT (&matrix, 1, 3) = 0.f;
|
|
||||||
translate_p->z = MAT (&matrix, 2, 3);
|
|
||||||
MAT (&matrix, 2, 3) = 0.f;
|
|
||||||
|
|
||||||
/* scale and shear; we split the upper 3x3 matrix into rows */
|
|
||||||
for (i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
row[i].x = MAT (&matrix, i, 0);
|
|
||||||
row[i].y = MAT (&matrix, i, 1);
|
|
||||||
row[i].z = MAT (&matrix, i, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* compute scale.x and normalize the first row */
|
|
||||||
scale_p->x = graphene_point3d_length (&row[0]);
|
|
||||||
graphene_point3d_normalize (&row[0], &row[0]);
|
|
||||||
|
|
||||||
/* compute XY shear and make the second row orthogonal to the first */
|
|
||||||
shear_p[XY_SHEAR] = graphene_point3d_dot (&row[0], &row[1]);
|
|
||||||
_clutter_util_vertex_combine (&row[1], &row[0],
|
|
||||||
1.0, -shear_p[XY_SHEAR],
|
|
||||||
&row[1]);
|
|
||||||
|
|
||||||
/* compute the Y scale and normalize the second row */
|
|
||||||
scale_p->y = graphene_point3d_length (&row[1]);
|
|
||||||
graphene_point3d_normalize (&row[1], &row[1]);
|
|
||||||
shear_p[XY_SHEAR] /= scale_p->y;
|
|
||||||
|
|
||||||
/* compute XZ and YZ shears, orthogonalize the third row */
|
|
||||||
shear_p[XZ_SHEAR] = graphene_point3d_dot (&row[0], &row[2]);
|
|
||||||
_clutter_util_vertex_combine (&row[2], &row[0],
|
|
||||||
1.0, -shear_p[XZ_SHEAR],
|
|
||||||
&row[2]);
|
|
||||||
|
|
||||||
shear_p[YZ_SHEAR] = graphene_point3d_dot (&row[1], &row[2]);
|
|
||||||
_clutter_util_vertex_combine (&row[2], &row[1],
|
|
||||||
1.0, -shear_p[YZ_SHEAR],
|
|
||||||
&row[2]);
|
|
||||||
|
|
||||||
/* get the Z scale and normalize the third row*/
|
|
||||||
scale_p->z = graphene_point3d_length (&row[2]);
|
|
||||||
graphene_point3d_normalize (&row[2], &row[2]);
|
|
||||||
shear_p[XZ_SHEAR] /= scale_p->z;
|
|
||||||
shear_p[YZ_SHEAR] /= scale_p->z;
|
|
||||||
|
|
||||||
/* at this point, the matrix (inside row[]) is orthonormal.
|
|
||||||
* check for a coordinate system flip; if the determinant
|
|
||||||
* is -1, then negate the matrix and scaling factors
|
|
||||||
*/
|
|
||||||
graphene_point3d_cross (&row[1], &row[2], &pdum);
|
|
||||||
if (graphene_point3d_dot (&row[0], &pdum) < 0.f)
|
|
||||||
{
|
|
||||||
scale_p->x *= -1.f;
|
|
||||||
|
|
||||||
for (i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
row[i].x *= -1.f;
|
|
||||||
row[i].y *= -1.f;
|
|
||||||
row[i].z *= -1.f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* now get the rotations out */
|
|
||||||
rotate_p->y = asinf (-row[0].z);
|
|
||||||
if (cosf (rotate_p->y) != 0.f)
|
|
||||||
{
|
|
||||||
rotate_p->x = atan2f (row[1].z, row[2].z);
|
|
||||||
rotate_p->z = atan2f (row[0].y, row[0].x);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rotate_p->x = atan2f (-row[2].x, row[1].y);
|
|
||||||
rotate_p->z = 0.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef XY_SHEAR
|
|
||||||
#undef XZ_SHEAR
|
|
||||||
#undef YZ_SHEAR
|
|
||||||
#undef MAT
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
GType value_type;
|
GType value_type;
|
||||||
|
Loading…
Reference in New Issue
Block a user