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