opennurbs_planesurface.h
Go to the documentation of this file.
00001 /* $NoKeywords: $ */
00002 /*
00003 //
00004 // Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved.
00005 // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
00006 // McNeel & Associates.
00007 //
00008 // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
00009 // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
00010 // MERCHANTABILITY ARE HEREBY DISCLAIMED.
00011 //                              
00012 // For complete openNURBS copyright information see <http://www.opennurbs.org>.
00013 //
00015 */
00016 
00017 #if !defined(ON_GEOMETRY_SURFACE_PLANE_INC_)
00018 #define ON_GEOMETRY_SURFACE_PLANE_INC_
00019 
00020 class ON_PlaneSurface;
00021 
00022 class ON_CLASS ON_PlaneSurface : public ON_Surface
00023 {
00024   ON_OBJECT_DECLARE(ON_PlaneSurface);
00025 
00026 public:
00027   ON_PlaneSurface();
00028   ON_PlaneSurface(const ON_PlaneSurface&);
00029   ON_PlaneSurface& operator=(const ON_PlaneSurface&);
00030 
00031   ON_PlaneSurface(const ON_Plane&);
00032   ON_PlaneSurface& operator=(const ON_Plane&);
00033 
00034   virtual ~ON_PlaneSurface();
00035 
00036   // An ON_PlaneSurface is really a finite rectangle.
00037   // m_plane defines the plane and m_extents[] stores
00038   // the x and y intervals of the plane that define the
00039   // rectangle.  The m_domain[] intervals specify the
00040   // evaluation domain.  Changing the extents are domain
00041   // are INDEPENDENT of each other.  Use Domain() and
00042   // SetDomain() to control the evluation domain.  Use
00043   // Extents() and SetExtents() to control the rectangle
00044   // extents.
00045   ON_Plane m_plane;
00046 
00048   // ON_Object overrides
00049 
00050   // virtual ON_Object::SizeOf override
00051   unsigned int SizeOf() const;
00052 
00053   // virtual ON_Object::DataCRC override
00054   ON__UINT32 DataCRC(ON__UINT32 current_remainder) const;
00055 
00056   /*
00057   Description:
00058     Tests an object to see if its data members are correctly
00059     initialized.
00060   Parameters:
00061     text_log - [in] if the object is not valid and text_log
00062         is not NULL, then a brief englis description of the
00063         reason the object is not valid is appened to the log.
00064         The information appended to text_log is suitable for 
00065         low-level debugging purposes by programmers and is 
00066         not intended to be useful as a high level user 
00067         interface tool.
00068   Returns:
00069     @untitled table
00070     true     object is valid
00071     false    object is invalid, uninitialized, etc.
00072   Remarks:
00073     Overrides virtual ON_Object::IsValid
00074   */
00075   ON_BOOL32 IsValid( ON_TextLog* text_log = NULL ) const;
00076 
00077   void Dump( ON_TextLog& ) const; // for debugging
00078 
00079   ON_BOOL32 Write(
00080          ON_BinaryArchive&  // open binary file
00081        ) const;
00082 
00083   ON_BOOL32 Read(
00084          ON_BinaryArchive&  // open binary file
00085        );
00086 
00088   // ON_Geometry overrides
00089 
00090   int Dimension() const;
00091 
00092   ON_BOOL32 GetBBox( // returns true if successful
00093          double*,    // minimum
00094          double*,    // maximum
00095          ON_BOOL32 = false  // true means grow box
00096          ) const;
00097 
00098   ON_BOOL32 Transform( 
00099          const ON_Xform&
00100          );
00101 
00103   // ON_Surface overrides
00104 
00105   /*
00106   Description:
00107     Sets the evaluation domains.  Does not change the geometry.
00108   Parameters:
00109     dir - [in] 0 sets first parameter's domain
00110                1 sets second parameter's domain
00111     t0 - [in]
00112     t1 - [in] (t0 < t1) the interval (t0,t1) will be the new domain
00113   Returns:
00114     True if successful.
00115   See Also:
00116     ON_PlaneSurface::SetExtents
00117   */
00118   ON_BOOL32 SetDomain( 
00119     int dir, 
00120     double t0, 
00121     double t1
00122     );
00123 
00124   ON_Interval Domain(
00125     int // 0 gets first parameter's domain, 1 gets second parameter's domain
00126     ) const;
00127 
00128   /*
00129   Description:
00130     Get an estimate of the size of the rectangle that would
00131     be created if the 3d surface where flattened into a rectangle.
00132   Parameters:
00133     width - [out]  (corresponds to the first surface parameter)
00134     height - [out] (corresponds to the first surface parameter)
00135   Remarks:
00136     overrides virtual ON_Surface::GetSurfaceSize
00137   Returns:
00138     true if successful.
00139   */
00140   ON_BOOL32 GetSurfaceSize( 
00141       double* width, 
00142       double* height 
00143       ) const;
00144 
00145   int SpanCount(
00146     int // 0 gets first parameter's domain, 1 gets second parameter's domain
00147     ) const; // number of smooth spans in curve
00148 
00149   ON_BOOL32 GetSpanVector( // span "knots" 
00150     int, // 0 gets first parameter's domain, 1 gets second parameter's domain
00151     double* // array of length SpanCount() + 1 
00152     ) const; // 
00153 
00154   int Degree( // returns maximum algebraic degree of any span 
00155                   // ( or a good estimate if curve spans are not algebraic )
00156     int // 0 gets first parameter's domain, 1 gets second parameter's domain
00157     ) const; 
00158 
00159   ON_BOOL32 GetParameterTolerance( // returns tminus < tplus: parameters tminus <= s <= tplus
00160          int,     // 0 gets first parameter, 1 gets second parameter
00161          double,  // t = parameter in domain
00162          double*, // tminus
00163          double*  // tplus
00164          ) const;
00165 
00166   /*
00167   Description:
00168     Test a surface to see if it is planar.
00169   Parameters:
00170     plane - [out] if not NULL and true is returned,
00171                   the plane parameters are filled in.
00172     tolerance - [in] tolerance to use when checking
00173   Returns:
00174     true if there is a plane such that the maximum distance from
00175     the surface to the plane is <= tolerance.
00176   Remarks:
00177     Overrides virtual ON_Surface::IsPlanar.
00178   */
00179   ON_BOOL32 IsPlanar(
00180         ON_Plane* plane = NULL,
00181         double tolerance = ON_ZERO_TOLERANCE
00182         ) const;
00183 
00184   ON_BOOL32 IsClosed(   // true if surface is closed in direction
00185         int        // dir  0 = "s", 1 = "t"
00186         ) const;
00187 
00188   ON_BOOL32 IsPeriodic( // true if surface is periodic in direction
00189         int        // dir  0 = "s", 1 = "t"
00190         ) const;
00191 
00192   ON_BOOL32 IsSingular( // true if surface side is collapsed to a point
00193         int        // side of parameter space to test
00194                    // 0 = south, 1 = east, 2 = north, 3 = west
00195         ) const;
00196   
00197   /*
00198   Description:
00199     Search for a derivatitive, tangent, or curvature 
00200     discontinuity.
00201   Parameters:
00202     dir - [in] If 0, then "u" parameter is checked.  If 1, then
00203                the "v" parameter is checked.
00204     c - [in] type of continity to test for.
00205     t0 - [in] Search begins at t0. If there is a discontinuity
00206               at t0, it will be ignored.  This makes it 
00207               possible to repeatedly call GetNextDiscontinuity
00208               and step through the discontinuities.
00209     t1 - [in] (t0 != t1)  If there is a discontinuity at t1 is 
00210               will be ingored unless c is a locus discontinuity
00211               type and t1 is at the start or end of the curve.
00212     t - [out] if a discontinuity is found, then *t reports the
00213           parameter at the discontinuity.
00214     hint - [in/out] if GetNextDiscontinuity will be called 
00215        repeatedly, passing a "hint" with initial value *hint=0
00216        will increase the speed of the search.       
00217     dtype - [out] if not NULL, *dtype reports the kind of 
00218         discontinuity found at *t.  A value of 1 means the first 
00219         derivative or unit tangent was discontinuous.  A value 
00220         of 2 means the second derivative or curvature was 
00221         discontinuous.  A value of 0 means teh curve is not
00222         closed, a locus discontinuity test was applied, and
00223         t1 is at the start of end of the curve.
00224     cos_angle_tolerance - [in] default = cos(1 degree) Used only
00225         when c is ON::G1_continuous or ON::G2_continuous.  If the
00226         cosine of the angle between two tangent vectors is 
00227         <= cos_angle_tolerance, then a G1 discontinuity is reported.
00228     curvature_tolerance - [in] (default = ON_SQRT_EPSILON) Used 
00229         only when c is ON::G2_continuous.  If K0 and K1 are 
00230         curvatures evaluated from above and below and 
00231         |K0 - K1| > curvature_tolerance, then a curvature 
00232         discontinuity is reported.
00233   Returns:
00234     Parametric continuity tests c = (C0_continuous, ..., G2_continuous):
00235 
00236       true if a parametric discontinuity was found strictly 
00237       between t0 and t1. Note well that all curves are 
00238       parametrically continuous at the ends of their domains.
00239 
00240     Locus continuity tests c = (C0_locus_continuous, ...,G2_locus_continuous):
00241 
00242       true if a locus discontinuity was found strictly between
00243       t0 and t1 or at t1 is the at the end of a curve.
00244       Note well that all open curves (IsClosed()=false) are locus
00245       discontinuous at the ends of their domains.  All closed 
00246       curves (IsClosed()=true) are at least C0_locus_continuous at 
00247       the ends of their domains.
00248   */
00249   bool GetNextDiscontinuity( 
00250                   int dir,
00251                   ON::continuity c,
00252                   double t0,
00253                   double t1,
00254                   double* t,
00255                   int* hint=NULL,
00256                   int* dtype=NULL,
00257                   double cos_angle_tolerance=ON_DEFAULT_ANGLE_TOLERANCE_COSINE,
00258                   double curvature_tolerance=ON_SQRT_EPSILON
00259                   ) const;
00260 
00261   /*
00262   Description:
00263     Test continuity at a surface parameter value.
00264   Parameters:
00265     c - [in] continuity to test for
00266     s - [in] surface parameter to test
00267     t - [in] surface parameter to test
00268     hint - [in] evaluation hint
00269     point_tolerance - [in] if the distance between two points is
00270         greater than point_tolerance, then the surface is not C0.
00271     d1_tolerance - [in] if the difference between two first derivatives is
00272         greater than d1_tolerance, then the surface is not C1.
00273     d2_tolerance - [in] if the difference between two second derivatives is
00274         greater than d2_tolerance, then the surface is not C2.
00275     cos_angle_tolerance - [in] default = cos(1 degree) Used only when
00276         c is ON::G1_continuous or ON::G2_continuous.  If the cosine
00277         of the angle between two normal vectors 
00278         is <= cos_angle_tolerance, then a G1 discontinuity is reported.
00279     curvature_tolerance - [in] (default = ON_SQRT_EPSILON) Used only when
00280         c is ON::G2_continuous.  If K0 and K1 are curvatures evaluated
00281         from above and below and |K0 - K1| > curvature_tolerance,
00282         then a curvature discontinuity is reported.
00283   Returns:
00284     true if the surface has at least the c type continuity at the parameter t.
00285   Remarks:
00286     Overrides virtual ON_Surface::IsContinuous
00287   */
00288   bool IsContinuous(
00289     ON::continuity c,
00290     double s, 
00291     double t, 
00292     int* hint = NULL,
00293     double point_tolerance=ON_ZERO_TOLERANCE,
00294     double d1_tolerance=ON_ZERO_TOLERANCE,
00295     double d2_tolerance=ON_ZERO_TOLERANCE,
00296     double cos_angle_tolerance=ON_DEFAULT_ANGLE_TOLERANCE_COSINE,
00297     double curvature_tolerance=ON_SQRT_EPSILON
00298     ) const;
00299 
00300   ON_BOOL32 Reverse(  // reverse parameterizatrion, Domain changes from [a,b] to [-b,-a]
00301     int // dir  0 = "s", 1 = "t"
00302     );
00303 
00304   ON_BOOL32 Transpose(); // transpose surface parameterization (swap "s" and "t")
00305 
00306 
00307   ON_BOOL32 Evaluate( // returns false if unable to evaluate
00308          double, double, // evaluation parameters
00309          int,            // number of derivatives (>=0)
00310          int,            // array stride (>=Dimension())
00311          double*,        // array of length stride*(ndir+1)*(ndir+2)/2
00312          int = 0,        // optional - determines which quadrant to evaluate from
00313                          //         0 = default
00314                          //         1 from NE quadrant
00315                          //         2 from NW quadrant
00316                          //         3 from SW quadrant
00317                          //         4 from SE quadrant
00318          int* = 0        // optional - evaluation hint (int[2]) used to speed
00319                          //            repeated evaluations
00320          ) const;
00321 
00322   /*
00323   Description:
00324     Get isoparametric curve.
00325     Overrides virtual ON_Surface::IsoCurve.
00326   Parameters:
00327     dir - [in] 0 first parameter varies and second parameter is constant
00328                  e.g., point on IsoCurve(0,c) at t is srf(t,c)
00329                1 first parameter is constant and second parameter varies
00330                  e.g., point on IsoCurve(1,c) at t is srf(c,t)
00331 
00332     c - [in] value of constant parameter 
00333   Returns:
00334     Isoparametric curve.
00335   */
00336   ON_Curve* IsoCurve(
00337          int dir,         
00338          double c
00339          ) const;
00340 
00341   /*
00342   Description:
00343     Removes the portions of the surface outside of the specified interval.
00344     Overrides virtual ON_Surface::Trim.
00345 
00346   Parameters:
00347     dir - [in] 0  The domain specifies an sub-interval of Domain(0)
00348                   (the first surface parameter).
00349                1  The domain specifies an sub-interval of Domain(1)
00350                   (the second surface parameter).
00351     domain - [in] interval of the surface to keep. If dir is 0, then
00352         the portions of the surface with parameters (s,t) satisfying
00353         s < Domain(0).Min() or s > Domain(0).Max() are trimmed away.
00354         If dir is 1, then the portions of the surface with parameters
00355         (s,t) satisfying t < Domain(1).Min() or t > Domain(1).Max() 
00356         are trimmed away.
00357   */
00358   ON_BOOL32 Trim(
00359          int dir,
00360          const ON_Interval& domain
00361          );
00362 
00363   /*
00364    Description:
00365      Where possible, analytically extends surface to include domain.
00366    Parameters:
00367      dir - [in] 0  new Domain(0) will include domain.
00368                    (the first surface parameter).
00369                 1  new Domain(1) will include domain.
00370                    (the second surface parameter).
00371      domain - [in] if domain is not included in surface domain, 
00372      surface will be extended so that its domain includes domain.  
00373      Will not work if surface is closed in direction dir. 
00374      Original surface is identical to the restriction of the
00375      resulting surface to the original surface domain, 
00376    Returns:
00377      true if successful.
00378      */
00379   bool Extend(
00380     int dir,
00381     const ON_Interval& domain
00382     );
00383 
00384   /*
00385   Description:
00386     Splits (divides) the surface into two parts at the 
00387     specified parameter.
00388     Overrides virtual ON_Surface::Split.
00389 
00390   Parameters:
00391     dir - [in] 0  The surface is split vertically.  The "west" side
00392                   is returned in "west_or_south_side" and the "east"
00393                   side is returned in "east_or_north_side".
00394                1  The surface is split horizontally.  The "south" side
00395                   is returned in "west_or_south_side" and the "north"
00396                   side is returned in "east_or_north_side".
00397     c - [in] value of constant parameter in interval returned
00398                by Domain(dir)
00399     west_or_south_side - [out] west/south portion of surface returned here
00400     east_or_north_side - [out] east/north portion of surface returned here
00401 
00402   Example:
00403 
00404           ON_PlaneSurface srf = ...;
00405           int dir = 1;
00406           ON_PlaneSurface* south_side = 0;
00407           ON_PlaneSurface* north_side = 0;
00408           srf.Split( dir, srf.Domain(dir).Mid() south_side, north_side );
00409 
00410   */
00411   ON_BOOL32 Split(
00412          int dir,
00413          double c,
00414          ON_Surface*& west_or_south_side,
00415          ON_Surface*& east_or_north_side
00416          ) const;
00417 
00418   /*
00419   Description:
00420     Get the parameters of the point on the surface that is closest to P.
00421   Parameters:
00422     P - [in] 
00423             test point
00424     s - [out]
00425     t - [out] 
00426             (*s,*t) = parameters of the surface point that 
00427             is closest to P.
00428     maximum_distance = 0.0 - [in] 
00429             optional upper bound on the distance from P to 
00430             the surface.  If you are only interested in 
00431             finding a point Q on the surface when 
00432             P.DistanceTo(Q) < maximum_distance, then set
00433             maximum_distance to that value.
00434     sdomain = 0 - [in] optional domain restriction
00435     tdomain = 0 - [in] optional domain restriction
00436   Returns:
00437     True if successful.  If false, the values of *s and *t
00438     are undefined.
00439   See Also:
00440     ON_Surface::GetLocalClosestPoint.
00441   */
00442   bool GetClosestPoint( 
00443           const ON_3dPoint& P,
00444           double* s,
00445           double* t,
00446           double maximum_distance = 0.0,
00447           const ON_Interval* sdomain = 0,
00448           const ON_Interval* tdomain = 0
00449           ) const;
00450 
00452   // Find parameters of the point on a surface that is locally closest to 
00453   // the test_point.  The search for a local close point starts at 
00454   // seed parameters. If a sub_domain parameter is not NULL, then
00455   // the search is restricted to the specified portion of the surface.
00456   //
00457   // true if returned if the search is successful.  false is returned if
00458   // the search fails.
00459   ON_BOOL32 GetLocalClosestPoint( const ON_3dPoint&, // test_point
00460           double,double,     // seed_parameters
00461           double*,double*,   // parameters of local closest point returned here
00462           const ON_Interval* = NULL, // first parameter sub_domain
00463           const ON_Interval* = NULL  // second parameter sub_domain
00464           ) const;
00465 
00466 
00467   /*
00468   Description:
00469     Offset surface.
00470   Parameters:
00471     offset_distance - [in] offset distance
00472     tolerance - [in] Some surfaces do not have an exact offset that
00473       can be represented using the same class of surface definition.
00474       In that case, the tolerance specifies the desired accuracy.
00475     max_deviation - [out] If this parameter is not NULL, the maximum
00476       deviation from the returned offset to the true offset is returned
00477       here.  This deviation is zero except for cases where an exact
00478       offset cannot be computed using the same class of surface definition.
00479   Remarks:
00480     Overrides virtual ON_Surface::Offset.
00481   Returns:
00482     Offset surface.
00483   */
00484   ON_Surface* Offset(
00485         double offset_distance, 
00486         double tolerance, 
00487         double* max_deviation = NULL
00488         ) const;
00489 
00490 
00491   int GetNurbForm( // returns 0: unable to create NURBS representation
00492                    //            with desired accuracy.
00493                    //         1: success - returned NURBS parameterization
00494                    //            matches the surface's to wthe desired accuracy
00495                    //         2: success - returned NURBS point locus matches
00496                    //            the surfaces's to the desired accuracy but, on
00497                    //            the interior of the surface's domain, the 
00498                    //            surface's parameterization and the NURBS
00499                    //            parameterization may not match to the 
00500                    //            desired accuracy.
00501         ON_NurbsSurface&,
00502         double = 0.0
00503         ) const;
00504 
00505   int HasNurbForm( // returns 0: unable to create NURBS representation
00506                    //            with desired accuracy.
00507                    //         1: success - returned NURBS parameterization
00508                    //            matches the surface's to wthe desired accuracy
00509                    //         2: success - returned NURBS point locus matches
00510                    //            the surfaces's to the desired accuracy but, on
00511                    //            the interior of the surface's domain, the 
00512                    //            surface's parameterization and the NURBS
00513                    //            parameterization may not match to the 
00514                    //            desired accuracy.
00515         ) const;
00516 
00517   /*
00518   Description:
00519     Sets the extents of then rectangle.  Does not change the evaluation
00520     domain.
00521   Parameters:
00522     dir - [in] 0 sets plane's x coordinate extents
00523                0 sets plane's y coordinate extents
00524     extents - [in] increasing interval
00525     bSynchDomain - [in] if true, the corresponding evaluation interval
00526                domain is set so that it matches the extents interval
00527   Returns:
00528     True if successful.
00529   See Also:
00530     ON_PlaneSurface::SetDomain
00531   */
00532   bool SetExtents( 
00533          int dir,
00534          ON_Interval extents,
00535          bool bSynchDomain = false
00536          );
00537 
00538   /*
00539   Description:
00540     Gets the extents of the rectangle.
00541   Parameters:
00542     dir - [in] 0 gets plane's x coordinate extents
00543                0 gets plane's y coordinate extents
00544   Returns:
00545     Increasing interval
00546   See Also:
00547     ON_PlaneSurface::Domain
00548   */
00549   ON_Interval Extents(
00550          int dir
00551          ) const;
00552 
00553   /*
00554   Description:
00555     Create a plane that contains the projection of a bounding box.
00556   Parameters:
00557     plane_equation - [in]
00558     bbox - [in]
00559     padding - [in]
00560       amount of extra padding to add around the edges of the
00561       plane.  Default is 0.0625
00562   Returns:
00563     true if successful
00564   */
00565   bool CreatePseudoInfinitePlane( 
00566           ON_PlaneEquation plane_equation,
00567           const ON_BoundingBox& bbox,
00568           double padding = 0.0625
00569           );
00570 
00571   /*
00572   Description:
00573     Create a plane that contains the projection of a bounding box.
00574   Parameters:
00575     plane - [in]
00576     bbox - [in]
00577     padding - [in]
00578       amount of extra padding to add around the edges of the
00579       plane.  Default is 0.0625
00580   Returns:
00581     true if successful
00582   */
00583   bool CreatePseudoInfinitePlane( 
00584           const ON_Plane& plane,
00585           const ON_BoundingBox& bbox,
00586           double padding = 0.0625
00587           );
00588 
00589   /*
00590   Description:
00591     Create a plane that contains the projection of a list of points.
00592   Parameters:
00593     plane - [in]
00594     point_count - [in]
00595     point_list - [in]
00596     padding - [in]
00597       amount of extra padding to add around the edges of the
00598       plane.  Default is 0.0625
00599   Returns:
00600     true if successful
00601   */
00602   bool CreatePseudoInfinitePlane( 
00603           const ON_Plane& plane,
00604           int point_count,
00605           const ON_3dPoint* point_list,
00606           double padding = 0.0625
00607           );
00608 
00609 protected:
00610   // evaluation domain (always increasing)
00611   ON_Interval m_domain[2]; // always increasing
00612 
00613   // rectangle extents (in m_plane x,y coordinates)
00614   ON_Interval m_extents[2];
00615 };
00616 
00617 
00618 class ON_CLASS ON_ClippingPlaneSurface : public ON_PlaneSurface
00619 {
00620   ON_OBJECT_DECLARE(ON_ClippingPlaneSurface);
00621 public:
00622   ON_ClippingPlaneSurface();
00623   ON_ClippingPlaneSurface(const ON_Plane& src);
00624   ON_ClippingPlaneSurface(const ON_PlaneSurface& src);
00625   ~ON_ClippingPlaneSurface();
00626 
00627   ON_ClippingPlaneSurface& operator=(const ON_Plane& src);
00628   ON_ClippingPlaneSurface& operator=(const ON_PlaneSurface& src);
00629 
00630   void Default();
00631 
00632   // override ON_Object::ObjectType() - returns ON::clipplane_object
00633   ON::object_type ObjectType() const;
00634 
00635   // virtual ON_Object::SizeOf override
00636   unsigned int SizeOf() const;
00637 
00638   // virtual ON_Object::DataCRC override
00639   ON__UINT32 DataCRC(ON__UINT32 current_remainder) const;
00640 
00641   // virtual ON_Object::Dump override
00642   void Dump( ON_TextLog& ) const; // for debugging
00643 
00644   // virtual ON_Object::Write override
00645   ON_BOOL32 Write(
00646          ON_BinaryArchive&  // open binary file
00647        ) const;
00648 
00649   // virtual ON_Object::Read override
00650   ON_BOOL32 Read(
00651          ON_BinaryArchive&  // open binary file
00652        );
00653 
00654   ON_ClippingPlane m_clipping_plane;
00655 };
00656 
00657 
00658 #endif


pcl
Author(s): Open Perception
autogenerated on Wed Aug 26 2015 15:27:02