Question Comment détectez-vous où deux segments de ligne se croisent? [fermé]


Comment déterminer si deux droites se croisent et, le cas échéant, à quel point x, y?


487
2018-02-18 22:47


origine


Réponses:


Il y a une bonne approche de ce problème qui utilise des produits vectoriels croisés. Définir le vecteur croisé bidimensionnel v×w être vXwy-vywX.

Supposons que les deux segments de ligne vont de p à p+r et de q à q+s. Alors n'importe quel point sur la première ligne est représentable comme p+tr (pour un paramètre scalairet) et tout point de la deuxième ligne comme q+tus (pour un paramètre scalairetu).

Two line segments intersecting

Les deux lignes se croisent si on peut trouver t et tu tel que:

p + tr = q + tus

Formulae for the point of intersection

Croiser les deux côtés avec s, obtenir

(p + tr× × × s = (q + tus× × × s

Et depuis s×s = 0, cela signifie

t(r × s) = (q - p× × × s

Et donc, résoudre pour t:

t = (q - p× × × s / (r × s)

De la même manière, nous pouvons résoudre pour tu:

(p + tr× × × r = (q + tus× × × r

tu(s × r) = (p - q× × × r

tu = (p - q× × × r / (s × r)

Pour réduire le nombre d'étapes de calcul, il est pratique de réécrire ceci (en se souvenant s×r = -r×s):

tu = (q - p× × × r / (r × s)

Maintenant, il y a quatre cas:

  1. Si r×s= 0 et (q-p× × ×r= 0, alors les deux lignes sont colinéaires.

    Dans ce cas, exprimez les extrémités du second segment (q et q+s) en termes d’équation du premier segment de ligne (p + t  r):

    t0 = (q - p) ·r / (r·r)

    t1 = (q + s - p) ·r / (r·r) = t0 + s·r / (r·r)

    Si l'intervalle entre t0 et t1 intersecte l'intervalle [0, 1] alors les segments de ligne sont colinéaires et se chevauchent; sinon ils sont colinéaires et disjoints.

    Notez que si s et r pointer dans des directions opposées, puis s·r <0 et donc l'intervalle à vérifier est [t1, t0] plutôt que [t0, t1]

  2. Si r×s= 0 et (q-p× × ×r≠ 0, alors les deux droites sont parallèles et non croisées.

  3. Si r×s≠ 0 et 0 ≤t≤ 1 et 0 ≤tu≤ 1, les deux segments se rencontrent au point p + tr = q + tus.

  4. Sinon, les deux segments ne sont pas parallèles mais ne se croisent pas.

Crédit: cette méthode est la spécialisation en 2 dimensions de l'algorithme d'intersection de lignes 3D de l'article "Intersection de deux lignes dans trois espaces" par Ronald Goldman, publié dans Gems Graphiques, page 304. En trois dimensions, le cas habituel est que les droites sont obliques (ni parallèles ni croisées) auquel cas la méthode donne les points d'approche les plus proches des deux droites.


612
2018-02-19 13:24



FWIW, la fonction suivante (en C) détecte les intersections de lignes et détermine le point d'intersection. Il est basé sur un algorithme chez Andre LeMothe "Astuces des gourous de la programmation du jeu Windows»Ce n'est pas différent de certains algorithmes dans d'autres réponses (par exemple Gareth's) LeMothe utilise alors la règle de Cramer (ne me demandez pas) pour résoudre les équations elles-mêmes.

Je peux attester que cela fonctionne dans mon clone d'astéroïdes faible, et semble traiter correctement les cas marginaux décrits dans d'autres réponses par Elemental, Dan et Wodzu. C'est aussi probablement plus rapide que le code posté par KingNestor parce que c'est une multiplication et une division, pas de racines carrées!

Je suppose qu'il y a un potentiel de division par zéro là-dedans, même si cela n'a pas été un problème dans mon cas. Assez facile à modifier pour éviter l'accident de toute façon.

// Returns 1 if the lines intersect, otherwise 0. In addition, if the lines 
// intersect the intersection point may be stored in the floats i_x and i_y.
char get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y, 
    float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
    float s1_x, s1_y, s2_x, s2_y;
    s1_x = p1_x - p0_x;     s1_y = p1_y - p0_y;
    s2_x = p3_x - p2_x;     s2_y = p3_y - p2_y;

    float s, t;
    s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
    t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);

    if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
    {
        // Collision detected
        if (i_x != NULL)
            *i_x = p0_x + (t * s1_x);
        if (i_y != NULL)
            *i_y = p0_y + (t * s1_y);
        return 1;
    }

    return 0; // No collision
}

BTW, je dois dire que dans le livre de LeMothe, bien qu'il semble apparemment avoir l'algorithme correct, l'exemple concret qu'il montre se branche sur les mauvais chiffres et fait des calculs faux. Par exemple:

(4 * (4 - 1) + 12 * (7 - 1)) / (17 * 4 + 12 * 10)

= 844 / 0,88

= 0.44

Cela m'a confondu pour heures. :(


213
2017-12-28 07:16



Le problème se réduit à cette question: Est-ce que deux lignes de A à B et de C à D se croisent? Ensuite, vous pouvez le demander quatre fois (entre la ligne et chacun des quatre côtés du rectangle).

Voici le calcul vectoriel pour le faire. Je suppose que la ligne de A à B est la ligne en question et la ligne de C à D est l'une des lignes rectangle. Ma notation est que Ax est la "coordonnée x de A" et Cy est la "coordonnée y de C." Et "*"signifie produit à points, par ex. A*B = Ax*Bx + Ay*By.

E = B-A = ( Bx-Ax, By-Ay )
F = D-C = ( Dx-Cx, Dy-Cy ) 
P = ( -Ey, Ex )
h = ( (A-C) * P ) / ( F * P )

Ce h nombre est la clé. Si h est entre 0 et 1, les lignes se croisent, sinon elles ne le font pas. Si F*P est nul, bien sûr vous ne pouvez pas faire le calcul, mais dans ce cas les lignes sont parallèles et ne se croisent donc que dans les cas les plus évidents.

Le point d'intersection exact est C + F*h.

Plus amusant:

Si h est exactement  0 ou 1 les lignes touchent à un point final. Vous pouvez considérer cela comme une "intersection" ou non comme vous le souhaitez.

Plus précisément, h est combien vous devez multiplier la longueur de la ligne afin de toucher exactement l'autre ligne.

Par conséquent, si h<0, cela signifie que la ligne rectangle est "derrière" la ligne donnée (avec "direction" étant "de A à B"), et si h>1 la ligne rectangle est "devant" de la ligne donnée.

Dérivation:

A et C sont des vecteurs qui pointent vers le début de la ligne; E et F sont les vecteurs à partir des extrémités de A et C qui forment la ligne.

Pour deux lignes non parallèles dans le plan, il doit y avoir exactement une paire de scalaires g et h de telle sorte que cette équation contient:

A + E*g = C + F*h

Pourquoi? Parce que deux lignes non parallèles doivent se croiser, ce qui signifie que vous pouvez mettre à l'échelle les deux lignes d'une certaine quantité et vous toucher les unes les autres.

(Au début, cela ressemble à une équation unique avec deux inconnues!  Mais ce n'est pas quand vous considérez que c'est une équation vectorielle 2D, ce qui signifie que c'est vraiment une paire d'équations dans x et y.)

Nous devons éliminer l'une de ces variables. Un moyen facile est de faire le E terme zéro. Pour ce faire, prenez le point-produit des deux côtés de l'équation en utilisant un vecteur qui point à zéro avec E. Ce vecteur que j'ai appelé P ci-dessus, et j'ai fait la transformation évidente de E.

Vous avez maintenant:

A*P = C*P + F*P*h
(A-C)*P = (F*P)*h
( (A-C)*P ) / (F*P) = h

61
2018-02-18 23:09



J'ai essayé de mettre en application l'algorithme si élégamment décrit par Jason ci-dessus; malheureusement, tout en travaillant les mathématiques dans le débogage, j'ai trouvé de nombreux cas pour lesquels cela ne fonctionne pas.

Considérons par exemple les points A (10,10) B (20,20) C (10,1) D (1,10) donne h = 0,5 et pourtant il est clair par examen que ces segments ne sont pas-près de chaque autre.

La représentation graphique montre clairement que 0 <h <1 critère seulement indique que le point d’interception se trouve sur le CD s’il existe, mais ne dit rien du point de savoir si ce point se trouve sur AB. Pour vous assurer qu'il existe un point de croisement, vous devez effectuer le calcul symétrique pour la variable g et l'exigence d'interception est la suivante: 0 <g <1 ET 0 <h <1


45
2017-07-29 16:05



Voici une amélioration de la réponse de Gavin. La solution de marcp est également similaire, mais ni ne retarde la division.

Cela s'avère en fait être une application pratique de la réponse de Gareth Rees, car l'équivalent en produit croisé en 2D est le produit perp-dot, ce que ce code utilise trois fois. Le passage en 3D et l'utilisation du produit croisé, en interpolant à la fois s et t, permettent d'obtenir les deux points les plus proches entre les lignes en 3D. En tout cas, la solution 2D:

int get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y, 
    float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
    float s02_x, s02_y, s10_x, s10_y, s32_x, s32_y, s_numer, t_numer, denom, t;
    s10_x = p1_x - p0_x;
    s10_y = p1_y - p0_y;
    s32_x = p3_x - p2_x;
    s32_y = p3_y - p2_y;

    denom = s10_x * s32_y - s32_x * s10_y;
    if (denom == 0)
        return 0; // Collinear
    bool denomPositive = denom > 0;

    s02_x = p0_x - p2_x;
    s02_y = p0_y - p2_y;
    s_numer = s10_x * s02_y - s10_y * s02_x;
    if ((s_numer < 0) == denomPositive)
        return 0; // No collision

    t_numer = s32_x * s02_y - s32_y * s02_x;
    if ((t_numer < 0) == denomPositive)
        return 0; // No collision

    if (((s_numer > denom) == denomPositive) || ((t_numer > denom) == denomPositive))
        return 0; // No collision
    // Collision detected
    t = t_numer / denom;
    if (i_x != NULL)
        *i_x = p0_x + (t * s10_x);
    if (i_y != NULL)
        *i_y = p0_y + (t * s10_y);

    return 1;
}

Fondamentalement, il reporte la division jusqu'au dernier moment et déplace la plupart des tests jusqu'à ce que certains calculs soient effectués, ajoutant ainsi des sorties anticipées. Enfin, elle évite également la division par zéro lorsque les lignes sont parallèles.

Vous pouvez également envisager d'utiliser un test epsilon plutôt qu'une comparaison avec zéro. Les lignes extrêmement proches du parallèle peuvent produire des résultats légèrement décalés. Ce n'est pas un bug, c'est une limitation avec les maths en virgule flottante.


43
2018-02-10 06:56



Question C: Comment détectez-vous si deux segments de ligne se croisent ou non?

J'ai cherché le même sujet et je n'étais pas satisfait des réponses. J'ai donc écrit un article qui explique très détaillé comment vérifier si deux segments de ligne se croisent avec beaucoup d'images. Il existe un code Java complet (et testé).

Voici l'article, recadré aux parties les plus importantes:

L'algorithme, qui vérifie si le segment de droite a une intersection avec le segment de ligne b, ressemble à ceci:

Enter image description here

Que sont les boîtes englobantes? Voici deux boîtes englobantes de deux segments de ligne:

enter image description here

Si les deux boîtes englobantes ont une intersection, vous déplacez le segment de ligne a pour qu’un point soit à (0 | 0). Maintenant vous avez une ligne à travers l'origine définie par a. Déplacez maintenant le segment de droite b de la même manière et vérifiez si les nouveaux points du segment de ligne b se trouvent sur des côtés différents de la ligne a. Si c'est le cas, vérifiez l'inverse. Si c'est également le cas, les segments de droite se croisent. Sinon, ils ne se croisent pas.

Question A: Où se croisent deux segments de ligne?

Vous savez que deux segments de ligne a et b se croisent. Si vous ne le savez pas, vérifiez-le avec les outils que je vous ai donnés dans la "Question C".

Maintenant, vous pouvez passer par certains cas et obtenir la solution avec les mathématiques de 7e année (voir code et exemple interactif).

Question B: Comment détectez-vous si deux lignes se croisent ou non?

Disons votre point A = (x1, y1), point B = (x2, y2), C = (x_3, y_3), D = (x_4, y_4). Votre première ligne est définie par AB (avec A! = B) et votre seconde par CD (avec C! = D).

function doLinesIntersect(AB, CD) {
    if (x1 == x2) {
        return !(x3 == x4 && x1 != x3);
    } else if (x3 == x4) {
        return true;
    } else {
        // Both lines are not parallel to the y-axis
        m1 = (y1-y2)/(x1-x2);
        m2 = (y3-y4)/(x3-x4);
        return m1 != m2;
    }
}

Question D: Où se croisent deux lignes?

Vérifiez avec la question B si elles se croisent.

Les lignes a et b sont définies par deux points pour chaque ligne. Vous pouvez fondamentalement appliquer la même logique que celle utilisée dans la question A.


36
2018-02-21 11:31



La réponse une fois acceptée ici est incorrecte (elle n’a pas été acceptée depuis le début de la journée). Il n'élimine pas correctement toutes les non-intersections. Trivialement cela peut sembler fonctionner mais cela peut échouer, surtout dans le cas où 0 et 1 sont considérés comme valides pour h.

Considérons le cas suivant:

Lignes à (4,1) - (5,1) et (0,0) - (0,2)

Ce sont des lignes perpendiculaires qui ne se chevauchent pas clairement.

A = (4,1)
B = (5,1)
C = (0,0)
D = (0,2)
E = (5,1) - (4,1) = (- 1,0)
F = (0,2) - (0,0) = (0, -2)
P = (0,1)
h = ((4,1) - (0,0)) point (0,1) / ((0, -2) point (0,1)) = 0

Selon la réponse ci-dessus, ces deux segments de ligne se rencontrent à un point final (valeurs de 0 et 1). Ce point final serait:

(0,0) + (0, -2) * 0 = (0,0)

Donc, apparemment, les deux segments de ligne se rencontrent à (0,0), ce qui est en ligne CD, mais pas sur la ligne AB. Donc, qu'est-ce qui ne va pas? La réponse est que les valeurs de 0 et de 1 ne sont pas valables et que parfois seulement HAPPEN est capable de prédire correctement l’intersection des extrémités. Lorsque l'extension d'une ligne (mais pas de l'autre) rencontre le segment de ligne, l'algorithme prédit une intersection de segments de ligne, mais ce n'est pas correct. J'imagine qu'en testant avec AB vs CD et en testant aussi avec CD vs AB, ce problème serait éliminé. Seulement si les deux tombent entre 0 et 1 inclusivement, peut-on dire qu'ils se croisent.

Je recommande d'utiliser la méthode de produit vectoriel croisé si vous devez prédire les points de fin.

-Dan


20
2018-04-04 00:26



Version Python de la réponse d'iMalc:

def find_intersection( p0, p1, p2, p3 ) :

    s10_x = p1[0] - p0[0]
    s10_y = p1[1] - p0[1]
    s32_x = p3[0] - p2[0]
    s32_y = p3[1] - p2[1]

    denom = s10_x * s32_y - s32_x * s10_y

    if denom == 0 : return None # collinear

    denom_is_positive = denom > 0

    s02_x = p0[0] - p2[0]
    s02_y = p0[1] - p2[1]

    s_numer = s10_x * s02_y - s10_y * s02_x

    if (s_numer < 0) == denom_is_positive : return None # no collision

    t_numer = s32_x * s02_y - s32_y * s02_x

    if (t_numer < 0) == denom_is_positive : return None # no collision

    if (s_numer > denom) == denom_is_positive or (t_numer > denom) == denom_is_positive : return None # no collision


    # collision detected

    t = t_numer / denom

    intersection_point = [ p0[0] + (t * s10_x), p0[1] + (t * s10_y) ]


    return intersection_point

12
2017-10-23 19:42



Trouver l'intersection correcte de deux segments de ligne est une tâche non triviale avec beaucoup de cas de bords. Voici une solution bien documentée, fonctionnelle et testée en Java.

En substance, il y a trois choses qui peuvent arriver quand on trouve l'intersection de deux segments de ligne:

  1. Les segments ne se croisent pas

  2. Il y a un point d'intersection unique

  3. L'intersection est un autre segment

REMARQUE: Dans le code, je suppose qu'un segment de ligne (x1, y1), (x2, y2) avec x1 = x2 et y1 = y2 est un segment de ligne valide. D'un point de vue mathématique, un segment de ligne est constitué de points distincts, mais j'autorise la complétude des segments.

Le code est pris de mon github repo

/**
 * This snippet finds the intersection of two line segments.
 * The intersection may either be empty, a single point or the
 * intersection is a subsegment there's an overlap.
 */

import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.lang.Math.min;

import java.util.ArrayList;
import java.util.List;

public class LineSegmentLineSegmentIntersection {

  // Small epsilon used for double value comparison.
  private static final double EPS = 1e-5;

  // 2D Point class.
  public static class Pt {
    double x, y;
    public Pt(double x, double y) {
      this.x = x; 
      this.y = y;
    }
    public boolean equals(Pt pt) {
      return abs(x - pt.x) < EPS && abs(y - pt.y) < EPS;
    }
  }

  // Finds the orientation of point 'c' relative to the line segment (a, b)
  // Returns  0 if all three points are collinear.
  // Returns -1 if 'c' is clockwise to segment (a, b), i.e right of line formed by the segment.
  // Returns +1 if 'c' is counter clockwise to segment (a, b), i.e left of line
  // formed by the segment.
  public static int orientation(Pt a, Pt b, Pt c) {
    double value = (b.y - a.y) * (c.x - b.x) - 
                   (b.x - a.x) * (c.y - b.y);
    if (abs(value) < EPS) return 0;
    return (value > 0) ? -1 : +1;
  }

  // Tests whether point 'c' is on the line segment (a, b).
  // Ensure first that point c is collinear to segment (a, b) and
  // then check whether c is within the rectangle formed by (a, b)
  public static boolean pointOnLine(Pt a, Pt b, Pt c) {
    return orientation(a, b, c) == 0 && 
           min(a.x, b.x) <= c.x && c.x <= max(a.x, b.x) && 
           min(a.y, b.y) <= c.y && c.y <= max(a.y, b.y);
  }

  // Determines whether two segments intersect.
  public static boolean segmentsIntersect(Pt p1, Pt p2, Pt p3, Pt p4) {

    // Get the orientation of points p3 and p4 in relation
    // to the line segment (p1, p2)
    int o1 = orientation(p1, p2, p3);
    int o2 = orientation(p1, p2, p4);
    int o3 = orientation(p3, p4, p1);
    int o4 = orientation(p3, p4, p2);

    // If the points p1, p2 are on opposite sides of the infinite
    // line formed by (p3, p4) and conversly p3, p4 are on opposite
    // sides of the infinite line formed by (p1, p2) then there is
    // an intersection.
    if (o1 != o2 && o3 != o4) return true;

    // Collinear special cases (perhaps these if checks can be simplified?)
    if (o1 == 0 && pointOnLine(p1, p2, p3)) return true;
    if (o2 == 0 && pointOnLine(p1, p2, p4)) return true;
    if (o3 == 0 && pointOnLine(p3, p4, p1)) return true;
    if (o4 == 0 && pointOnLine(p3, p4, p2)) return true;

    return false;
  }

  public static List<Pt> getCommonEndpoints(Pt p1, Pt p2, Pt p3, Pt p4) {

    List<Pt> points = new ArrayList<>();

    if (p1.equals(p3)) {
      points.add(p1);
      if (p2.equals(p4)) points.add(p2);

    } else if (p1.equals(p4)) {
      points.add(p1);
      if (p2.equals(p3)) points.add(p2);

    } else if (p2.equals(p3)) {
      points.add(p2);
      if (p1.equals(p4)) points.add(p1);

    } else if (p2.equals(p4)) {
      points.add(p2);
      if (p1.equals(p3)) points.add(p1);
    }

    return points;
  }

  // Finds the intersection point(s) of two line segments. Unlike regular line 
  // segments, segments which are points (x1 = x2 and y1 = y2) are allowed.
  public static Pt[] lineSegmentLineSegmentIntersection(Pt p1, Pt p2, Pt p3, Pt p4) {

    // No intersection.
    if (!segmentsIntersect(p1, p2, p3, p4)) return new Pt[]{};

    // Both segments are a single point.
    if (p1.equals(p2) && p2.equals(p3) && p3.equals(p4))
      return new Pt[]{p1};

    List<Pt> endpoints = getCommonEndpoints(p1, p2, p3, p4);
    int n = endpoints.size();

    // One of the line segments is an intersecting single point.
    // NOTE: checking only n == 1 is insufficient to return early
    // because the solution might be a sub segment.
    boolean singleton = p1.equals(p2) || p3.equals(p4);
    if (n == 1 && singleton) return new Pt[]{endpoints.get(0)};

    // Segments are equal.
    if (n == 2) return new Pt[]{endpoints.get(0), endpoints.get(1)};

    boolean collinearSegments = (orientation(p1, p2, p3) == 0) && 
                                (orientation(p1, p2, p4) == 0);

    // The intersection will be a sub-segment of the two
    // segments since they overlap each other.
    if (collinearSegments) {

      // Segment #2 is enclosed in segment #1
      if (pointOnLine(p1, p2, p3) && pointOnLine(p1, p2, p4))
        return new Pt[]{p3, p4};

      // Segment #1 is enclosed in segment #2
      if (pointOnLine(p3, p4, p1) && pointOnLine(p3, p4, p2))
        return new Pt[]{p1, p2};

      // The subsegment is part of segment #1 and part of segment #2.
      // Find the middle points which correspond to this segment.
      Pt midPoint1 = pointOnLine(p1, p2, p3) ? p3 : p4;
      Pt midPoint2 = pointOnLine(p3, p4, p1) ? p1 : p2;

      // There is actually only one middle point!
      if (midPoint1.equals(midPoint2)) return new Pt[]{midPoint1};

      return new Pt[]{midPoint1, midPoint2};
    }

    /* Beyond this point there is a unique intersection point. */

    // Segment #1 is a vertical line.
    if (abs(p1.x - p2.x) < EPS) {
      double m = (p4.y - p3.y) / (p4.x - p3.x);
      double b = p3.y - m * p3.x;
      return new Pt[]{new Pt(p1.x, m * p1.x + b)};
    }

    // Segment #2 is a vertical line.
    if (abs(p3.x - p4.x) < EPS) {
      double m = (p2.y - p1.y) / (p2.x - p1.x);
      double b = p1.y - m * p1.x;
      return new Pt[]{new Pt(p3.x, m * p3.x + b)};
    }

    double m1 = (p2.y - p1.y) / (p2.x - p1.x);
    double m2 = (p4.y - p3.y) / (p4.x - p3.x);
    double b1 = p1.y - m1 * p1.x;
    double b2 = p3.y - m2 * p3.x;
    double x = (b2 - b1) / (m1 - m2);
    double y = (m1 * b2 - m2 * b1) / (m1 - m2);

    return new Pt[]{new Pt(x, y)};
  }

}

Voici un exemple d'utilisation simple:

  public static void main(String[] args) {

    // Segment #1 is (p1, p2), segment #2 is (p3, p4)
    Pt p1, p2, p3, p4;

    p1 = new Pt(-2, 4); p2 = new Pt(3, 3);
    p3 = new Pt(0, 0);  p4 = new Pt(2, 4);
    Pt[] points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point = points[0];

    // Prints: (1.636, 3.273)
    System.out.printf("(%.3f, %.3f)\n", point.x, point.y);

    p1 = new Pt(-10, 0); p2 = new Pt(+10, 0);
    p3 = new Pt(-5, 0);  p4 = new Pt(+5, 0);
    points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point1 = points[0], point2 = points[1];

    // Prints: (-5.000, 0.000) (5.000, 0.000)
    System.out.printf("(%.3f, %.3f) (%.3f, %.3f)\n", point1.x, point1.y, point2.x, point2.y);
  }

8
2018-06-30 01:41