mirror of https://github.com/CGAL/cgal
597 lines
19 KiB
C++
597 lines
19 KiB
C++
// Copyright (c) 1997 Utrecht University (The Netherlands),
|
|
// ETH Zurich (Switzerland), Freie Universitaet Berlin (Germany),
|
|
// INRIA Sophia-Antipolis (France), Martin-Luther-University Halle-Wittenberg
|
|
// (Germany), Max-Planck-Institute Saarbruecken (Germany), RISC Linz (Austria),
|
|
// and Tel-Aviv University (Israel). All rights reserved.
|
|
//
|
|
// This file is part of CGAL (www.cgal.org); you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public License as
|
|
// published by the Free Software Foundation; version 2.1 of the License.
|
|
// See the file LICENSE.LGPL distributed with CGAL.
|
|
//
|
|
// Licensees holding a valid commercial license may use this file in
|
|
// accordance with the commercial license agreement provided with the software.
|
|
//
|
|
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
|
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
//
|
|
// $Source$
|
|
// $Revision$ $Date$
|
|
// $Name$
|
|
//
|
|
// Author(s) : Wieger Wesselink <wieger@cs.uu.nl>
|
|
|
|
#ifndef CGAL_SIMPLICITY_TEST_H
|
|
#define CGAL_SIMPLICITY_TEST_H
|
|
|
|
#error "FILE IS OBSOLETE AND SHOULD NOT GET INCLUDED"
|
|
// ==== This file is now OBSOLETE =====
|
|
// ==== The functionality has been placed in Polygon_2_simplicity.h ====
|
|
|
|
#include <cstdlib>
|
|
#include <algorithm>
|
|
#include <set>
|
|
#include <vector>
|
|
#include "CGAL/polygon_assertions.h"
|
|
|
|
CGAL_BEGIN_NAMESPACE
|
|
//-----------------------------------------------------------------------//
|
|
// Simplicity_test_2
|
|
//-----------------------------------------------------------------------//
|
|
// The simplicity test is implemented as a class.
|
|
// The algorithm used is a sweep line algorithm. The sweep line is a
|
|
// horizontal line that sweeps from top (big y) to bottom.
|
|
// In the sweep status the edges are ordered from left (small) to right
|
|
// (big).
|
|
|
|
template <class ForwardIterator, class Traits>
|
|
class Simplicity_test_2 {
|
|
protected:
|
|
std::vector<ForwardIterator> d_index;
|
|
// the attribute d_index is just a mapping between the integers and the
|
|
// sequence of points
|
|
|
|
int d_eventpoint;
|
|
// the index of the current event point
|
|
// the current sweepline is the horizontal line through this point
|
|
|
|
const Traits& d_traits;
|
|
// the traits class for polygons
|
|
|
|
public:
|
|
typedef typename Traits::Point_2 Point_2;
|
|
|
|
Simplicity_test_2(const Traits& tr): d_traits(tr) {}
|
|
~Simplicity_test_2() {}
|
|
|
|
const Traits& traits() const { return d_traits; }
|
|
|
|
const Point_2& Vertex(int i) const { return *d_index[i]; }
|
|
int NumberOfVertices() const { return d_index.size(); }
|
|
|
|
const Point_2& EventPoint() const { return Vertex(d_eventpoint); }
|
|
// return the current event point
|
|
|
|
bool Test(ForwardIterator first, ForwardIterator last);
|
|
// tests if the polygon with points in the range [first,last) is simple
|
|
|
|
bool EdgesDoIntersect(int e1, int e2) const;
|
|
// tests if the edges e1 and e2 have an intersection
|
|
// N.B. the common vertex of two consecutive edges is not counted
|
|
// as an intersection!
|
|
|
|
bool VertexCompare(int i, int j) const;
|
|
// compares the (lexicographical) order of vertex(i) and vertex(j)
|
|
|
|
class VertexComp {
|
|
protected:
|
|
const Simplicity_test_2<ForwardIterator, Traits>* s;
|
|
public:
|
|
VertexComp() {}
|
|
VertexComp(
|
|
const Simplicity_test_2<ForwardIterator, Traits>* s0): s(s0)
|
|
{}
|
|
bool operator() (int i, int j) const { return s->VertexCompare(i,j); }
|
|
};
|
|
|
|
bool has_on_left_side(const Point_2& p,
|
|
const Point_2& q,
|
|
const Point_2& r ) const
|
|
// returns true if point p is left of the point w, where w is the leftmost
|
|
// intersection point of the horizontal line through p and the line
|
|
// segment qr
|
|
// N.B. if p lies on the segment qr, the result is indeterminate.
|
|
{
|
|
Comparison_result qr = d_traits.compare_y(q,r);
|
|
if (qr == EQUAL)
|
|
return (d_traits.compare_x(p,q) == SMALLER);
|
|
else
|
|
return ( d_traits.is_negative(d_traits.cross_product_2(p-q,r-q)) ==
|
|
(qr == SMALLER) );
|
|
}
|
|
|
|
bool has_y_overlap(const Point_2& p,
|
|
const Point_2& q,
|
|
const Point_2& r ) const
|
|
// returns true if the horizontal line through p intersects the segments qr
|
|
{
|
|
Comparison_result pq = d_traits.compare_y(p,q);
|
|
Comparison_result pr = d_traits.compare_y(p,r);
|
|
return (pq != pr) || (pq == EQUAL);
|
|
}
|
|
|
|
bool EdgeCompare(int e1, int e2) const;
|
|
// computes the order of two edges e1 and e2 on the current sweepline
|
|
|
|
bool edge_compare_consecutive(int e1, int e2) const;
|
|
// computes the order of two edges e1 and e2 that share a vertex
|
|
|
|
bool edge_compare_non_consecutive(int e1, int e2) const;
|
|
// computes the order of two edges e1 and e2 that do not share a vertex
|
|
|
|
bool consecutive_edges(int e1, int e2) const;
|
|
// true if the edges e1 and e2 are not equal but share a vertex
|
|
|
|
class EdgeComp {
|
|
protected:
|
|
const Simplicity_test_2<ForwardIterator, Traits>* s;
|
|
public:
|
|
EdgeComp() {}
|
|
EdgeComp(
|
|
const Simplicity_test_2<ForwardIterator, Traits>* s0): s(s0)
|
|
{}
|
|
bool operator() (int i, int j) const { return s->EdgeCompare(i,j); }
|
|
};
|
|
|
|
class EventQueue {
|
|
//-----------------------------------------------------------------//
|
|
// g++ 2.7.2 seems to have problems with the following typedef
|
|
//
|
|
// typedef set<int,VertexComp>::const_iterator const_iterator;
|
|
//-----------------------------------------------------------------//
|
|
|
|
protected:
|
|
std::set<int,VertexComp> queue;
|
|
public:
|
|
EventQueue(Simplicity_test_2<ForwardIterator, Traits>* s)
|
|
: queue(VertexComp(s)) {}
|
|
bool insert(int i) { return queue.insert(i).second; }
|
|
bool empty() const { return queue.empty(); }
|
|
int pop() {
|
|
int Result = *(queue.begin());
|
|
queue.erase(queue.begin());
|
|
return Result;
|
|
}
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
void Show() const {
|
|
cout << " event queue: ";
|
|
|
|
typename std::set<int,VertexComp>::const_iterator i;
|
|
for (i = queue.begin(); i != queue.end(); ++i)
|
|
cout << *i << " ";
|
|
cout << endl;
|
|
}
|
|
#endif
|
|
};
|
|
|
|
class SweepStatus {
|
|
//-----------------------------------------------------------------//
|
|
// g++ 2.7.2 seems to have problems with the following typedef
|
|
//
|
|
// typedef std::set<int,EdgeComp>::const_iterator const_iterator;
|
|
//-----------------------------------------------------------------//
|
|
|
|
protected:
|
|
std::set<int,EdgeComp> status;
|
|
// if i is an element of status, it means that
|
|
|
|
std::vector<typename std::set<int,EdgeComp>::iterator> index;
|
|
// the iterators of the edges are stored to enable fast deletion
|
|
|
|
const Simplicity_test_2<ForwardIterator, Traits>* s;
|
|
// store a pointer to the Simplicity_test_2 class, to enable
|
|
// access to the vertices
|
|
|
|
public:
|
|
SweepStatus(
|
|
const Simplicity_test_2<ForwardIterator, Traits>* s0, int n)
|
|
: status(EdgeComp(s0)), s(s0)
|
|
{
|
|
index.insert(index.end(),n,status.end());
|
|
}
|
|
|
|
bool is_valid()
|
|
// A necessary condition for the sweep status to be valid is that
|
|
//
|
|
// 1) every edge in the status intersects the current sweepline
|
|
// 2) the edges are ordered along the current sweepline
|
|
{
|
|
int n = s->NumberOfVertices();
|
|
|
|
typename std::set<int,EdgeComp>::const_iterator i;
|
|
for (i = status.begin(); i != status.end(); ++i) {
|
|
int v1 = *i;
|
|
int v2 = (v1<n-1) ? v1+1 : v1+1-n;
|
|
// edge(v1) = (vertex(v1), vertex(v2))
|
|
|
|
Comparison_result c1 =
|
|
s->traits().compare_y(s->Vertex(v1), s->EventPoint());
|
|
|
|
Comparison_result c2 =
|
|
s->traits().compare_y(s->Vertex(v2), s->EventPoint());
|
|
|
|
if (c1 == SMALLER && c2 == SMALLER) return false;
|
|
if (c1 == LARGER && c2 == LARGER) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
void Show() {
|
|
cout << " sweep status: ";
|
|
typename std::set<int,EdgeComp>::const_iterator i;
|
|
for (i = status.begin(); i != status.end(); ++i)
|
|
cout << *i << " ";
|
|
cout << endl;
|
|
}
|
|
#endif // CGAL_POLYGON_DEBUG
|
|
|
|
void insert(int e)
|
|
{
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
{
|
|
cout << endl << " inserting edge " << e << " into sweep status" << endl;
|
|
}
|
|
#endif // CGAL_POLYGON_DEBUG
|
|
|
|
index[e] = status.insert(e).first;
|
|
}
|
|
|
|
void erase(int e)
|
|
{
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
{
|
|
cout << endl << " removing edge " << e << " from sweep status" << endl;
|
|
}
|
|
#endif // CGAL_POLYGON_DEBUG
|
|
|
|
status.erase(index[e]);
|
|
}
|
|
|
|
int replace(int e1, int e2)
|
|
{
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
{
|
|
cout << endl << " replacing edge " << e1 << " by edge "<< e2
|
|
<< " in sweep status" << endl;
|
|
}
|
|
#endif // CGAL_POLYGON_DEBUG
|
|
typename std::set<int,EdgeComp>::iterator cur = index[e1];
|
|
status.erase(cur++);
|
|
index[e2] = status.insert(cur, e2);
|
|
return e2;
|
|
}
|
|
|
|
int left(int e) const
|
|
{ typename std::set<int,EdgeComp>::const_iterator i = index[e];
|
|
return (i == status.begin()) ? -1 : *(--i);
|
|
}
|
|
|
|
int right(int e) const
|
|
{ typename std::set<int,EdgeComp>::const_iterator i = index[e]; ++i;
|
|
return (i == status.end()) ? -1 : *i;
|
|
}
|
|
};
|
|
};
|
|
|
|
template <class ForwardIterator, class Traits>
|
|
inline
|
|
bool Simplicity_test_2<ForwardIterator, Traits>::VertexCompare(
|
|
int i, int j) const
|
|
{
|
|
return !d_traits.lexicographically_yx_smaller_or_equal(Vertex(i), Vertex(j));
|
|
}
|
|
|
|
template <class ForwardIterator, class Traits>
|
|
inline
|
|
bool Simplicity_test_2<ForwardIterator, Traits>::EdgeCompare(
|
|
int e1, int e2) const
|
|
{
|
|
// Edges must always be compared in the same order! This is to avoid problems
|
|
// with overlapping edges:
|
|
//
|
|
// + v0
|
|
// /
|
|
// -----------------+-v2----------------- sweepline
|
|
// /
|
|
// /
|
|
// /
|
|
// /
|
|
// + v1
|
|
//
|
|
// In this case the order of the edges e0 = (v0,v1) and e1 = (v1,v2) on the
|
|
// sweepline is indeterminate. However, it will only be detected that these
|
|
// edges overlap after they have been inserted in the sweep state. To make
|
|
// sure that this insertion is done correctly, the choice for the order
|
|
// between e0 and e1 needs to be made consistently.
|
|
|
|
bool Result;
|
|
|
|
if (consecutive_edges(e1,e2)) {
|
|
if (e1 < e2)
|
|
Result = edge_compare_consecutive(e1,e2);
|
|
else
|
|
Result = !edge_compare_consecutive(e2,e1);
|
|
}
|
|
else {
|
|
if (e1 < e2)
|
|
Result = edge_compare_non_consecutive(e1,e2);
|
|
else
|
|
Result = !edge_compare_non_consecutive(e2,e1);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
template <class ForwardIterator, class Traits>
|
|
bool Simplicity_test_2<ForwardIterator, Traits>::edge_compare_consecutive(
|
|
int e1, int e2) const
|
|
// This function is used to compare two edges that share a vertex:
|
|
//
|
|
// + |
|
|
// / \ |
|
|
// e1 / \ e2 |
|
|
// / \ |
|
|
// / \ |
|
|
// -------------+---------+------------- sweepline |
|
|
// / \ |
|
|
// + + |
|
|
//
|
|
// Preconditions: 1) the shared vertex is lexicographically smaller or
|
|
// lexicographically bigger than both endpoints of the two
|
|
// edges (this condition is always satisfied in the
|
|
// sweepline algorithm)
|
|
//
|
|
// 2) both edges intersect the current sweepline
|
|
|
|
{
|
|
int n = NumberOfVertices();
|
|
int f1 = (e1<n-1) ? e1+1 : e1+1-n; // edge(e1) = (vertex(e1), vertex(f1))
|
|
int f2 = (e2<n-1) ? e2+1 : e2+1-n; // edge(e2) = (vertex(e2), vertex(f2))
|
|
|
|
if (f1 == e2)
|
|
if (d_traits.compare_y(Vertex(e2), Vertex(f2)) != EQUAL)
|
|
return has_on_left_side(Vertex(e1), Vertex(e2), Vertex(f2));
|
|
else if (d_traits.compare_x(Vertex(e2), Vertex(f2)) == SMALLER)
|
|
// Precondition 1) implies that e1 is on or above line (e2, f2).
|
|
// If above the line, then say segment e1 is smaller. If on the line,
|
|
// vertex e1 is to the right of e2; by convention make the edge
|
|
// with the smaller other endpoint the smaller one.
|
|
return (d_traits.compare_y(Vertex(e1), Vertex(f2)) != EQUAL) ||
|
|
(d_traits.compare_x(Vertex(e1), Vertex(f2)) == SMALLER);
|
|
else // vertex e1 is on or below line (e2, f2); edge e1 is smaller only
|
|
// if vertex e1 is on the line and to the left of f2
|
|
return (d_traits.compare_y(Vertex(e1), Vertex(f2)) == EQUAL) &&
|
|
(d_traits.compare_x(Vertex(e1), Vertex(f2)) == SMALLER);
|
|
else // f2 and e1 are the same
|
|
if (d_traits.compare_y(Vertex(e2), Vertex(f2)) != EQUAL)
|
|
return has_on_left_side(Vertex(f1), Vertex(f2), Vertex(e2));
|
|
else if (d_traits.compare_x(Vertex(e2), Vertex(f2)) == LARGER)
|
|
return (d_traits.compare_y(Vertex(f1), Vertex(e2)) != EQUAL) ||
|
|
(d_traits.compare_x(Vertex(f1), Vertex(e2)) == SMALLER);
|
|
else
|
|
return (d_traits.compare_y(Vertex(f1), Vertex(e2)) == EQUAL) &&
|
|
(d_traits.compare_x(Vertex(f1), Vertex(e2)) == SMALLER);
|
|
}
|
|
|
|
template <class ForwardIterator, class Traits>
|
|
bool
|
|
Simplicity_test_2<ForwardIterator, Traits>::edge_compare_non_consecutive(
|
|
int e1, int e2) const
|
|
{
|
|
int n = NumberOfVertices();
|
|
int f1 = (e1<n-1) ? e1+1 : e1+1-n; // edge(e1) = (vertex(e1), vertex(f1))
|
|
int f2 = (e2<n-1) ? e2+1 : e2+1-n; // edge(e2) = (vertex(e2), vertex(f2))
|
|
|
|
if (has_y_overlap(Vertex(e1), Vertex(e2), Vertex(f2)))
|
|
return has_on_left_side(Vertex(e1), Vertex(e2), Vertex(f2));
|
|
|
|
if (has_y_overlap(Vertex(f1), Vertex(e2), Vertex(f2)))
|
|
return has_on_left_side(Vertex(f1), Vertex(e2), Vertex(f2));
|
|
|
|
// if the vertices from edge e1 do not have y-overlap with edge e2, then
|
|
// the vertices from edge e2 must have y_overlap with edge e1
|
|
return !has_on_left_side(Vertex(e2), Vertex(e1), Vertex(f1));
|
|
}
|
|
|
|
template <class ForwardIterator, class Traits>
|
|
bool
|
|
Simplicity_test_2<ForwardIterator, Traits>::Test(ForwardIterator first,
|
|
ForwardIterator last)
|
|
{
|
|
int n = 0;
|
|
|
|
EventQueue events(this);
|
|
while (first != last) {
|
|
d_index.push_back(first);
|
|
if (!events.insert(n++)) // if two vertices coincide...
|
|
return false;
|
|
++first;
|
|
}
|
|
|
|
if (d_index.size() < 3)
|
|
return true;
|
|
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
{
|
|
cout << endl;
|
|
cout << "--- Simplicity test ----------------------------" << endl;
|
|
cout << endl;
|
|
cout << "Vertices:" << endl;
|
|
typedef typename std::vector<ForwardIterator>::size_type Size_type;
|
|
Size_type i;
|
|
for (i=0; i<d_index.size(); i++)
|
|
cout << i << " " << Vertex(i) << endl;
|
|
cout << endl;
|
|
events.Show();
|
|
}
|
|
#endif // CGAL_POLYGON_DEBUG
|
|
|
|
SweepStatus status(this,n);
|
|
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
{
|
|
cout << endl;
|
|
status.Show();
|
|
}
|
|
#endif // CGAL_POLYGON_DEBUG
|
|
|
|
while (!events.empty()) {
|
|
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
CGAL_polygon_assertion(status.is_valid());
|
|
#endif // CGAL_POLYGON_DEBUG
|
|
|
|
int i = events.pop();
|
|
d_eventpoint = i;
|
|
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
{
|
|
cout << endl;
|
|
cout << "--- Event point: " << d_eventpoint << " on sweep line {y = "
|
|
<< Vertex(d_eventpoint).y() << "}" << endl;
|
|
cout << endl;
|
|
status.Show();
|
|
}
|
|
#endif // CGAL_POLYGON_DEBUG
|
|
|
|
int prev = (i>0) ? i-1 : i-1+n;
|
|
int next = (i<n-1) ? i+1 : i+1-n;
|
|
|
|
bool prev_less_than_i = VertexCompare(i,prev);
|
|
bool next_less_than_i = VertexCompare(i,next);
|
|
if (prev_less_than_i != next_less_than_i) {
|
|
int e = prev_less_than_i ? status.replace(i,prev) :
|
|
status.replace(prev,i);
|
|
CGAL_polygon_assertion(status.is_valid());
|
|
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
{
|
|
cout << endl;
|
|
status.Show();
|
|
}
|
|
#endif // CGAL_POLYGON_DEBUG
|
|
|
|
// check for intersections of newly inserted edge e with neighbors
|
|
int left = status.left(e);
|
|
if ((left >= 0) && (EdgesDoIntersect(left,e))) return false;
|
|
|
|
int right = status.right(e);
|
|
if ((right >= 0) && (EdgesDoIntersect(e,right))) return false;
|
|
}
|
|
else if (prev_less_than_i) {
|
|
int e1 = prev;
|
|
int e2 = i;
|
|
|
|
status.insert(e1);
|
|
status.insert(e2);
|
|
CGAL_polygon_assertion(status.is_valid());
|
|
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
{
|
|
cout << endl;
|
|
status.Show();
|
|
}
|
|
#endif // CGAL_POLYGON_DEBUG
|
|
|
|
// check for intersections of newly inserted edges e1 and e2 with
|
|
// neighbors
|
|
int left, right;
|
|
left = status.left(e1);
|
|
if ((left >= 0) && (EdgesDoIntersect(left,e1))) return false;
|
|
|
|
right = status.right(e1);
|
|
if ((right >= 0) && (EdgesDoIntersect(e1,right))) return false;
|
|
|
|
left = status.left(e2);
|
|
if ((left >= 0) && (left != e1) && (EdgesDoIntersect(left,e2)))
|
|
return false;
|
|
|
|
right = status.right(e2);
|
|
if ((right >= 0) && (right != e1) &&(EdgesDoIntersect(e2,right)))
|
|
return false;
|
|
}
|
|
else {
|
|
// check for intersections between edges that become new neighbors
|
|
// in the sweep status due to the deletion
|
|
int left, right;
|
|
if (status.left(prev) == i)
|
|
{
|
|
left = status.left(i);
|
|
right = status.right(prev);
|
|
}
|
|
else
|
|
{
|
|
left = status.left(prev);
|
|
right = status.right(i);
|
|
}
|
|
status.erase(prev);
|
|
status.erase(i);
|
|
if (left >=0 && right >=0 && EdgesDoIntersect(left, right))
|
|
return false;
|
|
CGAL_polygon_assertion(status.is_valid());
|
|
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
{
|
|
cout << endl;
|
|
status.Show();
|
|
}
|
|
#endif // CGAL_POLYGON_DEBUG
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <class ForwardIterator, class Traits>
|
|
bool Simplicity_test_2<ForwardIterator, Traits>::EdgesDoIntersect(
|
|
int e1, int e2) const
|
|
{
|
|
#ifdef CGAL_POLYGON_DEBUG
|
|
{
|
|
cout << " intersecting edges " << e1 << " and " << e2 << endl;
|
|
}
|
|
#endif
|
|
|
|
int n = NumberOfVertices();
|
|
int f1 = (e1<n-1) ? e1+1 : e1+1-n; // edge(e1) = (vertex(e1), vertex(f1))
|
|
int f2 = (e2<n-1) ? e2+1 : e2+1-n; // edge(e2) = (vertex(e2), vertex(f2))
|
|
|
|
bool Result;
|
|
if (consecutive_edges(e1,e2))
|
|
Result = d_traits.have_equal_direction(Vertex(f1) - Vertex(e1),
|
|
Vertex(e2) - Vertex(f2) );
|
|
else
|
|
Result = d_traits.do_intersect(Vertex(e1),
|
|
Vertex(f1),
|
|
Vertex(e2),
|
|
Vertex(f2));
|
|
|
|
// N.B. traits() instead of d_traits gives an error with g++ 2.7.2
|
|
|
|
return Result;
|
|
}
|
|
|
|
template <class ForwardIterator, class Traits>
|
|
inline
|
|
bool Simplicity_test_2<ForwardIterator, Traits>::consecutive_edges(
|
|
int e1, int e2) const
|
|
{
|
|
int n = NumberOfVertices();
|
|
return ( CGAL_NTS abs(e2-e1) == 1 || CGAL_NTS abs(e2-e1) == n-1 );
|
|
}
|
|
|
|
CGAL_END_NAMESPACE
|
|
|
|
#endif // CGAL_SIMPLICITY_TEST_H
|