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/sdrextrudeprimitive3d.hxx> 28 #include <basegfx/matrix/b2dhommatrix.hxx> 29 #include <basegfx/polygon/b2dpolygontools.hxx> 30 #include <basegfx/polygon/b3dpolypolygontools.hxx> 31 #include <drawinglayer/primitive3d/sdrdecompositiontools3d.hxx> 32 #include <basegfx/tools/canvastools.hxx> 33 #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> 34 #include <drawinglayer/geometry/viewinformation3d.hxx> 35 #include <drawinglayer/attribute/sdrfillattribute.hxx> 36 #include <drawinglayer/attribute/sdrlineattribute.hxx> 37 #include <drawinglayer/attribute/sdrshadowattribute.hxx> 38 39 ////////////////////////////////////////////////////////////////////////////// 40 41 using namespace com::sun::star; 42 43 ////////////////////////////////////////////////////////////////////////////// 44 45 namespace drawinglayer 46 { 47 namespace primitive3d 48 { create3DDecomposition(const geometry::ViewInformation3D & rViewInformation) const49 Primitive3DSequence SdrExtrudePrimitive3D::create3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const 50 { 51 Primitive3DSequence aRetval; 52 53 // get slices 54 const Slice3DVector& rSliceVector = getSlices(); 55 56 if(rSliceVector.size()) 57 { 58 sal_uInt32 a; 59 60 // decide what to create 61 const ::com::sun::star::drawing::NormalsKind eNormalsKind(getSdr3DObjectAttribute().getNormalsKind()); 62 const bool bCreateNormals(::com::sun::star::drawing::NormalsKind_SPECIFIC == eNormalsKind); 63 const bool bCreateTextureCoordiantesX(::com::sun::star::drawing::TextureProjectionMode_OBJECTSPECIFIC == getSdr3DObjectAttribute().getTextureProjectionX()); 64 const bool bCreateTextureCoordiantesY(::com::sun::star::drawing::TextureProjectionMode_OBJECTSPECIFIC == getSdr3DObjectAttribute().getTextureProjectionY()); 65 double fRelativeTextureWidth(1.0); 66 basegfx::B2DHomMatrix aTexTransform; 67 68 if(!getSdrLFSAttribute().getFill().isDefault() && (bCreateTextureCoordiantesX || bCreateTextureCoordiantesY)) 69 { 70 const basegfx::B2DPolygon aFirstPolygon(maCorrectedPolyPolygon.getB2DPolygon(0L)); 71 const double fFrontLength(basegfx::tools::getLength(aFirstPolygon)); 72 const double fFrontArea(basegfx::tools::getArea(aFirstPolygon)); 73 const double fSqrtFrontArea(sqrt(fFrontArea)); 74 fRelativeTextureWidth = basegfx::fTools::equalZero(fSqrtFrontArea) ? 1.0 : fFrontLength / fSqrtFrontArea; 75 fRelativeTextureWidth = (double)((sal_uInt32)(fRelativeTextureWidth - 0.5)); 76 77 if(fRelativeTextureWidth < 1.0) 78 { 79 fRelativeTextureWidth = 1.0; 80 } 81 82 aTexTransform.translate(-0.5, -0.5); 83 aTexTransform.scale(-1.0, -1.0); 84 aTexTransform.translate(0.5, 0.5); 85 aTexTransform.scale(fRelativeTextureWidth, 1.0); 86 } 87 88 // create geometry 89 ::std::vector< basegfx::B3DPolyPolygon > aFill; 90 extractPlanesFromSlice(aFill, rSliceVector, 91 bCreateNormals, getSmoothHorizontalNormals(), getSmoothNormals(), getSmoothLids(), false, 92 0.5, 0.6, bCreateTextureCoordiantesX || bCreateTextureCoordiantesY, aTexTransform); 93 94 // get full range 95 const basegfx::B3DRange aRange(getRangeFrom3DGeometry(aFill)); 96 97 // normal creation 98 if(!getSdrLFSAttribute().getFill().isDefault()) 99 { 100 if(::com::sun::star::drawing::NormalsKind_SPHERE == eNormalsKind) 101 { 102 applyNormalsKindSphereTo3DGeometry(aFill, aRange); 103 } 104 else if(::com::sun::star::drawing::NormalsKind_FLAT == eNormalsKind) 105 { 106 applyNormalsKindFlatTo3DGeometry(aFill); 107 } 108 109 if(getSdr3DObjectAttribute().getNormalsInvert()) 110 { 111 applyNormalsInvertTo3DGeometry(aFill); 112 } 113 } 114 115 // texture coordinates 116 if(!getSdrLFSAttribute().getFill().isDefault()) 117 { 118 applyTextureTo3DGeometry( 119 getSdr3DObjectAttribute().getTextureProjectionX(), 120 getSdr3DObjectAttribute().getTextureProjectionY(), 121 aFill, 122 aRange, 123 getTextureSize()); 124 } 125 126 if(!getSdrLFSAttribute().getFill().isDefault()) 127 { 128 // add fill 129 aRetval = create3DPolyPolygonFillPrimitives( 130 aFill, 131 getTransform(), 132 getTextureSize(), 133 getSdr3DObjectAttribute(), 134 getSdrLFSAttribute().getFill(), 135 getSdrLFSAttribute().getFillFloatTransGradient()); 136 } 137 else 138 { 139 // create simplified 3d hit test geometry 140 aRetval = createHiddenGeometryPrimitives3D( 141 aFill, 142 getTransform(), 143 getTextureSize(), 144 getSdr3DObjectAttribute()); 145 } 146 147 // add line 148 if(!getSdrLFSAttribute().getLine().isDefault()) 149 { 150 if(getSdr3DObjectAttribute().getReducedLineGeometry()) 151 { 152 // create geometric outlines with reduced line geometry for chart. 153 const basegfx::B3DPolyPolygon aVerLine(extractVerticalLinesFromSlice(rSliceVector)); 154 const sal_uInt32 nCount(aVerLine.count()); 155 basegfx::B3DPolyPolygon aReducedLoops; 156 basegfx::B3DPolyPolygon aNewLineGeometry; 157 158 // sort out doubles (front and back planes when no edge rounding is done). Since 159 // this is a line geometry merged from PolyPolygons, loop over all Polygons 160 for(a = 0; a < nCount; a++) 161 { 162 const sal_uInt32 nReducedCount(aReducedLoops.count()); 163 const basegfx::B3DPolygon aCandidate(aVerLine.getB3DPolygon(a)); 164 bool bAdd(true); 165 166 if(nReducedCount) 167 { 168 for(sal_uInt32 b(0); bAdd && b < nReducedCount; b++) 169 { 170 if(aCandidate == aReducedLoops.getB3DPolygon(b)) 171 { 172 bAdd = false; 173 } 174 } 175 } 176 177 if(bAdd) 178 { 179 aReducedLoops.append(aCandidate); 180 } 181 } 182 183 // from here work with reduced loops and reduced count without changing them 184 const sal_uInt32 nReducedCount(aReducedLoops.count()); 185 186 if(nReducedCount > 1) 187 { 188 for(sal_uInt32 b(1); b < nReducedCount; b++) 189 { 190 // get loop pair 191 const basegfx::B3DPolygon aCandA(aReducedLoops.getB3DPolygon(b - 1)); 192 const basegfx::B3DPolygon aCandB(aReducedLoops.getB3DPolygon(b)); 193 194 // for each loop pair create the connection edges 195 createReducedOutlines( 196 rViewInformation, 197 getTransform(), 198 aCandA, 199 aCandB, 200 aNewLineGeometry); 201 } 202 } 203 204 // add reduced loops themselves 205 aNewLineGeometry.append(aReducedLoops); 206 207 // to create vertical edges at non-C1/C2 steady loops, use maCorrectedPolyPolygon 208 // directly since the 3D Polygons do not suport this. 209 // 210 // Unfortunately there is no bezier polygon provided by the chart module; one reason is 211 // that the API for extrude wants a 3D polygon geometry (for historical reasons, i guess) 212 // and those have no beziers. Another reason is that he chart module uses self-created 213 // stuff to create the 2D geometry (in ShapeFactory::createPieSegment), but this geometry 214 // does not contain bezier infos, either. The only way which is possible for now is to 'detect' 215 // candidates for vertical edges of pie segments by looking for the angles in the polygon. 216 // 217 // This is all not very well designed ATM. Ideally, the ReducedLineGeometry is responsible 218 // for creating the outer geometry edges (createReducedOutlines), but for special edges 219 // like the vertical ones for pie center and both start/end, the incarnation with the 220 // knowledge about that it needs to create those and IS a pie segment -> in this case, 221 // the chart itself. 222 const sal_uInt32 nPolyCount(maCorrectedPolyPolygon.count()); 223 224 for(sal_uInt32 c(0); c < nPolyCount; c++) 225 { 226 const basegfx::B2DPolygon aCandidate(maCorrectedPolyPolygon.getB2DPolygon(c)); 227 const sal_uInt32 nPointCount(aCandidate.count()); 228 229 if(nPointCount > 2) 230 { 231 sal_uInt32 nIndexA(nPointCount); 232 sal_uInt32 nIndexB(nPointCount); 233 sal_uInt32 nIndexC(nPointCount); 234 235 for(sal_uInt32 d(0); d < nPointCount; d++) 236 { 237 const sal_uInt32 nPrevInd((d + nPointCount - 1) % nPointCount); 238 const sal_uInt32 nNextInd((d + 1) % nPointCount); 239 const basegfx::B2DPoint aPoint(aCandidate.getB2DPoint(d)); 240 const basegfx::B2DVector aPrev(aCandidate.getB2DPoint(nPrevInd) - aPoint); 241 const basegfx::B2DVector aNext(aCandidate.getB2DPoint(nNextInd) - aPoint); 242 const double fAngle(aPrev.angle(aNext)); 243 244 // take each angle which deviates more than 10% from going straight as 245 // special edge. This will detect the two outer edges of pie segments, 246 // but not always the center one (think about a near 180 degree pie) 247 if(F_PI - fabs(fAngle) > F_PI * 0.1) 248 { 249 if(nPointCount == nIndexA) 250 { 251 nIndexA = d; 252 } 253 else if(nPointCount == nIndexB) 254 { 255 nIndexB = d; 256 } 257 else if(nPointCount == nIndexC) 258 { 259 nIndexC = d; 260 d = nPointCount; 261 } 262 } 263 } 264 265 const bool bIndexAUsed(nIndexA != nPointCount); 266 const bool bIndexBUsed(nIndexB != nPointCount); 267 bool bIndexCUsed(nIndexC != nPointCount); 268 269 if(bIndexCUsed) 270 { 271 // already three special edges found, so the center one was already detected 272 // and does not need to be searched 273 } 274 else if(bIndexAUsed && bIndexBUsed) 275 { 276 // outer edges detected (they are approx. 90 degrees), but center one not. 277 // Look with the knowledge that it's in-between the two found ones 278 if(((nIndexA + 2) % nPointCount) == nIndexB) 279 { 280 nIndexC = (nIndexA + 1) % nPointCount; 281 } 282 else if(((nIndexA + nPointCount - 2) % nPointCount) == nIndexB) 283 { 284 nIndexC = (nIndexA + nPointCount - 1) % nPointCount; 285 } 286 287 bIndexCUsed = (nIndexC != nPointCount); 288 } 289 290 if(bIndexAUsed) 291 { 292 const basegfx::B2DPoint aPoint(aCandidate.getB2DPoint(nIndexA)); 293 const basegfx::B3DPoint aStart(aPoint.getX(), aPoint.getY(), 0.0); 294 const basegfx::B3DPoint aEnd(aPoint.getX(), aPoint.getY(), getDepth()); 295 basegfx::B3DPolygon aToBeAdded; 296 297 aToBeAdded.append(aStart); 298 aToBeAdded.append(aEnd); 299 aNewLineGeometry.append(aToBeAdded); 300 } 301 302 if(bIndexBUsed) 303 { 304 const basegfx::B2DPoint aPoint(aCandidate.getB2DPoint(nIndexB)); 305 const basegfx::B3DPoint aStart(aPoint.getX(), aPoint.getY(), 0.0); 306 const basegfx::B3DPoint aEnd(aPoint.getX(), aPoint.getY(), getDepth()); 307 basegfx::B3DPolygon aToBeAdded; 308 309 aToBeAdded.append(aStart); 310 aToBeAdded.append(aEnd); 311 aNewLineGeometry.append(aToBeAdded); 312 } 313 314 if(bIndexCUsed) 315 { 316 const basegfx::B2DPoint aPoint(aCandidate.getB2DPoint(nIndexC)); 317 const basegfx::B3DPoint aStart(aPoint.getX(), aPoint.getY(), 0.0); 318 const basegfx::B3DPoint aEnd(aPoint.getX(), aPoint.getY(), getDepth()); 319 basegfx::B3DPolygon aToBeAdded; 320 321 aToBeAdded.append(aStart); 322 aToBeAdded.append(aEnd); 323 aNewLineGeometry.append(aToBeAdded); 324 } 325 } 326 } 327 328 // append loops themselves 329 aNewLineGeometry.append(aReducedLoops); 330 331 if(aNewLineGeometry.count()) 332 { 333 const Primitive3DSequence aLines(create3DPolyPolygonLinePrimitives( 334 aNewLineGeometry, getTransform(), getSdrLFSAttribute().getLine())); 335 appendPrimitive3DSequenceToPrimitive3DSequence(aRetval, aLines); 336 } 337 } 338 else 339 { 340 // extract line geometry from slices 341 const basegfx::B3DPolyPolygon aHorLine(extractHorizontalLinesFromSlice(rSliceVector, false)); 342 const basegfx::B3DPolyPolygon aVerLine(extractVerticalLinesFromSlice(rSliceVector)); 343 344 // add horizontal lines 345 const Primitive3DSequence aHorLines(create3DPolyPolygonLinePrimitives( 346 aHorLine, getTransform(), getSdrLFSAttribute().getLine())); 347 appendPrimitive3DSequenceToPrimitive3DSequence(aRetval, aHorLines); 348 349 // add vertical lines 350 const Primitive3DSequence aVerLines(create3DPolyPolygonLinePrimitives( 351 aVerLine, getTransform(), getSdrLFSAttribute().getLine())); 352 appendPrimitive3DSequenceToPrimitive3DSequence(aRetval, aVerLines); 353 } 354 } 355 356 // add shadow 357 if(!getSdrLFSAttribute().getShadow().isDefault() && aRetval.hasElements()) 358 { 359 const Primitive3DSequence aShadow(createShadowPrimitive3D( 360 aRetval, getSdrLFSAttribute().getShadow(), getSdr3DObjectAttribute().getShadow3D())); 361 appendPrimitive3DSequenceToPrimitive3DSequence(aRetval, aShadow); 362 } 363 } 364 365 return aRetval; 366 } 367 impCreateSlices()368 void SdrExtrudePrimitive3D::impCreateSlices() 369 { 370 // prepare the polygon. No double points, correct orientations and a correct 371 // outmost polygon are needed 372 // Also important: subdivide here to ensure equal point count for all slices (!) 373 maCorrectedPolyPolygon = basegfx::tools::adaptiveSubdivideByAngle(getPolyPolygon()); 374 maCorrectedPolyPolygon.removeDoublePoints(); 375 maCorrectedPolyPolygon = basegfx::tools::correctOrientations(maCorrectedPolyPolygon); 376 maCorrectedPolyPolygon = basegfx::tools::correctOutmostPolygon(maCorrectedPolyPolygon); 377 378 // prepare slices as geometry 379 createExtrudeSlices(maSlices, maCorrectedPolyPolygon, getBackScale(), getDiagonal(), getDepth(), getCharacterMode(), getCloseFront(), getCloseBack()); 380 } 381 getSlices() const382 const Slice3DVector& SdrExtrudePrimitive3D::getSlices() const 383 { 384 // This can be made dependent of getSdrLFSAttribute().getFill() and getSdrLFSAttribute().getLine() 385 // again when no longer geometry is needed for non-visible 3D objects as it is now for chart 386 if(getPolyPolygon().count() && !maSlices.size()) 387 { 388 ::osl::Mutex m_mutex; 389 const_cast< SdrExtrudePrimitive3D& >(*this).impCreateSlices(); 390 } 391 392 return maSlices; 393 } 394 SdrExtrudePrimitive3D(const basegfx::B3DHomMatrix & rTransform,const basegfx::B2DVector & rTextureSize,const attribute::SdrLineFillShadowAttribute3D & rSdrLFSAttribute,const attribute::Sdr3DObjectAttribute & rSdr3DObjectAttribute,const basegfx::B2DPolyPolygon & rPolyPolygon,double fDepth,double fDiagonal,double fBackScale,bool bSmoothNormals,bool bSmoothHorizontalNormals,bool bSmoothLids,bool bCharacterMode,bool bCloseFront,bool bCloseBack)395 SdrExtrudePrimitive3D::SdrExtrudePrimitive3D( 396 const basegfx::B3DHomMatrix& rTransform, 397 const basegfx::B2DVector& rTextureSize, 398 const attribute::SdrLineFillShadowAttribute3D& rSdrLFSAttribute, 399 const attribute::Sdr3DObjectAttribute& rSdr3DObjectAttribute, 400 const basegfx::B2DPolyPolygon& rPolyPolygon, 401 double fDepth, 402 double fDiagonal, 403 double fBackScale, 404 bool bSmoothNormals, 405 bool bSmoothHorizontalNormals, 406 bool bSmoothLids, 407 bool bCharacterMode, 408 bool bCloseFront, 409 bool bCloseBack) 410 : SdrPrimitive3D(rTransform, rTextureSize, rSdrLFSAttribute, rSdr3DObjectAttribute), 411 maCorrectedPolyPolygon(), 412 maSlices(), 413 maPolyPolygon(rPolyPolygon), 414 mfDepth(fDepth), 415 mfDiagonal(fDiagonal), 416 mfBackScale(fBackScale), 417 mpLastRLGViewInformation(0), 418 mbSmoothNormals(bSmoothNormals), 419 mbSmoothHorizontalNormals(bSmoothHorizontalNormals), 420 mbSmoothLids(bSmoothLids), 421 mbCharacterMode(bCharacterMode), 422 mbCloseFront(bCloseFront), 423 mbCloseBack(bCloseBack) 424 { 425 // make sure depth is positive 426 if(basegfx::fTools::lessOrEqual(getDepth(), 0.0)) 427 { 428 mfDepth = 0.0; 429 } 430 431 // make sure the percentage value getDiagonal() is between 0.0 and 1.0 432 if(basegfx::fTools::lessOrEqual(getDiagonal(), 0.0)) 433 { 434 mfDiagonal = 0.0; 435 } 436 else if(basegfx::fTools::moreOrEqual(getDiagonal(), 1.0)) 437 { 438 mfDiagonal = 1.0; 439 } 440 441 // no close front/back when polygon is not closed 442 if(getPolyPolygon().count() && !getPolyPolygon().getB2DPolygon(0L).isClosed()) 443 { 444 mbCloseFront = mbCloseBack = false; 445 } 446 447 // no edge rounding when not closing 448 if(!getCloseFront() && !getCloseBack()) 449 { 450 mfDiagonal = 0.0; 451 } 452 } 453 ~SdrExtrudePrimitive3D()454 SdrExtrudePrimitive3D::~SdrExtrudePrimitive3D() 455 { 456 if(mpLastRLGViewInformation) 457 { 458 delete mpLastRLGViewInformation; 459 } 460 } 461 operator ==(const BasePrimitive3D & rPrimitive) const462 bool SdrExtrudePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const 463 { 464 if(SdrPrimitive3D::operator==(rPrimitive)) 465 { 466 const SdrExtrudePrimitive3D& rCompare = static_cast< const SdrExtrudePrimitive3D& >(rPrimitive); 467 468 return (getPolyPolygon() == rCompare.getPolyPolygon() 469 && getDepth() == rCompare.getDepth() 470 && getDiagonal() == rCompare.getDiagonal() 471 && getBackScale() == rCompare.getBackScale() 472 && getSmoothNormals() == rCompare.getSmoothNormals() 473 && getSmoothHorizontalNormals() == rCompare.getSmoothHorizontalNormals() 474 && getSmoothLids() == rCompare.getSmoothLids() 475 && getCharacterMode() == rCompare.getCharacterMode() 476 && getCloseFront() == rCompare.getCloseFront() 477 && getCloseBack() == rCompare.getCloseBack()); 478 } 479 480 return false; 481 } 482 getB3DRange(const geometry::ViewInformation3D &) const483 basegfx::B3DRange SdrExtrudePrimitive3D::getB3DRange(const geometry::ViewInformation3D& /*rViewInformation*/) const 484 { 485 // use defaut from sdrPrimitive3D which uses transformation expanded by line width/2 486 // The parent implementation which uses the ranges of the decomposition would be more 487 // corrcet, but for historical reasons it is necessary to do the old method: To get 488 // the range of the non-transformed geometry and transform it then. This leads to different 489 // ranges where the new method is more correct, but the need to keep the old behaviour 490 // has priority here. 491 return get3DRangeFromSlices(getSlices()); 492 } 493 get3DDecomposition(const geometry::ViewInformation3D & rViewInformation) const494 Primitive3DSequence SdrExtrudePrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const 495 { 496 if(getSdr3DObjectAttribute().getReducedLineGeometry()) 497 { 498 if(!mpLastRLGViewInformation || 499 (getBuffered3DDecomposition().hasElements() 500 && *mpLastRLGViewInformation != rViewInformation)) 501 { 502 // conditions of last local decomposition with reduced lines have changed. Remember 503 // new one and clear current decompositiopn 504 ::osl::Mutex m_mutex; 505 SdrExtrudePrimitive3D* pThat = const_cast< SdrExtrudePrimitive3D* >(this); 506 pThat->setBuffered3DDecomposition(Primitive3DSequence()); 507 delete pThat->mpLastRLGViewInformation; 508 pThat->mpLastRLGViewInformation = new geometry::ViewInformation3D(rViewInformation); 509 } 510 } 511 512 // no test for buffering needed, call parent 513 return SdrPrimitive3D::get3DDecomposition(rViewInformation); 514 } 515 516 // provide unique ID 517 ImplPrimitrive3DIDBlock(SdrExtrudePrimitive3D, PRIMITIVE3D_ID_SDREXTRUDEPRIMITIVE3D) 518 519 } // end of namespace primitive3d 520 } // end of namespace drawinglayer 521 522 ////////////////////////////////////////////////////////////////////////////// 523 // eof 524