Point Cloud Library (PCL)  1.9.1-dev
organized_fast_mesh.h
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Point Cloud Library (PCL) - www.pointclouds.org
5  * Copyright (c) 2011, Dirk Holz, University of Bonn.
6  * Copyright (c) 2010-2011, Willow Garage, Inc.
7  *
8  * All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  *
14  * * Redistributions of source code must retain the above copyright
15  * notice, this list of conditions and the following disclaimer.
16  * * Redistributions in binary form must reproduce the above
17  * copyright notice, this list of conditions and the following
18  * disclaimer in the documentation and/or other materials provided
19  * with the distribution.
20  * * Neither the name of Willow Garage, Inc. nor the names of its
21  * contributors may be used to endorse or promote products derived
22  * from this software without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  * POSSIBILITY OF SUCH DAMAGE.
36  *
37  * $Id$
38  *
39  */
40 
41 #pragma once
42 
43 #include <pcl/common/angles.h>
44 #include <pcl/surface/reconstruction.h>
45 
46 namespace pcl
47 {
48 
49  /** \brief Simple triangulation/surface reconstruction for organized point
50  * clouds. Neighboring points (pixels in image space) are connected to
51  * construct a triangular (or quad) mesh.
52  *
53  * \note If you use this code in any academic work, please cite:
54  * D. Holz and S. Behnke.
55  * Fast Range Image Segmentation and Smoothing using Approximate Surface Reconstruction and Region Growing.
56  * In Proceedings of the 12th International Conference on Intelligent Autonomous Systems (IAS),
57  * Jeju Island, Korea, June 26-29 2012.
58  * <a href="http://purl.org/holz/papers/holz_2012_ias.pdf">http://purl.org/holz/papers/holz_2012_ias.pdf</a>
59  *
60  * \author Dirk Holz, Radu B. Rusu
61  * \ingroup surface
62  */
63  template <typename PointInT>
64  class OrganizedFastMesh : public MeshConstruction<PointInT>
65  {
66  public:
67  using Ptr = boost::shared_ptr<OrganizedFastMesh<PointInT> >;
68  using ConstPtr = boost::shared_ptr<const OrganizedFastMesh<PointInT> >;
69 
72 
74 
75  using Polygons = std::vector<pcl::Vertices>;
76 
78  {
79  TRIANGLE_RIGHT_CUT, // _always_ "cuts" a quad from top left to bottom right
80  TRIANGLE_LEFT_CUT, // _always_ "cuts" a quad from top right to bottom left
81  TRIANGLE_ADAPTIVE_CUT, // "cuts" where possible and prefers larger differences in 'z' direction
82  QUAD_MESH // create a simple quad mesh
83  };
84 
85  /** \brief Constructor. Triangulation type defaults to \a QUAD_MESH. */
87  : max_edge_length_a_ (0.0f)
88  , max_edge_length_b_ (0.0f)
89  , max_edge_length_c_ (0.0f)
90  , max_edge_length_set_ (false)
95  , viewpoint_ (Eigen::Vector3f::Zero ())
96  , store_shadowed_faces_ (false)
97  , cos_angle_tolerance_ (fabsf (cosf (pcl::deg2rad (12.5f))))
98  , distance_tolerance_ (-1.0f)
99  , distance_dependent_ (false)
100  , use_depth_as_distance_(false)
101  {
102  check_tree_ = false;
103  };
104 
105  /** \brief Destructor. */
107 
108  /** \brief Set a maximum edge length.
109  * Using not only the scalar \a a, but also \a b and \a c, allows for using a distance threshold in the form of:
110  * threshold(x) = c*x*x + b*x + a
111  * \param[in] a scalar coefficient of the (distance-dependent polynom) threshold
112  * \param[in] b linear coefficient of the (distance-dependent polynom) threshold
113  * \param[in] c quadratic coefficient of the (distance-dependent polynom) threshold
114  */
115  inline void
116  setMaxEdgeLength (float a, float b = 0.0f, float c = 0.0f)
117  {
118  max_edge_length_a_ = a;
119  max_edge_length_b_ = b;
120  max_edge_length_c_ = c;
121  if ((max_edge_length_a_ + max_edge_length_b_ + max_edge_length_c_) > std::numeric_limits<float>::min())
122  max_edge_length_set_ = true;
123  else
124  max_edge_length_set_ = false;
125  };
126 
127  inline void
129  {
130  max_edge_length_set_ = false;
131  }
132 
133  /** \brief Set the edge length (in pixels) used for constructing the fixed mesh.
134  * \param[in] triangle_size edge length in pixels
135  * (Default: 1 = neighboring pixels are connected)
136  */
137  inline void
138  setTrianglePixelSize (int triangle_size)
139  {
140  setTrianglePixelSizeRows (triangle_size);
141  setTrianglePixelSizeColumns (triangle_size);
142  }
143 
144  /** \brief Set the edge length (in pixels) used for iterating over rows when constructing the fixed mesh.
145  * \param[in] triangle_size edge length in pixels
146  * (Default: 1 = neighboring pixels are connected)
147  */
148  inline void
149  setTrianglePixelSizeRows (int triangle_size)
150  {
151  triangle_pixel_size_rows_ = std::max (1, (triangle_size - 1));
152  }
153 
154  /** \brief Set the edge length (in pixels) used for iterating over columns when constructing the fixed mesh.
155  * \param[in] triangle_size edge length in pixels
156  * (Default: 1 = neighboring pixels are connected)
157  */
158  inline void
159  setTrianglePixelSizeColumns (int triangle_size)
160  {
161  triangle_pixel_size_columns_ = std::max (1, (triangle_size - 1));
162  }
163 
164  /** \brief Set the triangulation type (see \a TriangulationType)
165  * \param[in] type quad mesh, triangle mesh with fixed left, right cut,
166  * or adaptive cut (splits a quad w.r.t. the depth (z) of the points)
167  */
168  inline void
170  {
171  triangulation_type_ = type;
172  }
173 
174  /** \brief Set the viewpoint from where the input point cloud has been acquired.
175  * \param[in] viewpoint Vector containing the viewpoint coordinates (in the coordinate system of the data)
176  */
177  inline void setViewpoint (const Eigen::Vector3f& viewpoint)
178  {
179  viewpoint_ = viewpoint;
180  }
181 
182  /** \brief Get the viewpoint from where the input point cloud has been acquired. */
183  const inline Eigen::Vector3f& getViewpoint () const
184  {
185  return viewpoint_;
186  }
187 
188  /** \brief Store shadowed faces or not.
189  * \param[in] enable set to true to store shadowed faces
190  */
191  inline void
192  storeShadowedFaces (bool enable)
193  {
194  store_shadowed_faces_ = enable;
195  }
196 
197  /** \brief Set the angle tolerance used for checking whether or not an edge is occluded.
198  * Standard values are 5deg to 15deg (input in rad!). Set a value smaller than zero to
199  * disable the check for shadowed edges.
200  * \param[in] angle_tolerance Angle tolerance (in rad). Set a value <0 to disable.
201  */
202  inline void
203  setAngleTolerance(float angle_tolerance)
204  {
205  if (angle_tolerance > 0)
206  cos_angle_tolerance_ = fabsf (cosf (angle_tolerance));
207  else
208  cos_angle_tolerance_ = -1.0f;
209  }
210 
211 
212  inline void setDistanceTolerance(float distance_tolerance, bool depth_dependent = false)
213  {
214  distance_tolerance_ = distance_tolerance;
215  if (distance_tolerance_ < 0)
216  return;
217 
218  distance_dependent_ = depth_dependent;
219  if (!distance_dependent_)
221  }
222 
223  /** \brief Use the points' depths (z-coordinates) instead of measured distances (points' distances to the viewpoint).
224  * \param[in] enable Set to true skips comptations and further speeds up computation by using depth instead of computing distance. false to disable. */
225  inline void useDepthAsDistance(bool enable)
226  {
227  use_depth_as_distance_ = enable;
228  }
229 
230  protected:
231  /** \brief max length of edge, scalar component */
233  /** \brief max length of edge, scalar component */
235  /** \brief max length of edge, scalar component */
237  /** \brief flag whether or not edges are limited in length */
239 
240  /** \brief flag whether or not max edge length is distance dependent. */
242 
243  /** \brief size of triangle edges (in pixels) for iterating over rows. */
245 
246  /** \brief size of triangle edges (in pixels) for iterating over columns*/
248 
249  /** \brief Type of meshing scheme (quads vs. triangles, left cut vs. right cut ... */
251 
252  /** \brief Viewpoint from which the point cloud has been acquired (in the same coordinate frame as the data). */
253  Eigen::Vector3f viewpoint_;
254 
255  /** \brief Whether or not shadowed faces are stored, e.g., for exploration */
257 
258  /** \brief (Cosine of the) angle tolerance used when checking whether or not an edge between two points is shadowed. */
260 
261  /** \brief distance tolerance for filtering out shadowed/occluded edges */
263 
264  /** \brief flag whether or not \a distance_tolerance_ is distance dependent (multiplied by the squared distance to the point) or not. */
266 
267  /** \brief flag whether or not the points' depths are used instead of measured distances (points' distances to the viewpoint).
268  This flag may be set using useDepthAsDistance(true) for (RGB-)Depth cameras to skip computations and gain additional speed up. */
270 
271 
272  /** \brief Perform the actual polygonal reconstruction.
273  * \param[out] polygons the resultant polygons
274  */
275  void
276  reconstructPolygons (std::vector<pcl::Vertices>& polygons);
277 
278  /** \brief Create the surface.
279  * \param[out] polygons the resultant polygons, as a set of vertices. The Vertices structure contains an array of point indices.
280  */
281  void
282  performReconstruction (std::vector<pcl::Vertices> &polygons) override;
283 
284  /** \brief Create the surface.
285  *
286  * Simply uses image indices to create an initial polygonal mesh for organized point clouds.
287  * \a indices_ are ignored!
288  *
289  * \param[out] output the resultant polygonal mesh
290  */
291  void
292  performReconstruction (pcl::PolygonMesh &output) override;
293 
294  /** \brief Add a new triangle to the current polygon mesh
295  * \param[in] a index of the first vertex
296  * \param[in] b index of the second vertex
297  * \param[in] c index of the third vertex
298  * \param[in] idx the index in the set of polygon vertices (assumes \a idx is valid in \a polygons)
299  * \param[out] polygons the polygon mesh to be updated
300  */
301  inline void
302  addTriangle (int a, int b, int c, int idx, std::vector<pcl::Vertices>& polygons)
303  {
304  assert (idx < static_cast<int> (polygons.size ()));
305  polygons[idx].vertices.resize (3);
306  polygons[idx].vertices[0] = a;
307  polygons[idx].vertices[1] = b;
308  polygons[idx].vertices[2] = c;
309  }
310 
311  /** \brief Add a new quad to the current polygon mesh
312  * \param[in] a index of the first vertex
313  * \param[in] b index of the second vertex
314  * \param[in] c index of the third vertex
315  * \param[in] d index of the fourth vertex
316  * \param[in] idx the index in the set of polygon vertices (assumes \a idx is valid in \a polygons)
317  * \param[out] polygons the polygon mesh to be updated
318  */
319  inline void
320  addQuad (int a, int b, int c, int d, int idx, std::vector<pcl::Vertices>& polygons)
321  {
322  assert (idx < static_cast<int> (polygons.size ()));
323  polygons[idx].vertices.resize (4);
324  polygons[idx].vertices[0] = a;
325  polygons[idx].vertices[1] = b;
326  polygons[idx].vertices[2] = c;
327  polygons[idx].vertices[3] = d;
328  }
329 
330  /** \brief Set (all) coordinates of a particular point to the specified value
331  * \param[in] point_index index of point
332  * \param[out] mesh to modify
333  * \param[in] value value to use when re-setting
334  * \param[in] field_x_idx the X coordinate of the point
335  * \param[in] field_y_idx the Y coordinate of the point
336  * \param[in] field_z_idx the Z coordinate of the point
337  */
338  inline void
339  resetPointData (const int &point_index, pcl::PolygonMesh &mesh, const float &value = 0.0f,
340  int field_x_idx = 0, int field_y_idx = 1, int field_z_idx = 2)
341  {
342  float new_value = value;
343  memcpy (&mesh.cloud.data[point_index * mesh.cloud.point_step + mesh.cloud.fields[field_x_idx].offset], &new_value, sizeof (float));
344  memcpy (&mesh.cloud.data[point_index * mesh.cloud.point_step + mesh.cloud.fields[field_y_idx].offset], &new_value, sizeof (float));
345  memcpy (&mesh.cloud.data[point_index * mesh.cloud.point_step + mesh.cloud.fields[field_z_idx].offset], &new_value, sizeof (float));
346  }
347 
348  /** \brief Check if a point is shadowed by another point
349  * \param[in] point_a the first point
350  * \param[in] point_b the second point
351  */
352  inline bool
353  isShadowed (const PointInT& point_a, const PointInT& point_b)
354  {
355  bool valid = true;
356 
357  Eigen::Vector3f dir_a = viewpoint_ - point_a.getVector3fMap ();
358  Eigen::Vector3f dir_b = point_b.getVector3fMap () - point_a.getVector3fMap ();
359  float distance_to_points = dir_a.norm ();
360  float distance_between_points = dir_b.norm ();
361 
362  if (cos_angle_tolerance_ > 0)
363  {
364  float cos_angle = dir_a.dot (dir_b) / (distance_to_points*distance_between_points);
365  if (std::isnan(cos_angle))
366  cos_angle = 1.0f;
367  bool check_angle = std::fabs (cos_angle) >= cos_angle_tolerance_;
368 
369  bool check_distance = true;
370  if (check_angle && (distance_tolerance_ > 0))
371  {
372  float dist_thresh = distance_tolerance_;
373  if (distance_dependent_)
374  {
375  float d = distance_to_points;
376  if (use_depth_as_distance_)
377  d = std::max(point_a.z, point_b.z);
378  dist_thresh *= d*d;
379  dist_thresh *= dist_thresh; // distance_tolerance_ is already squared if distance_dependent_ is false.
380  }
381  check_distance = (distance_between_points > dist_thresh);
382  }
383  valid = !(check_angle && check_distance);
384  }
385 
386  // check if max. edge length is not exceeded
387  if (max_edge_length_set_)
388  {
389  float dist = (use_depth_as_distance_ ? std::max(point_a.z, point_b.z) : distance_to_points);
390  float dist_thresh = max_edge_length_a_;
391  if (std::fabs(max_edge_length_b_) > std::numeric_limits<float>::min())
392  dist_thresh += max_edge_length_b_ * dist;
393  if (std::fabs(max_edge_length_c_) > std::numeric_limits<float>::min())
394  dist_thresh += max_edge_length_c_ * dist * dist;
395  valid = (distance_between_points <= dist_thresh);
396  }
397 
398  return !valid;
399  }
400 
401  /** \brief Check if a triangle is valid.
402  * \param[in] a index of the first vertex
403  * \param[in] b index of the second vertex
404  * \param[in] c index of the third vertex
405  */
406  inline bool
407  isValidTriangle (const int& a, const int& b, const int& c)
408  {
409  if (!pcl::isFinite (input_->points[a])) return (false);
410  if (!pcl::isFinite (input_->points[b])) return (false);
411  if (!pcl::isFinite (input_->points[c])) return (false);
412  return (true);
413  }
414 
415  /** \brief Check if a triangle is shadowed.
416  * \param[in] a index of the first vertex
417  * \param[in] b index of the second vertex
418  * \param[in] c index of the third vertex
419  */
420  inline bool
421  isShadowedTriangle (const int& a, const int& b, const int& c)
422  {
423  if (isShadowed (input_->points[a], input_->points[b])) return (true);
424  if (isShadowed (input_->points[b], input_->points[c])) return (true);
425  if (isShadowed (input_->points[c], input_->points[a])) return (true);
426  return (false);
427  }
428 
429  /** \brief Check if a quad is valid.
430  * \param[in] a index of the first vertex
431  * \param[in] b index of the second vertex
432  * \param[in] c index of the third vertex
433  * \param[in] d index of the fourth vertex
434  */
435  inline bool
436  isValidQuad (const int& a, const int& b, const int& c, const int& d)
437  {
438  if (!pcl::isFinite (input_->points[a])) return (false);
439  if (!pcl::isFinite (input_->points[b])) return (false);
440  if (!pcl::isFinite (input_->points[c])) return (false);
441  if (!pcl::isFinite (input_->points[d])) return (false);
442  return (true);
443  }
444 
445  /** \brief Check if a triangle is shadowed.
446  * \param[in] a index of the first vertex
447  * \param[in] b index of the second vertex
448  * \param[in] c index of the third vertex
449  * \param[in] d index of the fourth vertex
450  */
451  inline bool
452  isShadowedQuad (const int& a, const int& b, const int& c, const int& d)
453  {
454  if (isShadowed (input_->points[a], input_->points[b])) return (true);
455  if (isShadowed (input_->points[b], input_->points[c])) return (true);
456  if (isShadowed (input_->points[c], input_->points[d])) return (true);
457  if (isShadowed (input_->points[d], input_->points[a])) return (true);
458  return (false);
459  }
460 
461  /** \brief Create a quad mesh.
462  * \param[out] polygons the resultant mesh
463  */
464  void
465  makeQuadMesh (std::vector<pcl::Vertices>& polygons);
466 
467  /** \brief Create a right cut mesh.
468  * \param[out] polygons the resultant mesh
469  */
470  void
471  makeRightCutMesh (std::vector<pcl::Vertices>& polygons);
472 
473  /** \brief Create a left cut mesh.
474  * \param[out] polygons the resultant mesh
475  */
476  void
477  makeLeftCutMesh (std::vector<pcl::Vertices>& polygons);
478 
479  /** \brief Create an adaptive cut mesh.
480  * \param[out] polygons the resultant mesh
481  */
482  void
483  makeAdaptiveCutMesh (std::vector<pcl::Vertices>& polygons);
484  };
485 }
486 
487 #ifdef PCL_NO_PRECOMPILE
488 #include <pcl/surface/impl/organized_fast_mesh.hpp>
489 #endif
boost::shared_ptr< const OrganizedFastMesh< PointInT > > ConstPtr
void setTriangulationType(TriangulationType type)
Set the triangulation type (see TriangulationType)
void makeQuadMesh(std::vector< pcl::Vertices > &polygons)
Create a quad mesh.
void setDistanceTolerance(float distance_tolerance, bool depth_dependent=false)
bool isFinite(const PointT &pt)
Tests if the 3D components of a point are all finite param[in] pt point to be tested return true if f...
Definition: point_tests.h:53
float deg2rad(float alpha)
Convert an angle from degrees to radians.
Definition: angles.hpp:67
bool check_tree_
A flag specifying whether or not the derived reconstruction algorithm needs the search object tree...
void resetPointData(const int &point_index, pcl::PolygonMesh &mesh, const float &value=0.0f, int field_x_idx=0, int field_y_idx=1, int field_z_idx=2)
Set (all) coordinates of a particular point to the specified value.
void makeAdaptiveCutMesh(std::vector< pcl::Vertices > &polygons)
Create an adaptive cut mesh.
pcl::uint32_t point_step
Eigen::Vector3f viewpoint_
Viewpoint from which the point cloud has been acquired (in the same coordinate frame as the data)...
Simple triangulation/surface reconstruction for organized point clouds.
This file defines compatibility wrappers for low level I/O functions.
Definition: convolution.h:45
bool distance_dependent_
flag whether or not distance_tolerance_ is distance dependent (multiplied by the squared distance to ...
void setTrianglePixelSizeRows(int triangle_size)
Set the edge length (in pixels) used for iterating over rows when constructing the fixed mesh...
void setTrianglePixelSize(int triangle_size)
Set the edge length (in pixels) used for constructing the fixed mesh.
void makeLeftCutMesh(std::vector< pcl::Vertices > &polygons)
Create a left cut mesh.
void reconstructPolygons(std::vector< pcl::Vertices > &polygons)
Perform the actual polygonal reconstruction.
TriangulationType triangulation_type_
Type of meshing scheme (quads vs.
Definition: bfgs.h:9
int triangle_pixel_size_rows_
size of triangle edges (in pixels) for iterating over rows.
void makeRightCutMesh(std::vector< pcl::Vertices > &polygons)
Create a right cut mesh.
float max_edge_length_a_
max length of edge, scalar component
void setAngleTolerance(float angle_tolerance)
Set the angle tolerance used for checking whether or not an edge is occluded.
typename pcl::PointCloud< PointInT >::Ptr PointCloudPtr
float max_edge_length_b_
max length of edge, scalar component
int triangle_pixel_size_columns_
size of triangle edges (in pixels) for iterating over columns
bool isValidTriangle(const int &a, const int &b, const int &c)
Check if a triangle is valid.
void setTrianglePixelSizeColumns(int triangle_size)
Set the edge length (in pixels) used for iterating over columns when constructing the fixed mesh...
bool isShadowedQuad(const int &a, const int &b, const int &c, const int &d)
Check if a triangle is shadowed.
bool isValidQuad(const int &a, const int &b, const int &c, const int &d)
Check if a quad is valid.
void setMaxEdgeLength(float a, float b=0.0f, float c=0.0f)
Set a maximum edge length.
void setViewpoint(const Eigen::Vector3f &viewpoint)
Set the viewpoint from where the input point cloud has been acquired.
float max_edge_length_c_
max length of edge, scalar component
boost::shared_ptr< PointCloud< PointT > > Ptr
Definition: point_cloud.h:429
float cos_angle_tolerance_
(Cosine of the) angle tolerance used when checking whether or not an edge between two points is shado...
MeshConstruction represents a base surface reconstruction class.
std::vector< ::pcl::PCLPointField > fields
void addQuad(int a, int b, int c, int d, int idx, std::vector< pcl::Vertices > &polygons)
Add a new quad to the current polygon mesh.
const Eigen::Vector3f & getViewpoint() const
Get the viewpoint from where the input point cloud has been acquired.
void useDepthAsDistance(bool enable)
Use the points&#39; depths (z-coordinates) instead of measured distances (points&#39; distances to the viewpo...
::pcl::PCLPointCloud2 cloud
Definition: PolygonMesh.h:22
void addTriangle(int a, int b, int c, int idx, std::vector< pcl::Vertices > &polygons)
Add a new triangle to the current polygon mesh.
float distance_tolerance_
distance tolerance for filtering out shadowed/occluded edges
PointCloudConstPtr input_
The input point cloud dataset.
Definition: pcl_base.h:151
bool isShadowedTriangle(const int &a, const int &b, const int &c)
Check if a triangle is shadowed.
boost::shared_ptr< OrganizedFastMesh< PointInT > > Ptr
void storeShadowedFaces(bool enable)
Store shadowed faces or not.
void performReconstruction(std::vector< pcl::Vertices > &polygons) override
Create the surface.
std::vector< pcl::Vertices > Polygons
bool max_edge_length_set_
flag whether or not edges are limited in length
bool use_depth_as_distance_
flag whether or not the points&#39; depths are used instead of measured distances (points&#39; distances to t...
bool isShadowed(const PointInT &point_a, const PointInT &point_b)
Check if a point is shadowed by another point.
bool store_shadowed_faces_
Whether or not shadowed faces are stored, e.g., for exploration.
std::vector< pcl::uint8_t > data
bool max_edge_length_dist_dependent_
flag whether or not max edge length is distance dependent.
OrganizedFastMesh()
Constructor.