1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <primitive3d/polygontubeprimitive3d.hxx>
21 #include <drawinglayer/attribute/materialattribute3d.hxx>
22 #include <basegfx/matrix/b3dhommatrix.hxx>
23 #include <basegfx/polygon/b3dpolypolygon.hxx>
24 #include <drawinglayer/primitive3d/polypolygonprimitive3d.hxx>
25 #include <basegfx/polygon/b3dpolypolygontools.hxx>
26 #include <drawinglayer/primitive3d/transformprimitive3d.hxx>
27 #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx>
28 #include <rtl/instance.hxx>
29 
30 
31 namespace drawinglayer::primitive3d
32 {
33         namespace // anonymous namespace
34         {
35             class TubeBuffer
36             {
37             private:
38                 // data for buffered tube primitives
39                 Primitive3DContainer m_aLineTubeList;
40                 sal_uInt32 m_nLineTubeSegments;
41                 attribute::MaterialAttribute3D m_aLineMaterial;
42                 ::osl::Mutex m_aMutex;
43             public:
TubeBuffer()44                 TubeBuffer()
45                     : m_nLineTubeSegments(0)
46                 {
47                 }
48 
49                 TubeBuffer(const TubeBuffer&) = delete;
50                 const TubeBuffer& operator=(const TubeBuffer&) = delete;
51 
getLineTubeSegments(sal_uInt32 nSegments,const attribute::MaterialAttribute3D & rMaterial)52                 Primitive3DContainer getLineTubeSegments(
53                     sal_uInt32 nSegments,
54                     const attribute::MaterialAttribute3D& rMaterial)
55                 {
56                     // may exclusively change cached data, use mutex
57                     ::osl::MutexGuard aGuard(m_aMutex);
58 
59                     if (nSegments != m_nLineTubeSegments || !(rMaterial == m_aLineMaterial))
60                     {
61                         m_nLineTubeSegments = nSegments;
62                         m_aLineMaterial = rMaterial;
63                         m_aLineTubeList = Primitive3DContainer();
64                     }
65 
66                     if (m_aLineTubeList.empty() && m_nLineTubeSegments != 0)
67                     {
68                         const basegfx::B3DPoint aLeft(0.0, 0.0, 0.0);
69                         const basegfx::B3DPoint aRight(1.0, 0.0, 0.0);
70                         basegfx::B3DPoint aLastLeft(0.0, 1.0, 0.0);
71                         basegfx::B3DPoint aLastRight(1.0, 1.0, 0.0);
72                         basegfx::B3DHomMatrix aRot;
73                         aRot.rotate(F_2PI / static_cast<double>(m_nLineTubeSegments), 0.0, 0.0);
74                         m_aLineTubeList.resize(m_nLineTubeSegments);
75 
76                         for(sal_uInt32 a = 0; a < m_nLineTubeSegments; ++a)
77                         {
78                             const basegfx::B3DPoint aNextLeft(aRot * aLastLeft);
79                             const basegfx::B3DPoint aNextRight(aRot * aLastRight);
80                             basegfx::B3DPolygon aNewPolygon;
81 
82                             aNewPolygon.append(aNextLeft);
83                             aNewPolygon.setNormal(0, basegfx::B3DVector(aNextLeft - aLeft));
84 
85                             aNewPolygon.append(aLastLeft);
86                             aNewPolygon.setNormal(1, basegfx::B3DVector(aLastLeft - aLeft));
87 
88                             aNewPolygon.append(aLastRight);
89                             aNewPolygon.setNormal(2, basegfx::B3DVector(aLastRight - aRight));
90 
91                             aNewPolygon.append(aNextRight);
92                             aNewPolygon.setNormal(3, basegfx::B3DVector(aNextRight - aRight));
93 
94                             aNewPolygon.setClosed(true);
95 
96                             const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon);
97                             const Primitive3DReference xRef(new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, m_aLineMaterial, false));
98                             m_aLineTubeList[a] = xRef;
99 
100                             aLastLeft = aNextLeft;
101                             aLastRight = aNextRight;
102                         }
103                     }
104                     return m_aLineTubeList;
105                 }
106             };
107 
108             struct theTubeBuffer :
109                 public rtl::Static< TubeBuffer, theTubeBuffer > {};
110 
getLineTubeSegments(sal_uInt32 nSegments,const attribute::MaterialAttribute3D & rMaterial)111             Primitive3DContainer getLineTubeSegments(
112                 sal_uInt32 nSegments,
113                 const attribute::MaterialAttribute3D& rMaterial)
114             {
115                 // static data for buffered tube primitives
116                 TubeBuffer &rTheBuffer = theTubeBuffer::get();
117                 return rTheBuffer.getLineTubeSegments(nSegments, rMaterial);
118             }
119 
120             class CapBuffer
121             {
122             private:
123                 // data for buffered cap primitives
124                 Primitive3DContainer m_aLineCapList;
125                 sal_uInt32 m_nLineCapSegments;
126                 attribute::MaterialAttribute3D m_aLineMaterial;
127                 ::osl::Mutex m_aMutex;
128             public:
CapBuffer()129                 CapBuffer()
130                     : m_nLineCapSegments(0)
131                 {
132                 }
133                 CapBuffer(const CapBuffer&) = delete;
134                 const CapBuffer& operator=(const CapBuffer&) = delete;
135 
getLineCapSegments(sal_uInt32 nSegments,const attribute::MaterialAttribute3D & rMaterial)136                 Primitive3DContainer getLineCapSegments(
137                     sal_uInt32 nSegments,
138                     const attribute::MaterialAttribute3D& rMaterial)
139                 {
140                     // may exclusively change cached data, use mutex
141                     ::osl::MutexGuard aGuard(m_aMutex);
142 
143                     if (nSegments != m_nLineCapSegments || !(rMaterial == m_aLineMaterial))
144                     {
145                         m_nLineCapSegments = nSegments;
146                         m_aLineMaterial = rMaterial;
147                         m_aLineCapList = Primitive3DContainer();
148                     }
149 
150                     if (m_aLineCapList.empty() && m_nLineCapSegments != 0)
151                     {
152                         const basegfx::B3DPoint aNull(0.0, 0.0, 0.0);
153                         basegfx::B3DPoint aLast(0.0, 1.0, 0.0);
154                         basegfx::B3DHomMatrix aRot;
155                         aRot.rotate(F_2PI / static_cast<double>(m_nLineCapSegments), 0.0, 0.0);
156                         m_aLineCapList.resize(m_nLineCapSegments);
157 
158                         for(sal_uInt32 a = 0; a < m_nLineCapSegments; ++a)
159                         {
160                             const basegfx::B3DPoint aNext(aRot * aLast);
161                             basegfx::B3DPolygon aNewPolygon;
162 
163                             aNewPolygon.append(aLast);
164                             aNewPolygon.setNormal(0, basegfx::B3DVector(aLast - aNull));
165 
166                             aNewPolygon.append(aNext);
167                             aNewPolygon.setNormal(1, basegfx::B3DVector(aNext - aNull));
168 
169                             aNewPolygon.append(aNull);
170                             aNewPolygon.setNormal(2, basegfx::B3DVector(-1.0, 0.0, 0.0));
171 
172                             aNewPolygon.setClosed(true);
173 
174                             const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon);
175                             const Primitive3DReference xRef(new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, m_aLineMaterial, false));
176                             m_aLineCapList[a] = xRef;
177 
178                             aLast = aNext;
179                         }
180                     }
181 
182                     return m_aLineCapList;
183                 }
184             };
185 
186             struct theCapBuffer :
187                 public rtl::Static< CapBuffer, theCapBuffer > {};
188 
getLineCapSegments(sal_uInt32 nSegments,const attribute::MaterialAttribute3D & rMaterial)189             Primitive3DContainer getLineCapSegments(
190                 sal_uInt32 nSegments,
191                 const attribute::MaterialAttribute3D& rMaterial)
192             {
193                 // static data for buffered cap primitives
194                 CapBuffer &rTheBuffer = theCapBuffer::get();
195                 return rTheBuffer.getLineCapSegments(nSegments, rMaterial);
196             }
197 
198             class CapRoundBuffer
199             {
200             private:
201                 // data for buffered capround primitives
202                 Primitive3DContainer m_aLineCapRoundList;
203                 sal_uInt32 m_nLineCapRoundSegments;
204                 attribute::MaterialAttribute3D m_aLineMaterial;
205                 ::osl::Mutex m_aMutex;
206             public:
CapRoundBuffer()207                 CapRoundBuffer()
208                     : m_nLineCapRoundSegments(0)
209                 {
210                 }
211                 CapRoundBuffer(const CapRoundBuffer&) = delete;
212                 const CapRoundBuffer& operator=(const CapRoundBuffer&) = delete;
213 
getLineCapRoundSegments(sal_uInt32 nSegments,const attribute::MaterialAttribute3D & rMaterial)214                 Primitive3DContainer getLineCapRoundSegments(
215                     sal_uInt32 nSegments,
216                     const attribute::MaterialAttribute3D& rMaterial)
217                 {
218                     // may exclusively change cached data, use mutex
219                     ::osl::MutexGuard aGuard(m_aMutex);
220 
221                     if (nSegments != m_nLineCapRoundSegments || !(rMaterial == m_aLineMaterial))
222                     {
223                         m_nLineCapRoundSegments = nSegments;
224                         m_aLineMaterial = rMaterial;
225                         m_aLineCapRoundList = Primitive3DContainer();
226                     }
227 
228                     if (m_aLineCapRoundList.empty() && m_nLineCapRoundSegments)
229                     {
230                         // calculate new horizontal segments
231                         sal_uInt32 nVerSeg(nSegments / 2);
232 
233                         if (nVerSeg < 1)
234                         {
235                             nVerSeg = 1;
236                         }
237 
238                         // create half-sphere; upper half of unit sphere
239                         basegfx::B3DPolyPolygon aSphere(
240                             basegfx::utils::createUnitSphereFillPolyPolygon(
241                                 nSegments,
242                                 nVerSeg,
243                                 true,
244                                 F_PI2, 0.0,
245                                 0.0, F_2PI));
246                         const sal_uInt32 nCount(aSphere.count());
247 
248                         if (nCount)
249                         {
250                             // rotate to have sphere cap oriented to negative X-Axis; do not
251                             // forget to transform normals, too
252                             basegfx::B3DHomMatrix aSphereTrans;
253 
254                             aSphereTrans.rotate(0.0, 0.0, F_PI2);
255                             aSphere.transform(aSphereTrans);
256                             aSphere.transformNormals(aSphereTrans);
257 
258                             // realloc for primitives and create based on polygon snippets
259                             m_aLineCapRoundList.resize(nCount);
260 
261                             for (sal_uInt32 a = 0; a < nCount; ++a)
262                             {
263                                 const basegfx::B3DPolygon& aPartPolygon(aSphere.getB3DPolygon(a));
264                                 const basegfx::B3DPolyPolygon aPartPolyPolygon(aPartPolygon);
265 
266                                 // need to create one primitive per Polygon since the primitive
267                                 // is for planar PolyPolygons which is definitely not the case here
268                                 m_aLineCapRoundList[a] = new PolyPolygonMaterialPrimitive3D(
269                                     aPartPolyPolygon,
270                                     rMaterial,
271                                     false);
272                             }
273                         }
274                     }
275 
276                     return m_aLineCapRoundList;
277                 }
278 
279             };
280 
281             struct theCapRoundBuffer :
282                 public rtl::Static< CapRoundBuffer, theCapRoundBuffer > {};
283 
284 
getLineCapRoundSegments(sal_uInt32 nSegments,const attribute::MaterialAttribute3D & rMaterial)285             Primitive3DContainer getLineCapRoundSegments(
286                 sal_uInt32 nSegments,
287                 const attribute::MaterialAttribute3D& rMaterial)
288             {
289                 // static data for buffered cap primitives
290                 CapRoundBuffer &rTheBuffer = theCapRoundBuffer::get();
291                 return rTheBuffer.getLineCapRoundSegments(nSegments, rMaterial);
292             }
293 
getLineJoinSegments(sal_uInt32 nSegments,const attribute::MaterialAttribute3D & rMaterial,double fAngle,double fMiterMinimumAngle,basegfx::B2DLineJoin aLineJoin)294             Primitive3DContainer getLineJoinSegments(
295                 sal_uInt32 nSegments,
296                 const attribute::MaterialAttribute3D& rMaterial,
297                 double fAngle,
298                 double fMiterMinimumAngle,
299                 basegfx::B2DLineJoin aLineJoin)
300             {
301                 // nSegments is for whole circle, adapt to half circle
302                 const sal_uInt32 nVerSeg(nSegments >> 1);
303                 std::vector< BasePrimitive3D* > aResultVector;
304 
305                 if(nVerSeg)
306                 {
307                     if(basegfx::B2DLineJoin::Round == aLineJoin)
308                     {
309                         // calculate new horizontal segments
310                         const sal_uInt32 nHorSeg(basegfx::fround((fAngle / F_2PI) * static_cast<double>(nSegments)));
311 
312                         if(nHorSeg)
313                         {
314                             // create half-sphere
315                             const basegfx::B3DPolyPolygon aSphere(basegfx::utils::createUnitSphereFillPolyPolygon(nHorSeg, nVerSeg, true, F_PI2, -F_PI2, 0.0, fAngle));
316 
317                             for(sal_uInt32 a(0); a < aSphere.count(); a++)
318                             {
319                                 const basegfx::B3DPolygon& aPartPolygon(aSphere.getB3DPolygon(a));
320                                 const basegfx::B3DPolyPolygon aPartPolyPolygon(aPartPolygon);
321                                 aResultVector.push_back(new PolyPolygonMaterialPrimitive3D(aPartPolyPolygon, rMaterial, false));
322                             }
323                         }
324                         else
325                         {
326                             // fallback to bevel when there is not at least one segment hor and ver
327                             aLineJoin = basegfx::B2DLineJoin::Bevel;
328                         }
329                     }
330 
331                     if (basegfx::B2DLineJoin::Bevel == aLineJoin ||
332                         basegfx::B2DLineJoin::Miter == aLineJoin)
333                     {
334                         if(basegfx::B2DLineJoin::Miter == aLineJoin)
335                         {
336                             const double fMiterAngle(fAngle/2.0);
337 
338                             if(fMiterAngle < fMiterMinimumAngle)
339                             {
340                                 // fallback to bevel when miter's angle is too small
341                                 aLineJoin = basegfx::B2DLineJoin::Bevel;
342                             }
343                         }
344 
345                         const double fInc(F_PI / static_cast<double>(nVerSeg));
346                         const double fSin(sin(-fAngle));
347                         const double fCos(cos(-fAngle));
348                         const bool bMiter(basegfx::B2DLineJoin::Miter == aLineJoin);
349                         const double fMiterSin(bMiter ? sin(-(fAngle/2.0)) : 0.0);
350                         const double fMiterCos(bMiter ? cos(-(fAngle/2.0)) : 0.0);
351                         double fPos(-F_PI2);
352                         basegfx::B3DPoint aPointOnXY, aPointRotY, aNextPointOnXY, aNextPointRotY;
353                         basegfx::B3DPoint aCurrMiter, aNextMiter;
354                         basegfx::B3DPolygon aNewPolygon, aMiterPolygon;
355 
356                         // close polygon
357                         aNewPolygon.setClosed(true);
358                         aMiterPolygon.setClosed(true);
359 
360                         for(sal_uInt32 a(0); a < nVerSeg; a++)
361                         {
362                             const bool bFirst(0 == a);
363                             const bool bLast(a + 1 == nVerSeg);
364 
365                             if(bFirst || !bLast)
366                             {
367                                 fPos += fInc;
368 
369                                 aNextPointOnXY = basegfx::B3DPoint(
370                                     cos(fPos),
371                                     sin(fPos),
372                                     0.0);
373 
374                                 aNextPointRotY = basegfx::B3DPoint(
375                                     aNextPointOnXY.getX() * fCos,
376                                     aNextPointOnXY.getY(),
377                                     aNextPointOnXY.getX() * fSin);
378 
379                                 if(bMiter)
380                                 {
381                                     aNextMiter = basegfx::B3DPoint(
382                                         aNextPointOnXY.getX(),
383                                         aNextPointOnXY.getY(),
384                                         fMiterSin * (aNextPointOnXY.getX() / fMiterCos));
385                                 }
386                             }
387 
388                             if(bFirst)
389                             {
390                                 aNewPolygon.clear();
391 
392                                 if(bMiter)
393                                 {
394                                     aNewPolygon.append(basegfx::B3DPoint(0.0, -1.0, 0.0));
395                                     aNewPolygon.append(aNextPointOnXY);
396                                     aNewPolygon.append(aNextMiter);
397 
398                                     aMiterPolygon.clear();
399                                     aMiterPolygon.append(basegfx::B3DPoint(0.0, -1.0, 0.0));
400                                     aMiterPolygon.append(aNextMiter);
401                                     aMiterPolygon.append(aNextPointRotY);
402                                 }
403                                 else
404                                 {
405                                     aNewPolygon.append(basegfx::B3DPoint(0.0, -1.0, 0.0));
406                                     aNewPolygon.append(aNextPointOnXY);
407                                     aNewPolygon.append(aNextPointRotY);
408                                 }
409                             }
410                             else if(bLast)
411                             {
412                                 aNewPolygon.clear();
413 
414                                 if(bMiter)
415                                 {
416                                     aNewPolygon.append(basegfx::B3DPoint(0.0, 1.0, 0.0));
417                                     aNewPolygon.append(aCurrMiter);
418                                     aNewPolygon.append(aPointOnXY);
419 
420                                     aMiterPolygon.clear();
421                                     aMiterPolygon.append(basegfx::B3DPoint(0.0, 1.0, 0.0));
422                                     aMiterPolygon.append(aPointRotY);
423                                     aMiterPolygon.append(aCurrMiter);
424                                 }
425                                 else
426                                 {
427                                     aNewPolygon.append(basegfx::B3DPoint(0.0, 1.0, 0.0));
428                                     aNewPolygon.append(aPointRotY);
429                                     aNewPolygon.append(aPointOnXY);
430                                 }
431                             }
432                             else
433                             {
434                                 aNewPolygon.clear();
435 
436                                 if(bMiter)
437                                 {
438                                     aNewPolygon.append(aPointOnXY);
439                                     aNewPolygon.append(aNextPointOnXY);
440                                     aNewPolygon.append(aNextMiter);
441                                     aNewPolygon.append(aCurrMiter);
442 
443                                     aMiterPolygon.clear();
444                                     aMiterPolygon.append(aCurrMiter);
445                                     aMiterPolygon.append(aNextMiter);
446                                     aMiterPolygon.append(aNextPointRotY);
447                                     aMiterPolygon.append(aPointRotY);
448                                 }
449                                 else
450                                 {
451                                     aNewPolygon.append(aPointRotY);
452                                     aNewPolygon.append(aPointOnXY);
453                                     aNewPolygon.append(aNextPointOnXY);
454                                     aNewPolygon.append(aNextPointRotY);
455                                 }
456                             }
457 
458                             // set normals
459                             for(sal_uInt32 b(0); b < aNewPolygon.count(); b++)
460                             {
461                                 aNewPolygon.setNormal(b, basegfx::B3DVector(aNewPolygon.getB3DPoint(b)));
462                             }
463 
464                             // create primitive
465                             if(aNewPolygon.count())
466                             {
467                                 const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon);
468                                 aResultVector.push_back(new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, rMaterial, false));
469                             }
470 
471                             if(bMiter && aMiterPolygon.count())
472                             {
473                                 // set normals
474                                 for(sal_uInt32 c(0); c < aMiterPolygon.count(); c++)
475                                 {
476                                     aMiterPolygon.setNormal(c, basegfx::B3DVector(aMiterPolygon.getB3DPoint(c)));
477                                 }
478 
479                                 // create primitive
480                                 const basegfx::B3DPolyPolygon aMiterPolyPolygon(aMiterPolygon);
481                                 aResultVector.push_back(new PolyPolygonMaterialPrimitive3D(aMiterPolyPolygon, rMaterial, false));
482                             }
483 
484                             // prepare next step
485                             if(bFirst || !bLast)
486                             {
487                                 aPointOnXY = aNextPointOnXY;
488                                 aPointRotY = aNextPointRotY;
489 
490                                 if(bMiter)
491                                 {
492                                     aCurrMiter = aNextMiter;
493                                 }
494                             }
495                         }
496                     }
497                 }
498 
499                 Primitive3DContainer aRetval(aResultVector.size());
500 
501                 for(size_t a(0); a < aResultVector.size(); a++)
502                 {
503                     aRetval[a] = Primitive3DReference(aResultVector[a]);
504                 }
505 
506                 return aRetval;
507             }
508 
getRotationFromVector(const basegfx::B3DVector & rVector)509             basegfx::B3DHomMatrix getRotationFromVector(const basegfx::B3DVector& rVector)
510             {
511                 // build transformation from unit vector to vector
512                 basegfx::B3DHomMatrix aRetval;
513 
514                 // get applied rotations from angles in XY and in XZ (cartesian)
515                 const double fRotInXY(atan2(rVector.getY(), rVector.getXZLength()));
516                 const double fRotInXZ(atan2(-rVector.getZ(), rVector.getX()));
517 
518                 // apply rotations. Rot around Z needs to be done first, so apply in two steps
519                 aRetval.rotate(0.0, 0.0, fRotInXY);
520                 aRetval.rotate(0.0, fRotInXZ, 0.0);
521 
522                 return aRetval;
523             }
524         } // end of anonymous namespace
525 
526 
527 using namespace com::sun::star;
528 
impCreate3DDecomposition(const geometry::ViewInformation3D &) const529         Primitive3DContainer PolygonTubePrimitive3D::impCreate3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const
530         {
531             const sal_uInt32 nPointCount(getB3DPolygon().count());
532             std::vector< BasePrimitive3D* > aResultVector;
533 
534             if(nPointCount)
535             {
536                 if(basegfx::fTools::more(getRadius(), 0.0))
537                 {
538                     const attribute::MaterialAttribute3D aMaterial(getBColor());
539                     static const sal_uInt32 nSegments(8); // default for 3d line segments, for more quality just raise this value (in even steps)
540                     const bool bClosed(getB3DPolygon().isClosed());
541                     const bool bNoLineJoin(basegfx::B2DLineJoin::NONE == getLineJoin());
542                     const sal_uInt32 nLoopCount(bClosed ? nPointCount : nPointCount - 1);
543                     basegfx::B3DPoint aLast(getB3DPolygon().getB3DPoint(nPointCount - 1));
544                     basegfx::B3DPoint aCurr(getB3DPolygon().getB3DPoint(0));
545 
546                     for(sal_uInt32 a(0); a < nLoopCount; a++)
547                     {
548                         // get next data
549                         const basegfx::B3DPoint aNext(getB3DPolygon().getB3DPoint((a + 1) % nPointCount));
550                         const basegfx::B3DVector aForw(aNext - aCurr);
551                         const double fForwLen(aForw.getLength());
552 
553                         if(basegfx::fTools::more(fForwLen, 0.0))
554                         {
555                             // find out if linecap is active
556                             const bool bFirst(!a);
557                             const bool bLast(a + 1 == nLoopCount);
558                             const bool bLineCapPossible(!bClosed && (bFirst || bLast));
559                             const bool bLineCapRound(bLineCapPossible && css::drawing::LineCap_ROUND == getLineCap());
560                             const bool bLineCapSquare(bLineCapPossible && css::drawing::LineCap_SQUARE == getLineCap());
561 
562                             // get rotation from vector, this describes rotation from (1, 0, 0) to aForw
563                             basegfx::B3DHomMatrix aRotVector(getRotationFromVector(aForw));
564 
565                             // prepare transformations for tube and cap
566                             basegfx::B3DHomMatrix aTubeTrans;
567                             basegfx::B3DHomMatrix aCapTrans;
568 
569                             // cap gets radius size
570                             aCapTrans.scale(getRadius(), getRadius(), getRadius());
571 
572                             if(bLineCapSquare)
573                             {
574                                 // when square line cap just prolong line segment in X, maybe 2 x radius when
575                                 // first and last (simple line segment)
576                                 const double fExtraLength(bFirst && bLast ? getRadius() * 2.0 : getRadius());
577 
578                                 aTubeTrans.scale(fForwLen + fExtraLength, getRadius(), getRadius());
579 
580                                 if(bFirst)
581                                 {
582                                     // correct start positions for tube and cap when first and square prolonged
583                                     aTubeTrans.translate(-getRadius(), 0.0, 0.0);
584                                     aCapTrans.translate(-getRadius(), 0.0, 0.0);
585                                 }
586                             }
587                             else
588                             {
589                                 // normal tube size
590                                 aTubeTrans.scale(fForwLen, getRadius(), getRadius());
591                             }
592 
593                             // rotate and translate tube and cap
594                             aTubeTrans *= aRotVector;
595                             aTubeTrans.translate(aCurr.getX(), aCurr.getY(), aCurr.getZ());
596                             aCapTrans *= aRotVector;
597                             aCapTrans.translate(aCurr.getX(), aCurr.getY(), aCurr.getZ());
598 
599                             if(bNoLineJoin || (!bClosed && bFirst))
600                             {
601                                 // line start edge, build transformed primitiveVector3D
602                                 Primitive3DContainer aSequence;
603 
604                                 if(bLineCapRound && bFirst)
605                                 {
606                                     // LineCapRound used
607                                     aSequence = getLineCapRoundSegments(nSegments, aMaterial);
608                                 }
609                                 else
610                                 {
611                                     // simple closing cap
612                                     aSequence = getLineCapSegments(nSegments, aMaterial);
613                                 }
614 
615                                 aResultVector.push_back(new TransformPrimitive3D(aCapTrans, aSequence));
616                             }
617                             else
618                             {
619                                 const basegfx::B3DVector aBack(aCurr - aLast);
620                                 const double fCross(basegfx::cross(aBack, aForw).getLength());
621 
622                                 if(!basegfx::fTools::equalZero(fCross))
623                                 {
624                                     // line connect non-parallel, aBack, aForw, use getLineJoin()
625                                     const double fAngle(acos(aBack.scalar(aForw) / (fForwLen * aBack.getLength()))); // 0.0 .. F_PI2
626                                     Primitive3DContainer aNewList(
627                                         getLineJoinSegments(
628                                             nSegments,
629                                             aMaterial,
630                                             fAngle,
631                                             getMiterMinimumAngle(),
632                                             getLineJoin()));
633 
634                                     // calculate transformation. First, get angle in YZ between nForw projected on (1, 0, 0) and nBack
635                                     basegfx::B3DHomMatrix aInvRotVector(aRotVector);
636                                     aInvRotVector.invert();
637                                     basegfx::B3DVector aTransBack(aInvRotVector * aBack);
638                                     const double fRotInYZ(atan2(aTransBack.getY(), aTransBack.getZ()));
639 
640                                     // create trans by rotating unit sphere with angle 90 degrees around Y, then 180-fRot in X.
641                                     // Also apply usual scaling and translation
642                                     basegfx::B3DHomMatrix aSphereTrans;
643                                     aSphereTrans.rotate(0.0, F_PI2, 0.0);
644                                     aSphereTrans.rotate(F_PI - fRotInYZ, 0.0, 0.0);
645                                     aSphereTrans *= aRotVector;
646                                     aSphereTrans.scale(getRadius(), getRadius(), getRadius());
647                                     aSphereTrans.translate(aCurr.getX(), aCurr.getY(), aCurr.getZ());
648 
649                                     // line start edge, build transformed primitiveVector3D
650                                     aResultVector.push_back(
651                                         new TransformPrimitive3D(
652                                             aSphereTrans,
653                                             aNewList));
654                                 }
655                             }
656 
657                             // create line segments, build transformed primitiveVector3D
658                             aResultVector.push_back(
659                                 new TransformPrimitive3D(
660                                     aTubeTrans,
661                                     getLineTubeSegments(nSegments, aMaterial)));
662 
663                             if(bNoLineJoin || (!bClosed && bLast))
664                             {
665                                 // line end edge
666                                 basegfx::B3DHomMatrix aBackCapTrans;
667 
668                                 // Mirror (line end) and radius scale
669                                 aBackCapTrans.rotate(0.0, F_PI, 0.0);
670                                 aBackCapTrans.scale(getRadius(), getRadius(), getRadius());
671 
672                                 if(bLineCapSquare && bLast)
673                                 {
674                                     // correct position when square and prolonged
675                                     aBackCapTrans.translate(fForwLen + getRadius(), 0.0, 0.0);
676                                 }
677                                 else
678                                 {
679                                     // standard position
680                                     aBackCapTrans.translate(fForwLen, 0.0, 0.0);
681                                 }
682 
683                                 // rotate and translate to destination
684                                 aBackCapTrans *= aRotVector;
685                                 aBackCapTrans.translate(aCurr.getX(), aCurr.getY(), aCurr.getZ());
686 
687                                 // get primitiveVector3D
688                                 Primitive3DContainer aSequence;
689 
690                                 if(bLineCapRound && bLast)
691                                 {
692                                     // LineCapRound used
693                                     aSequence = getLineCapRoundSegments(nSegments, aMaterial);
694                                 }
695                                 else
696                                 {
697                                     // simple closing cap
698                                     aSequence = getLineCapSegments(nSegments, aMaterial);
699                                 }
700 
701                                 aResultVector.push_back(
702                                     new TransformPrimitive3D(
703                                         aBackCapTrans,
704                                         aSequence));
705                             }
706                         }
707 
708                         // prepare next loop step
709                         aLast = aCurr;
710                         aCurr = aNext;
711                     }
712                 }
713                 else
714                 {
715                     // create hairline
716                     aResultVector.push_back(new PolygonHairlinePrimitive3D(getB3DPolygon(), getBColor()));
717                 }
718             }
719 
720             // prepare return value
721             Primitive3DContainer aRetval(aResultVector.size());
722 
723             for(size_t a(0); a < aResultVector.size(); a++)
724             {
725                 aRetval[a] = Primitive3DReference(aResultVector[a]);
726             }
727 
728             return aRetval;
729         }
730 
PolygonTubePrimitive3D(const basegfx::B3DPolygon & rPolygon,const basegfx::BColor & rBColor,double fRadius,basegfx::B2DLineJoin aLineJoin,css::drawing::LineCap aLineCap,double fDegreeStepWidth,double fMiterMinimumAngle)731         PolygonTubePrimitive3D::PolygonTubePrimitive3D(
732             const basegfx::B3DPolygon& rPolygon,
733             const basegfx::BColor& rBColor,
734             double fRadius, basegfx::B2DLineJoin aLineJoin,
735             css::drawing::LineCap aLineCap,
736             double fDegreeStepWidth,
737             double fMiterMinimumAngle)
738         :   PolygonHairlinePrimitive3D(rPolygon, rBColor),
739             maLast3DDecomposition(),
740             mfRadius(fRadius),
741             mfDegreeStepWidth(fDegreeStepWidth),
742             mfMiterMinimumAngle(fMiterMinimumAngle),
743             maLineJoin(aLineJoin),
744             maLineCap(aLineCap)
745         {
746         }
747 
operator ==(const BasePrimitive3D & rPrimitive) const748         bool PolygonTubePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const
749         {
750             if(PolygonHairlinePrimitive3D::operator==(rPrimitive))
751             {
752                 const PolygonTubePrimitive3D& rCompare = static_cast<const PolygonTubePrimitive3D&>(rPrimitive);
753 
754                 return (getRadius() == rCompare.getRadius()
755                     && getDegreeStepWidth() == rCompare.getDegreeStepWidth()
756                     && getMiterMinimumAngle() == rCompare.getMiterMinimumAngle()
757                     && getLineJoin() == rCompare.getLineJoin()
758                     && getLineCap() == rCompare.getLineCap());
759             }
760 
761             return false;
762         }
763 
get3DDecomposition(const geometry::ViewInformation3D & rViewInformation) const764         Primitive3DContainer PolygonTubePrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const
765         {
766             ::osl::MutexGuard aGuard( m_aMutex );
767 
768             if(getLast3DDecomposition().empty())
769             {
770                 const Primitive3DContainer aNewSequence(impCreate3DDecomposition(rViewInformation));
771                 const_cast< PolygonTubePrimitive3D* >(this)->maLast3DDecomposition = aNewSequence;
772             }
773 
774             return getLast3DDecomposition();
775         }
776 
777         // provide unique ID
778         ImplPrimitive3DIDBlock(PolygonTubePrimitive3D, PRIMITIVE3D_ID_POLYGONTUBEPRIMITIVE3D)
779 
780 }
781 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
782