0030595: Oriented Bounding Box seems not optimal for some shapes
[occt.git] / src / BRepBndLib / BRepBndLib_1.cxx
CommitLineData
1a0339b4 1// Copyright (c) 1999-2017 OPEN CASCADE SAS
2//
3// This file is part of Open CASCADE Technology software library.
4//
5// This library is free software; you can redistribute it and/or modify it under
6// the terms of the GNU Lesser General Public License version 2.1 as published
7// by the Free Software Foundation, with special exception defined in the file
8// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
9// distribution for complete text of the license and disclaimer of any warranty.
10//
11// Alternatively, this file may be used under the terms of Open CASCADE
12// commercial license or contractual agreement.
13
14#include <Adaptor3d_HCurve.hxx>
15#include <Adaptor3d_HSurface.hxx>
16#include <GeomAdaptor_HCurve.hxx>
17#include <BRepBndLib.hxx>
18#include <GProp_GProps.hxx>
19#include <TopoDS_Shape.hxx>
20#include <BRep_Tool.hxx>
21#include <TopoDS.hxx>
22#include <Bnd_OBB.hxx>
23#include <BRepGProp.hxx>
24#include <TopExp_Explorer.hxx>
25#include <GProp_PrincipalProps.hxx>
26#include <gp_Ax3.hxx>
27#include <BRepBuilderAPI_Transform.hxx>
28#include <Bnd_Box.hxx>
29#include <NCollection_List.hxx>
30#include <TColgp_Array1OfPnt.hxx>
31#include <TColStd_Array1OfReal.hxx>
32#include <Geom_Plane.hxx>
33#include <Geom_Line.hxx>
34#include <TColStd_Array1OfInteger.hxx>
35#include <BRepAdaptor_Curve.hxx>
36#include <BRepAdaptor_HSurface.hxx>
37
38#include <Geom_OffsetCurve.hxx>
39#include <Geom_BSplineCurve.hxx>
40#include <Geom_BezierCurve.hxx>
41#include <Geom_BSplineSurface.hxx>
42#include <Geom_BezierSurface.hxx>
43
44//=======================================================================
45// Function : IsLinear
46// purpose : Returns TRUE if theC is line-like.
47//=======================================================================
48static Standard_Boolean IsLinear(const Adaptor3d_Curve& theC)
49{
50 const GeomAbs_CurveType aCT = theC.GetType();
51 if(aCT == GeomAbs_OffsetCurve)
52 {
53 return IsLinear(GeomAdaptor_Curve(theC.OffsetCurve()->BasisCurve()));
54 }
55
56 if((aCT == GeomAbs_BSplineCurve) || (aCT == GeomAbs_BezierCurve))
57 {
58 // Indeed, curves with C0-continuity and degree==1, may be
59 // represented with set of points. It will be possible made
60 // in the future.
61
62 return ((theC.Degree() == 1) &&
63 (theC.Continuity() != GeomAbs_C0));
64 }
65
66 if(aCT == GeomAbs_Line)
67 {
68 return Standard_True;
69 }
70
71 return Standard_False;
72}
73
74//=======================================================================
75// Function : IsPlanar
76// purpose : Returns TRUE if theS is plane-like.
77//=======================================================================
78static Standard_Boolean IsPlanar(const Adaptor3d_Surface& theS)
79{
80 const GeomAbs_SurfaceType aST = theS.GetType();
81 if(aST == GeomAbs_OffsetSurface)
82 {
83 return IsPlanar(theS.BasisSurface()->Surface());
84 }
85
86 if(aST == GeomAbs_SurfaceOfExtrusion)
87 {
88 return IsLinear(theS.BasisCurve()->Curve());
89 }
90
91 if((aST == GeomAbs_BSplineSurface) || (aST == GeomAbs_BezierSurface))
92 {
93 if((theS.UDegree() != 1) || (theS.VDegree() != 1))
94 return Standard_False;
95
96 // Indeed, surfaces with C0-continuity and degree==1, may be
97 // represented with set of points. It will be possible made
98 // in the future.
99
100 return ((theS.UContinuity() != GeomAbs_C0) && (theS.VContinuity() != GeomAbs_C0));
101 }
102
103 if(aST == GeomAbs_Plane)
104 {
105 return Standard_True;
106 }
107
108 return Standard_False;
109}
110
111//=======================================================================
112// Function : PointsForOBB
113// purpose : Returns number of points for array.
114//
115// Attention!!!
116// 1. Start index for thePts must be 0 strictly.
117// 2. Currently, infinite edges/faces (e.g. half-space) are not
118// processed correctly because computation of UV-bounds is a costly operation.
119//=======================================================================
120static Standard_Integer PointsForOBB(const TopoDS_Shape& theS,
121 const Standard_Boolean theIsTriangulationUsed,
122 TColgp_Array1OfPnt* thePts = 0,
123 TColStd_Array1OfReal* theArrOfToler = 0)
124{
125 Standard_Integer aRetVal = 0;
126 TopExp_Explorer anExpF, anExpE;
127
128 // get all vertices from the shape
129 for(anExpF.Init(theS, TopAbs_VERTEX); anExpF.More(); anExpF.Next())
130 {
131 const TopoDS_Vertex &aVert = TopoDS::Vertex(anExpF.Current());
132 if(thePts)
133 {
134 const gp_Pnt aP = BRep_Tool::Pnt(aVert);
135 (*thePts)(aRetVal) = aP;
136 }
137
138 if(theArrOfToler)
139 {
140 (*theArrOfToler) (aRetVal) = BRep_Tool::Tolerance(aVert);
141 }
142
143 ++aRetVal;
144 }
145
146 if(aRetVal == 0)
147 return 0;
148
149 // analyze the faces of the shape on planarity and existence of triangulation
150 TopLoc_Location aLoc;
151 for(anExpF.Init(theS, TopAbs_FACE); anExpF.More(); anExpF.Next())
152 {
153 const TopoDS_Face &aF = TopoDS::Face(anExpF.Current());
154 const BRepAdaptor_Surface anAS(aF, Standard_False);
155
156 if (!IsPlanar(anAS.Surface()))
157 {
158 if (!theIsTriangulationUsed)
159 // not planar and triangulation usage disabled
160 return 0;
161 }
162 else
163 {
164 // planar face
165 for(anExpE.Init(aF, TopAbs_EDGE); anExpE.More(); anExpE.Next())
166 {
167 const TopoDS_Edge &anE = TopoDS::Edge(anExpE.Current());
87a64d53 168 if (BRep_Tool::IsGeometric (anE))
1a0339b4 169 {
87a64d53 170 const BRepAdaptor_Curve anAC(anE);
171 if (!IsLinear(anAC))
172 {
173 if (!theIsTriangulationUsed)
174 // not linear and triangulation usage disabled
175 return 0;
176
177 break;
178 }
1a0339b4 179 }
180 }
181
182 if (!anExpE.More())
183 // skip planar face with linear edges as its vertices have already been added
184 continue;
185 }
186
187 // Use triangulation of the face
188 const Handle(Poly_Triangulation) &aTrng = BRep_Tool::Triangulation(aF, aLoc);
189 if (aTrng.IsNull())
190 // no triangulation on the face
191 return 0;
192
193 const Standard_Integer aCNode = aTrng->NbNodes();
194 const TColgp_Array1OfPnt& aNodesArr = aTrng->Nodes();
195 for (Standard_Integer i = 1; i <= aCNode; i++)
196 {
197 if (thePts)
198 {
199 const gp_Pnt aP = aLoc.IsIdentity() ? aNodesArr(i) :
200 aNodesArr(i).Transformed(aLoc);
201 (*thePts)(aRetVal) = aP;
202 }
203
204 if (theArrOfToler)
205 {
206 (*theArrOfToler) (aRetVal) = aTrng->Deflection();
207 }
208
209 ++aRetVal;
210 }
211 }
212
213 // Consider edges without faces
214
215 for(anExpE.Init(theS, TopAbs_EDGE, TopAbs_FACE); anExpE.More(); anExpE.Next())
216 {
217 const TopoDS_Edge &anE = TopoDS::Edge(anExpE.Current());
87a64d53 218 if (BRep_Tool::IsGeometric (anE))
219 {
220 const BRepAdaptor_Curve anAC(anE);
221 if (IsLinear(anAC))
222 {
223 // skip linear edge as its vertices have already been added
224 continue;
225 }
226 }
1a0339b4 227
228 if (!theIsTriangulationUsed)
229 // not linear and triangulation usage disabled
230 return 0;
231
232 const Handle(Poly_Polygon3D) &aPolygon = BRep_Tool::Polygon3D(anE, aLoc);
233 if (aPolygon.IsNull())
234 return 0;
235
236 const Standard_Integer aCNode = aPolygon->NbNodes();
237 const TColgp_Array1OfPnt& aNodesArr = aPolygon->Nodes();
238 for (Standard_Integer i = 1; i <= aCNode; i++)
239 {
240 if (thePts)
241 {
242 const gp_Pnt aP = aLoc.IsIdentity() ? aNodesArr(i) :
243 aNodesArr(i).Transformed(aLoc);
244 (*thePts)(aRetVal) = aP;
245 }
246
247 if (theArrOfToler)
248 {
249 (*theArrOfToler) (aRetVal) = aPolygon->Deflection();
250 }
251
252 ++aRetVal;
253 }
254 }
255
256 return aRetVal;
257}
258
259//=======================================================================
260// Function : IsWCS
261// purpose : Returns 0 if the theDir does not match any axis of WCS.
262// Otherwise, returns the index of correspond axis.
263//=======================================================================
264static Standard_Integer IsWCS(const gp_Dir& theDir)
265{
266 const Standard_Real aToler = Precision::Angular()*Precision::Angular();
267
268 const Standard_Real aX = theDir.X(),
269 aY = theDir.Y(),
270 aZ = theDir.Z();
271
272 const Standard_Real aVx = aY*aY + aZ*aZ,
273 aVy = aX*aX + aZ*aZ,
274 aVz = aX*aX + aY*aY;
275
276 if(aVz < aToler)
277 return 3; // Z-axis
278
279 if(aVy < aToler)
280 return 2; // Y-axis
281
282 if(aVx < aToler)
283 return 1; // X-axis
284
285 return 0;
286}
287
288//=======================================================================
289// Function : CheckPoints
290// purpose : Collects points for DiTO algorithm for OBB construction on
291// linear/planar shapes and shapes having triangulation
292// (http://www.idt.mdh.se/~tla/publ/FastOBBs.pdf).
293//=======================================================================
294static Standard_Boolean CheckPoints(const TopoDS_Shape& theS,
295 const Standard_Boolean theIsTriangulationUsed,
1bb67d38 296 const Standard_Boolean theIsOptimal,
1a0339b4 297 const Standard_Boolean theIsShapeToleranceUsed,
298 Bnd_OBB& theOBB)
299{
300 const Standard_Integer aNbPnts = PointsForOBB(theS, theIsTriangulationUsed);
301
302 if(aNbPnts < 1)
303 return Standard_False;
304
305 TColgp_Array1OfPnt anArrPnts(0, theOBB.IsVoid() ? aNbPnts - 1 : aNbPnts + 7);
306 TColStd_Array1OfReal anArrOfTolerances;
307 if(theIsShapeToleranceUsed)
308 {
309 anArrOfTolerances.Resize(anArrPnts.Lower(), anArrPnts.Upper(), Standard_False);
310 anArrOfTolerances.Init(0.0);
311 }
312
313 TColStd_Array1OfReal *aPtrArrTol = theIsShapeToleranceUsed ? &anArrOfTolerances : 0;
314
315 PointsForOBB(theS, theIsTriangulationUsed, &anArrPnts, aPtrArrTol);
316
317 if(!theOBB.IsVoid())
318 {
319 // All points of old OBB have zero-tolerance
320 theOBB.GetVertex(&anArrPnts(aNbPnts));
321 }
322
323#if 0
324 for(Standard_Integer i = anArrPnts.Lower(); i <= anArrPnts.Upper(); i++)
325 {
326 const gp_Pnt &aP = anArrPnts(i);
327 std::cout << "point p" << i << " " << aP.X() << ", " <<
328 aP.Y() << ", " <<
329 aP.Z() << ", "<< std::endl;
330 }
331#endif
332
1bb67d38 333 theOBB.ReBuild(anArrPnts, aPtrArrTol, theIsOptimal);
1a0339b4 334
335 return (!theOBB.IsVoid());
336}
337
338//=======================================================================
339// Function : ComputeProperties
340// purpose : Computes properties of theS.
341//=======================================================================
342static void ComputeProperties(const TopoDS_Shape& theS,
343 GProp_GProps& theGCommon)
344{
345 TopExp_Explorer anExp;
346 for(anExp.Init(theS, TopAbs_SOLID); anExp.More(); anExp.Next())
347 {
348 GProp_GProps aG;
349 BRepGProp::VolumeProperties(anExp.Current(), aG, Standard_True);
350 theGCommon.Add(aG);
351 }
352
353 for(anExp.Init(theS, TopAbs_FACE, TopAbs_SOLID); anExp.More(); anExp.Next())
354 {
355 GProp_GProps aG;
356 BRepGProp::SurfaceProperties(anExp.Current(), aG, Standard_True);
357 theGCommon.Add(aG);
358 }
359
360 for(anExp.Init(theS, TopAbs_EDGE, TopAbs_FACE); anExp.More(); anExp.Next())
361 {
362 GProp_GProps aG;
363 BRepGProp::LinearProperties(anExp.Current(), aG, Standard_True);
364 theGCommon.Add(aG);
365 }
366
367 for(anExp.Init(theS, TopAbs_VERTEX, TopAbs_EDGE); anExp.More(); anExp.Next())
368 {
369 GProp_GProps aG(BRep_Tool::Pnt(TopoDS::Vertex(anExp.Current())));
370 theGCommon.Add(aG);
371 }
372}
373
374//=======================================================================
375// Function : ComputePCA
376// purpose : Creates OBB with axes of inertia.
377//=======================================================================
378static void ComputePCA(const TopoDS_Shape& theS,
379 Bnd_OBB& theOBB,
380 const Standard_Boolean theIsTriangulationUsed,
381 const Standard_Boolean theIsOptimal,
382 const Standard_Boolean theIsShapeToleranceUsed)
383{
384 // Compute the transformation matrix to obtain more tight bounding box
385 GProp_GProps aGCommon;
386 ComputeProperties(theS, aGCommon);
387
388 // Transform the shape to the local coordinate system
389 gp_Trsf aTrsf;
390
391 const Standard_Integer anIdx1 =
392 IsWCS(aGCommon.PrincipalProperties().FirstAxisOfInertia());
393 const Standard_Integer anIdx2 =
394 IsWCS(aGCommon.PrincipalProperties().SecondAxisOfInertia());
395
396 if((anIdx1 == 0) || (anIdx2 == 0))
397 {
398 // Coordinate system in which the shape will have the optimal bounding box
399 gp_Ax3 aLocCoordSys(aGCommon.CentreOfMass(),
400 aGCommon.PrincipalProperties().ThirdAxisOfInertia(),
401 aGCommon.PrincipalProperties().FirstAxisOfInertia());
402 aTrsf.SetTransformation(aLocCoordSys);
403 }
404
405 const TopoDS_Shape aST = (aTrsf.Form() == gp_Identity) ? theS :
406 theS.Moved(TopLoc_Location(aTrsf));
407
408 // Initial axis-aligned BndBox
409 Bnd_Box aShapeBox;
410 if(theIsOptimal)
411 {
412 BRepBndLib::AddOptimal(aST, aShapeBox, theIsTriangulationUsed, theIsShapeToleranceUsed);
413 }
414 else
415 {
416 BRepBndLib::Add(aST, aShapeBox);
417 }
0939d4cf 418 if (aShapeBox.IsVoid())
419 {
420 return;
421 }
1a0339b4 422
423 gp_Pnt aPMin = aShapeBox.CornerMin();
424 gp_Pnt aPMax = aShapeBox.CornerMax();
425
426 gp_XYZ aXDir(1, 0, 0);
427 gp_XYZ aYDir(0, 1, 0);
428 gp_XYZ aZDir(0, 0, 1);
429
430 // Compute the center of the box
431 gp_XYZ aCenter = (aPMin.XYZ() + aPMax.XYZ()) / 2.;
432
433 // Compute the half diagonal size of the box.
434 // It takes into account the gap.
435 gp_XYZ anOBBHSize = (aPMax.XYZ() - aPMin.XYZ()) / 2.;
436
437 // Apply transformation if necessary
438 if(aTrsf.Form() != gp_Identity)
439 {
440 aTrsf.Invert();
441 aTrsf.Transforms(aCenter);
442
443 // Make transformation
444 const Standard_Real * aMat = &aTrsf.HVectorialPart().Value(1, 1);
445 // Compute axes directions of the box
446 aXDir = gp_XYZ(aMat[0], aMat[3], aMat[6]);
447 aYDir = gp_XYZ(aMat[1], aMat[4], aMat[7]);
448 aZDir = gp_XYZ(aMat[2], aMat[5], aMat[8]);
449 }
450
451 if(theOBB.IsVoid())
452 {
453 // Create the OBB box
454
455 // Set parameters to the OBB
456 theOBB.SetCenter(aCenter);
457
458 theOBB.SetXComponent(aXDir, anOBBHSize.X());
459 theOBB.SetYComponent(aYDir, anOBBHSize.Y());
460 theOBB.SetZComponent(aZDir, anOBBHSize.Z());
461 theOBB.SetAABox(aTrsf.Form() == gp_Identity);
462 }
463 else
464 {
465 // Recreate the OBB box
466
467 TColgp_Array1OfPnt aListOfPnts(0, 15);
468 theOBB.GetVertex(&aListOfPnts(0));
469
470 const Standard_Real aX = anOBBHSize.X();
471 const Standard_Real aY = anOBBHSize.Y();
472 const Standard_Real aZ = anOBBHSize.Z();
473
474 const gp_XYZ aXext = aX*aXDir,
475 aYext = aY*aYDir,
476 aZext = aZ*aZDir;
477
478 Standard_Integer aPntIdx = 8;
479 aListOfPnts(aPntIdx++) = aCenter - aXext - aYext - aZext;
480 aListOfPnts(aPntIdx++) = aCenter + aXext - aYext - aZext;
481 aListOfPnts(aPntIdx++) = aCenter - aXext + aYext - aZext;
482 aListOfPnts(aPntIdx++) = aCenter + aXext + aYext - aZext;
483 aListOfPnts(aPntIdx++) = aCenter - aXext - aYext + aZext;
484 aListOfPnts(aPntIdx++) = aCenter + aXext - aYext + aZext;
485 aListOfPnts(aPntIdx++) = aCenter - aXext + aYext + aZext;
486 aListOfPnts(aPntIdx++) = aCenter + aXext + aYext + aZext;
487
488 theOBB.ReBuild(aListOfPnts);
489 }
490}
491
492//=======================================================================
493// Function : AddOBB
494// purpose :
495//=======================================================================
496void BRepBndLib::AddOBB(const TopoDS_Shape& theS,
497 Bnd_OBB& theOBB,
498 const Standard_Boolean theIsTriangulationUsed,
499 const Standard_Boolean theIsOptimal,
500 const Standard_Boolean theIsShapeToleranceUsed)
501{
1bb67d38 502 if (CheckPoints(theS, theIsTriangulationUsed, theIsOptimal, theIsShapeToleranceUsed, theOBB))
1a0339b4 503 return;
504
505 ComputePCA(theS, theOBB, theIsTriangulationUsed, theIsOptimal, theIsShapeToleranceUsed);
506}