1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_drawinglayer.hxx" 26 27 #include <drawinglayer/primitive3d/polygontubeprimitive3d.hxx> 28 #include <drawinglayer/attribute/materialattribute3d.hxx> 29 #include <basegfx/matrix/b3dhommatrix.hxx> 30 #include <basegfx/polygon/b3dpolypolygon.hxx> 31 #include <drawinglayer/primitive3d/polypolygonprimitive3d.hxx> 32 #include <basegfx/polygon/b3dpolypolygontools.hxx> 33 #include <drawinglayer/primitive3d/transformprimitive3d.hxx> 34 #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> 35 36 ////////////////////////////////////////////////////////////////////////////// 37 38 namespace drawinglayer 39 { 40 namespace primitive3d 41 { 42 namespace // anonymous namespace 43 { 44 Primitive3DSequence getLineTubeSegments( 45 sal_uInt32 nSegments, 46 const attribute::MaterialAttribute3D& rMaterial) 47 { 48 // static data for buffered tube primitives 49 static Primitive3DSequence aLineTubeList; 50 static sal_uInt32 nLineTubeSegments(0L); 51 static attribute::MaterialAttribute3D aLineMaterial; 52 53 // may exclusively change static data, use mutex 54 ::osl::Mutex m_mutex; 55 56 if(nSegments != nLineTubeSegments || !(rMaterial == aLineMaterial)) 57 { 58 nLineTubeSegments = nSegments; 59 aLineMaterial = rMaterial; 60 aLineTubeList = Primitive3DSequence(); 61 } 62 63 if(!aLineTubeList.hasElements() && 0L != nLineTubeSegments) 64 { 65 const basegfx::B3DPoint aLeft(0.0, 0.0, 0.0); 66 const basegfx::B3DPoint aRight(1.0, 0.0, 0.0); 67 basegfx::B3DPoint aLastLeft(0.0, 1.0, 0.0); 68 basegfx::B3DPoint aLastRight(1.0, 1.0, 0.0); 69 basegfx::B3DHomMatrix aRot; 70 aRot.rotate(F_2PI / (double)nLineTubeSegments, 0.0, 0.0); 71 aLineTubeList.realloc(nLineTubeSegments); 72 73 for(sal_uInt32 a(0L); a < nLineTubeSegments; a++) 74 { 75 const basegfx::B3DPoint aNextLeft(aRot * aLastLeft); 76 const basegfx::B3DPoint aNextRight(aRot * aLastRight); 77 basegfx::B3DPolygon aNewPolygon; 78 79 aNewPolygon.append(aNextLeft); 80 aNewPolygon.setNormal(0L, basegfx::B3DVector(aNextLeft - aLeft)); 81 82 aNewPolygon.append(aLastLeft); 83 aNewPolygon.setNormal(1L, basegfx::B3DVector(aLastLeft - aLeft)); 84 85 aNewPolygon.append(aLastRight); 86 aNewPolygon.setNormal(2L, basegfx::B3DVector(aLastRight - aRight)); 87 88 aNewPolygon.append(aNextRight); 89 aNewPolygon.setNormal(3L, basegfx::B3DVector(aNextRight - aRight)); 90 91 aNewPolygon.setClosed(true); 92 93 const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); 94 const Primitive3DReference xRef(new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, aLineMaterial, false)); 95 aLineTubeList[a] = xRef; 96 97 aLastLeft = aNextLeft; 98 aLastRight = aNextRight; 99 } 100 } 101 102 return aLineTubeList; 103 } 104 105 Primitive3DSequence getLineCapSegments( 106 sal_uInt32 nSegments, 107 const attribute::MaterialAttribute3D& rMaterial) 108 { 109 // static data for buffered tube primitives 110 static Primitive3DSequence aLineCapList; 111 static sal_uInt32 nLineCapSegments(0L); 112 static attribute::MaterialAttribute3D aLineMaterial; 113 114 // may exclusively change static data, use mutex 115 ::osl::Mutex m_mutex; 116 117 if(nSegments != nLineCapSegments || !(rMaterial == aLineMaterial)) 118 { 119 nLineCapSegments = nSegments; 120 aLineMaterial = rMaterial; 121 aLineCapList = Primitive3DSequence(); 122 } 123 124 if(!aLineCapList.hasElements() && 0L != nLineCapSegments) 125 { 126 const basegfx::B3DPoint aNull(0.0, 0.0, 0.0); 127 basegfx::B3DPoint aLast(0.0, 1.0, 0.0); 128 basegfx::B3DHomMatrix aRot; 129 aRot.rotate(F_2PI / (double)nLineCapSegments, 0.0, 0.0); 130 aLineCapList.realloc(nLineCapSegments); 131 132 for(sal_uInt32 a(0L); a < nLineCapSegments; a++) 133 { 134 const basegfx::B3DPoint aNext(aRot * aLast); 135 basegfx::B3DPolygon aNewPolygon; 136 137 aNewPolygon.append(aLast); 138 aNewPolygon.setNormal(0L, basegfx::B3DVector(aLast - aNull)); 139 140 aNewPolygon.append(aNext); 141 aNewPolygon.setNormal(1L, basegfx::B3DVector(aNext - aNull)); 142 143 aNewPolygon.append(aNull); 144 aNewPolygon.setNormal(2L, basegfx::B3DVector(-1.0, 0.0, 0.0)); 145 146 aNewPolygon.setClosed(true); 147 148 const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); 149 const Primitive3DReference xRef(new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, aLineMaterial, false)); 150 aLineCapList[a] = xRef; 151 152 aLast = aNext; 153 } 154 } 155 156 return aLineCapList; 157 } 158 159 Primitive3DSequence getLineJoinSegments( 160 sal_uInt32 nSegments, 161 const attribute::MaterialAttribute3D& rMaterial, 162 double fAngle, 163 double /*fDegreeStepWidth*/, 164 double fMiterMinimumAngle, 165 basegfx::B2DLineJoin aLineJoin) 166 { 167 // nSegments is for whole circle, adapt to half circle 168 const sal_uInt32 nVerSeg(nSegments >> 1L); 169 std::vector< BasePrimitive3D* > aResultVector; 170 171 if(nVerSeg) 172 { 173 if(basegfx::B2DLINEJOIN_ROUND == aLineJoin) 174 { 175 // calculate new horizontal segments 176 const sal_uInt32 nHorSeg((sal_uInt32)((fAngle / F_2PI) * (double)nSegments)); 177 178 if(nHorSeg) 179 { 180 // create half-sphere 181 const basegfx::B3DPolyPolygon aSphere(basegfx::tools::createUnitSphereFillPolyPolygon(nHorSeg, nVerSeg, true, F_PI2, -F_PI2, 0.0, fAngle)); 182 183 for(sal_uInt32 a(0L); a < aSphere.count(); a++) 184 { 185 const basegfx::B3DPolygon aPartPolygon(aSphere.getB3DPolygon(a)); 186 const basegfx::B3DPolyPolygon aPartPolyPolygon(aPartPolygon); 187 BasePrimitive3D* pNew = new PolyPolygonMaterialPrimitive3D(aPartPolyPolygon, rMaterial, false); 188 aResultVector.push_back(pNew); 189 } 190 } 191 else 192 { 193 // fallback to bevel when there is not at least one segment hor and ver 194 aLineJoin = basegfx::B2DLINEJOIN_BEVEL; 195 } 196 } 197 198 if(basegfx::B2DLINEJOIN_MIDDLE == aLineJoin 199 || basegfx::B2DLINEJOIN_BEVEL == aLineJoin 200 || basegfx::B2DLINEJOIN_MITER == aLineJoin) 201 { 202 if(basegfx::B2DLINEJOIN_MITER == aLineJoin) 203 { 204 const double fMiterAngle(fAngle/2.0); 205 206 if(fMiterAngle < fMiterMinimumAngle) 207 { 208 // fallback to bevel when miter's angle is too small 209 aLineJoin = basegfx::B2DLINEJOIN_BEVEL; 210 } 211 } 212 213 const double fInc(F_PI / (double)nVerSeg); 214 const double fSin(sin(-fAngle)); 215 const double fCos(cos(-fAngle)); 216 const bool bMiter(basegfx::B2DLINEJOIN_MITER == aLineJoin); 217 const double fMiterSin(bMiter ? sin(-(fAngle/2.0)) : 0.0); 218 const double fMiterCos(bMiter ? cos(-(fAngle/2.0)) : 0.0); 219 double fPos(-F_PI2); 220 basegfx::B3DPoint aPointOnXY, aPointRotY, aNextPointOnXY, aNextPointRotY; 221 basegfx::B3DPoint aCurrMiter, aNextMiter; 222 basegfx::B3DPolygon aNewPolygon, aMiterPolygon; 223 224 // close polygon 225 aNewPolygon.setClosed(true); 226 aMiterPolygon.setClosed(true); 227 228 for(sal_uInt32 a(0L); a < nVerSeg; a++) 229 { 230 const bool bFirst(0L == a); 231 const bool bLast(a + 1L == nVerSeg); 232 233 if(bFirst || !bLast) 234 { 235 fPos += fInc; 236 237 aNextPointOnXY = basegfx::B3DPoint( 238 cos(fPos), 239 sin(fPos), 240 0.0); 241 242 aNextPointRotY = basegfx::B3DPoint( 243 aNextPointOnXY.getX() * fCos, 244 aNextPointOnXY.getY(), 245 aNextPointOnXY.getX() * fSin); 246 247 if(bMiter) 248 { 249 aNextMiter = basegfx::B3DPoint( 250 aNextPointOnXY.getX(), 251 aNextPointOnXY.getY(), 252 fMiterSin * (aNextPointOnXY.getX() / fMiterCos)); 253 } 254 } 255 256 if(bFirst) 257 { 258 aNewPolygon.clear(); 259 260 if(bMiter) 261 { 262 aNewPolygon.append(basegfx::B3DPoint(0.0, -1.0, 0.0)); 263 aNewPolygon.append(aNextPointOnXY); 264 aNewPolygon.append(aNextMiter); 265 266 aMiterPolygon.clear(); 267 aMiterPolygon.append(basegfx::B3DPoint(0.0, -1.0, 0.0)); 268 aMiterPolygon.append(aNextMiter); 269 aMiterPolygon.append(aNextPointRotY); 270 } 271 else 272 { 273 aNewPolygon.append(basegfx::B3DPoint(0.0, -1.0, 0.0)); 274 aNewPolygon.append(aNextPointOnXY); 275 aNewPolygon.append(aNextPointRotY); 276 } 277 } 278 else if(bLast) 279 { 280 aNewPolygon.clear(); 281 282 if(bMiter) 283 { 284 aNewPolygon.append(basegfx::B3DPoint(0.0, 1.0, 0.0)); 285 aNewPolygon.append(aCurrMiter); 286 aNewPolygon.append(aPointOnXY); 287 288 aMiterPolygon.clear(); 289 aMiterPolygon.append(basegfx::B3DPoint(0.0, 1.0, 0.0)); 290 aMiterPolygon.append(aPointRotY); 291 aMiterPolygon.append(aCurrMiter); 292 } 293 else 294 { 295 aNewPolygon.append(basegfx::B3DPoint(0.0, 1.0, 0.0)); 296 aNewPolygon.append(aPointRotY); 297 aNewPolygon.append(aPointOnXY); 298 } 299 } 300 else 301 { 302 aNewPolygon.clear(); 303 304 if(bMiter) 305 { 306 aNewPolygon.append(aPointOnXY); 307 aNewPolygon.append(aNextPointOnXY); 308 aNewPolygon.append(aNextMiter); 309 aNewPolygon.append(aCurrMiter); 310 311 aMiterPolygon.clear(); 312 aMiterPolygon.append(aCurrMiter); 313 aMiterPolygon.append(aNextMiter); 314 aMiterPolygon.append(aNextPointRotY); 315 aMiterPolygon.append(aPointRotY); 316 } 317 else 318 { 319 aNewPolygon.append(aPointRotY); 320 aNewPolygon.append(aPointOnXY); 321 aNewPolygon.append(aNextPointOnXY); 322 aNewPolygon.append(aNextPointRotY); 323 } 324 } 325 326 // set normals 327 for(sal_uInt32 b(0L); b < aNewPolygon.count(); b++) 328 { 329 aNewPolygon.setNormal(b, basegfx::B3DVector(aNewPolygon.getB3DPoint(b))); 330 } 331 332 // create primitive 333 if(aNewPolygon.count()) 334 { 335 const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); 336 BasePrimitive3D* pNew = new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, rMaterial, false); 337 aResultVector.push_back(pNew); 338 } 339 340 if(bMiter && aMiterPolygon.count()) 341 { 342 // set normals 343 for(sal_uInt32 c(0L); c < aMiterPolygon.count(); c++) 344 { 345 aMiterPolygon.setNormal(c, basegfx::B3DVector(aMiterPolygon.getB3DPoint(c))); 346 } 347 348 // create primitive 349 const basegfx::B3DPolyPolygon aMiterPolyPolygon(aMiterPolygon); 350 BasePrimitive3D* pNew = new PolyPolygonMaterialPrimitive3D(aMiterPolyPolygon, rMaterial, false); 351 aResultVector.push_back(pNew); 352 } 353 354 // prepare next step 355 if(bFirst || !bLast) 356 { 357 aPointOnXY = aNextPointOnXY; 358 aPointRotY = aNextPointRotY; 359 360 if(bMiter) 361 { 362 aCurrMiter = aNextMiter; 363 } 364 } 365 } 366 } 367 } 368 369 Primitive3DSequence aRetval(aResultVector.size()); 370 371 for(sal_uInt32 a(0L); a < aResultVector.size(); a++) 372 { 373 aRetval[a] = Primitive3DReference(aResultVector[a]); 374 } 375 376 return aRetval; 377 } 378 379 basegfx::B3DHomMatrix getRotationFromVector(const basegfx::B3DVector& rVector) 380 { 381 // build transformation from unit vector to vector 382 basegfx::B3DHomMatrix aRetval; 383 384 // get applied rotations from angles in XY and in XZ (cartesian) 385 const double fRotInXY(atan2(rVector.getY(), rVector.getXZLength())); 386 const double fRotInXZ(atan2(-rVector.getZ(), rVector.getX())); 387 388 // apply rotations. Rot around Z needs to be done first, so apply in two steps 389 aRetval.rotate(0.0, 0.0, fRotInXY); 390 aRetval.rotate(0.0, fRotInXZ, 0.0); 391 392 return aRetval; 393 } 394 } // end of anonymous namespace 395 } // end of namespace primitive3d 396 } // end of namespace drawinglayer 397 398 ////////////////////////////////////////////////////////////////////////////// 399 400 using namespace com::sun::star; 401 402 ////////////////////////////////////////////////////////////////////////////// 403 404 namespace drawinglayer 405 { 406 namespace primitive3d 407 { 408 Primitive3DSequence PolygonTubePrimitive3D::impCreate3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const 409 { 410 const sal_uInt32 nPointCount(getB3DPolygon().count()); 411 std::vector< BasePrimitive3D* > aResultVector; 412 413 if(0L != nPointCount) 414 { 415 if(basegfx::fTools::more(getRadius(), 0.0)) 416 { 417 const attribute::MaterialAttribute3D aMaterial(getBColor()); 418 static sal_uInt32 nSegments(8L); // default for 3d line segments, for more quality just raise this value (in even steps) 419 const bool bClosed(getB3DPolygon().isClosed()); 420 const bool bNoLineJoin(basegfx::B2DLINEJOIN_NONE == getLineJoin()); 421 const sal_uInt32 nLoopCount(bClosed ? nPointCount : nPointCount - 1L); 422 basegfx::B3DPoint aLast(getB3DPolygon().getB3DPoint(nPointCount - 1L)); 423 basegfx::B3DPoint aCurr(getB3DPolygon().getB3DPoint(0L)); 424 425 for(sal_uInt32 a(0L); a < nLoopCount; a++) 426 { 427 // get next data 428 const basegfx::B3DPoint aNext(getB3DPolygon().getB3DPoint((a + 1L) % nPointCount)); 429 const basegfx::B3DVector aForw(aNext - aCurr); 430 const double fForwLen(aForw.getLength()); 431 432 if(basegfx::fTools::more(fForwLen, 0.0)) 433 { 434 // get rotation from vector, this describes rotation from (1, 0, 0) to aForw 435 basegfx::B3DHomMatrix aRotVector(getRotationFromVector(aForw)); 436 437 // create default transformation with scale and rotate 438 basegfx::B3DHomMatrix aVectorTrans; 439 aVectorTrans.scale(fForwLen, getRadius(), getRadius()); 440 aVectorTrans *= aRotVector; 441 aVectorTrans.translate(aCurr.getX(), aCurr.getY(), aCurr.getZ()); 442 443 if(bNoLineJoin || (!bClosed && !a)) 444 { 445 // line start edge, build transformed primitiveVector3D 446 TransformPrimitive3D* pNewTransformedA = new TransformPrimitive3D(aVectorTrans, getLineCapSegments(nSegments, aMaterial)); 447 aResultVector.push_back(pNewTransformedA); 448 } 449 else 450 { 451 const basegfx::B3DVector aBack(aCurr - aLast); 452 const double fCross(basegfx::cross(aBack, aForw).getLength()); 453 454 if(!basegfx::fTools::equalZero(fCross)) 455 { 456 // line connect non-parallel, aBack, aForw, use getLineJoin() 457 const double fAngle(acos(aBack.scalar(aForw) / (fForwLen * aBack.getLength()))); // 0.0 .. F_PI2 458 Primitive3DSequence aNewList(getLineJoinSegments(nSegments, aMaterial, fAngle, getDegreeStepWidth(), getMiterMinimumAngle(), getLineJoin())); 459 460 // calculate transformation. First, get angle in YZ between nForw projected on (1, 0, 0) and nBack 461 basegfx::B3DHomMatrix aInvRotVector(aRotVector); 462 aInvRotVector.invert(); 463 basegfx::B3DVector aTransBack(aInvRotVector * aBack); 464 const double fRotInYZ(atan2(aTransBack.getY(), aTransBack.getZ())); 465 466 // create trans by rotating unit sphere with angle 90 degrees around Y, then 180-fRot in X. 467 // Also apply usual scaling and translation 468 basegfx::B3DHomMatrix aSphereTrans; 469 aSphereTrans.rotate(0.0, F_PI2, 0.0); 470 aSphereTrans.rotate(F_PI - fRotInYZ, 0.0, 0.0); 471 aSphereTrans *= aRotVector; 472 aSphereTrans.scale(getRadius(), getRadius(), getRadius()); 473 aSphereTrans.translate(aCurr.getX(), aCurr.getY(), aCurr.getZ()); 474 475 // line start edge, build transformed primitiveVector3D 476 TransformPrimitive3D* pNewTransformedB = new TransformPrimitive3D(aSphereTrans, aNewList); 477 aResultVector.push_back(pNewTransformedB); 478 } 479 } 480 481 // create line segments, build transformed primitiveVector3D 482 TransformPrimitive3D* pNewTransformedC = new TransformPrimitive3D(aVectorTrans, getLineTubeSegments(nSegments, aMaterial)); 483 aResultVector.push_back(pNewTransformedC); 484 485 if(bNoLineJoin || (!bClosed && ((a + 1L) == nLoopCount))) 486 { 487 // line end edge, first rotate (mirror) and translate, then use use aRotVector 488 basegfx::B3DHomMatrix aBackTrans; 489 aBackTrans.rotate(0.0, F_PI, 0.0); 490 aBackTrans.translate(1.0, 0.0, 0.0); 491 aBackTrans.scale(fForwLen, getRadius(), getRadius()); 492 aBackTrans *= aRotVector; 493 aBackTrans.translate(aCurr.getX(), aCurr.getY(), aCurr.getZ()); 494 495 // line end edge, build transformed primitiveVector3D 496 TransformPrimitive3D* pNewTransformedD = new TransformPrimitive3D(aBackTrans, getLineCapSegments(nSegments, aMaterial)); 497 aResultVector.push_back(pNewTransformedD); 498 } 499 } 500 501 // prepare next loop step 502 aLast = aCurr; 503 aCurr = aNext; 504 } 505 } 506 else 507 { 508 // create hairline 509 PolygonHairlinePrimitive3D* pNew = new PolygonHairlinePrimitive3D(getB3DPolygon(), getBColor()); 510 aResultVector.push_back(pNew); 511 } 512 } 513 514 // prepare return value 515 Primitive3DSequence aRetval(aResultVector.size()); 516 517 for(sal_uInt32 a(0L); a < aResultVector.size(); a++) 518 { 519 aRetval[a] = Primitive3DReference(aResultVector[a]); 520 } 521 522 return aRetval; 523 } 524 525 PolygonTubePrimitive3D::PolygonTubePrimitive3D( 526 const basegfx::B3DPolygon& rPolygon, 527 const basegfx::BColor& rBColor, 528 double fRadius, basegfx::B2DLineJoin aLineJoin, 529 double fDegreeStepWidth, 530 double fMiterMinimumAngle) 531 : PolygonHairlinePrimitive3D(rPolygon, rBColor), 532 maLast3DDecomposition(), 533 mfRadius(fRadius), 534 mfDegreeStepWidth(fDegreeStepWidth), 535 mfMiterMinimumAngle(fMiterMinimumAngle), 536 maLineJoin(aLineJoin) 537 { 538 } 539 540 bool PolygonTubePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const 541 { 542 if(PolygonHairlinePrimitive3D::operator==(rPrimitive)) 543 { 544 const PolygonTubePrimitive3D& rCompare = (PolygonTubePrimitive3D&)rPrimitive; 545 546 return (getRadius() == rCompare.getRadius() 547 && getDegreeStepWidth() == rCompare.getDegreeStepWidth() 548 && getMiterMinimumAngle() == rCompare.getMiterMinimumAngle() 549 && getLineJoin() == rCompare.getLineJoin()); 550 } 551 552 return false; 553 } 554 555 Primitive3DSequence PolygonTubePrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const 556 { 557 ::osl::MutexGuard aGuard( m_aMutex ); 558 559 if(!getLast3DDecomposition().hasElements()) 560 { 561 const Primitive3DSequence aNewSequence(impCreate3DDecomposition(rViewInformation)); 562 const_cast< PolygonTubePrimitive3D* >(this)->setLast3DDecomposition(aNewSequence); 563 } 564 565 return getLast3DDecomposition(); 566 } 567 568 // provide unique ID 569 ImplPrimitrive3DIDBlock(PolygonTubePrimitive3D, PRIMITIVE3D_ID_POLYGONTUBEPRIMITIVE3D) 570 571 } // end of namespace primitive3d 572 } // end of namespace drawinglayer 573 574 ////////////////////////////////////////////////////////////////////////////// 575 // eof 576