xref: /trunk/main/drawinglayer/source/primitive2d/sceneprimitive2d.cxx (revision e1d5bd03a6ea7ac2b26b792c9e2a94e9f347a43b)
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/primitive2d/sceneprimitive2d.hxx>
28 #include <basegfx/tools/canvastools.hxx>
29 #include <basegfx/polygon/b2dpolygontools.hxx>
30 #include <basegfx/polygon/b2dpolygon.hxx>
31 #include <basegfx/polygon/b2dpolygonclipper.hxx>
32 #include <basegfx/polygon/b2dpolypolygontools.hxx>
33 #include <basegfx/matrix/b2dhommatrix.hxx>
34 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
35 #include <drawinglayer/processor3d/zbufferprocessor3d.hxx>
36 #include <drawinglayer/processor3d/shadow3dextractor.hxx>
37 #include <drawinglayer/geometry/viewinformation2d.hxx>
38 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
39 #include <svtools/optionsdrawinglayer.hxx>
40 #include <drawinglayer/processor3d/geometry2dextractor.hxx>
41 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
42 
43 //////////////////////////////////////////////////////////////////////////////
44 
45 using namespace com::sun::star;
46 
47 //////////////////////////////////////////////////////////////////////////////
48 
49 namespace drawinglayer
50 {
51     namespace primitive2d
52     {
53         bool ScenePrimitive2D::impGetShadow3D(const geometry::ViewInformation2D& /*rViewInformation*/) const
54         {
55             ::osl::MutexGuard aGuard( m_aMutex );
56 
57             // create on demand
58             if(!mbShadow3DChecked && getChildren3D().hasElements())
59             {
60                 basegfx::B3DVector aLightNormal;
61                 const double fShadowSlant(getSdrSceneAttribute().getShadowSlant());
62                 const basegfx::B3DRange aScene3DRange(primitive3d::getB3DRangeFromPrimitive3DSequence(getChildren3D(), getViewInformation3D()));
63 
64                 if(maSdrLightingAttribute.getLightVector().size())
65                 {
66                     // get light normal from first light and normalize
67                     aLightNormal = maSdrLightingAttribute.getLightVector()[0].getDirection();
68                     aLightNormal.normalize();
69                 }
70 
71                 // create shadow extraction processor
72                 processor3d::Shadow3DExtractingProcessor aShadowProcessor(
73                     getViewInformation3D(),
74                     getObjectTransformation(),
75                     aLightNormal,
76                     fShadowSlant,
77                     aScene3DRange);
78 
79                 // process local primitives
80                 aShadowProcessor.process(getChildren3D());
81 
82                 // fetch result and set checked flag
83                 const_cast< ScenePrimitive2D* >(this)->maShadowPrimitives = aShadowProcessor.getPrimitive2DSequence();
84                 const_cast< ScenePrimitive2D* >(this)->mbShadow3DChecked = true;
85             }
86 
87             // return if there are shadow primitives
88             return maShadowPrimitives.hasElements();
89         }
90 
91         void ScenePrimitive2D::calculateDiscreteSizes(
92             const geometry::ViewInformation2D& rViewInformation,
93             basegfx::B2DRange& rDiscreteRange,
94             basegfx::B2DRange& rVisibleDiscreteRange,
95             basegfx::B2DRange& rUnitVisibleRange) const
96         {
97             // use unit range and transform to discrete coordinates
98             rDiscreteRange = basegfx::B2DRange(0.0, 0.0, 1.0, 1.0);
99             rDiscreteRange.transform(rViewInformation.getObjectToViewTransformation() * getObjectTransformation());
100 
101             // clip it against discrete Viewport (if set)
102             rVisibleDiscreteRange = rDiscreteRange;
103 
104             if(!rViewInformation.getViewport().isEmpty())
105             {
106                 rVisibleDiscreteRange.intersect(rViewInformation.getDiscreteViewport());
107             }
108 
109             if(rVisibleDiscreteRange.isEmpty())
110             {
111                 rUnitVisibleRange = rVisibleDiscreteRange;
112             }
113             else
114             {
115                 // create UnitVisibleRange containing unit range values [0.0 .. 1.0] describing
116                 // the relative position of rVisibleDiscreteRange inside rDiscreteRange
117                 const double fDiscreteScaleFactorX(basegfx::fTools::equalZero(rDiscreteRange.getWidth()) ? 1.0 : 1.0 / rDiscreteRange.getWidth());
118                 const double fDiscreteScaleFactorY(basegfx::fTools::equalZero(rDiscreteRange.getHeight()) ? 1.0 : 1.0 / rDiscreteRange.getHeight());
119 
120                 const double fMinX(basegfx::fTools::equal(rVisibleDiscreteRange.getMinX(), rDiscreteRange.getMinX())
121                     ? 0.0
122                     : (rVisibleDiscreteRange.getMinX() - rDiscreteRange.getMinX()) * fDiscreteScaleFactorX);
123                 const double fMinY(basegfx::fTools::equal(rVisibleDiscreteRange.getMinY(), rDiscreteRange.getMinY())
124                     ? 0.0
125                     : (rVisibleDiscreteRange.getMinY() - rDiscreteRange.getMinY()) * fDiscreteScaleFactorY);
126 
127                 const double fMaxX(basegfx::fTools::equal(rVisibleDiscreteRange.getMaxX(), rDiscreteRange.getMaxX())
128                     ? 1.0
129                     : (rVisibleDiscreteRange.getMaxX() - rDiscreteRange.getMinX()) * fDiscreteScaleFactorX);
130                 const double fMaxY(basegfx::fTools::equal(rVisibleDiscreteRange.getMaxY(), rDiscreteRange.getMaxY())
131                     ? 1.0
132                     : (rVisibleDiscreteRange.getMaxY() - rDiscreteRange.getMinY()) * fDiscreteScaleFactorY);
133 
134                 rUnitVisibleRange = basegfx::B2DRange(fMinX, fMinY, fMaxX, fMaxY);
135             }
136         }
137 
138         Primitive2DSequence ScenePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const
139         {
140             Primitive2DSequence aRetval;
141 
142             // create 2D shadows from contained 3D primitives. This creates the shadow primitives on demand and tells if
143             // there are some or not. Do this at start, the shadow might still be visible even when the scene is not
144             if(impGetShadow3D(rViewInformation))
145             {
146                 // test visibility
147                 const basegfx::B2DRange aShadow2DRange(
148                     getB2DRangeFromPrimitive2DSequence(maShadowPrimitives, rViewInformation));
149                 const basegfx::B2DRange aViewRange(
150                     rViewInformation.getViewport());
151 
152                 if(aViewRange.isEmpty() || aShadow2DRange.overlaps(aViewRange))
153                 {
154                     // add extracted 2d shadows (before 3d scene creations itself)
155                     aRetval = maShadowPrimitives;
156                 }
157             }
158 
159             // get the involved ranges (see helper method calculateDiscreteSizes for details)
160             basegfx::B2DRange aDiscreteRange;
161             basegfx::B2DRange aVisibleDiscreteRange;
162             basegfx::B2DRange aUnitVisibleRange;
163 
164             calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange);
165 
166             if(!aVisibleDiscreteRange.isEmpty())
167             {
168                 // test if discrete view size (pixel) maybe too big and limit it
169                 double fViewSizeX(aVisibleDiscreteRange.getWidth());
170                 double fViewSizeY(aVisibleDiscreteRange.getHeight());
171                 const double fViewVisibleArea(fViewSizeX * fViewSizeY);
172                 const SvtOptionsDrawinglayer aDrawinglayerOpt;
173                 const double fMaximumVisibleArea(aDrawinglayerOpt.GetQuadratic3DRenderLimit());
174                 double fReduceFactor(1.0);
175 
176                 if(fViewVisibleArea > fMaximumVisibleArea)
177                 {
178                     fReduceFactor = sqrt(fMaximumVisibleArea / fViewVisibleArea);
179                     fViewSizeX *= fReduceFactor;
180                     fViewSizeY *= fReduceFactor;
181                 }
182 
183                 if(rViewInformation.getReducedDisplayQuality())
184                 {
185                     // when reducing the visualisation is allowed (e.g. an OverlayObject
186                     // only needed for dragging), reduce resolution extra
187                     // to speed up dragging interactions
188                     const double fArea(fViewSizeX * fViewSizeY);
189                     double fReducedVisualisationFactor(1.0 / (sqrt(fArea) * (1.0 / 170.0)));
190 
191                     if(fReducedVisualisationFactor > 1.0)
192                     {
193                         fReducedVisualisationFactor = 1.0;
194                     }
195                     else if(fReducedVisualisationFactor < 0.20)
196                     {
197                         fReducedVisualisationFactor = 0.20;
198                     }
199 
200                     if(fReducedVisualisationFactor != 1.0)
201                     {
202                         fReduceFactor *= fReducedVisualisationFactor;
203                         fViewSizeX *= fReducedVisualisationFactor;
204                         fViewSizeY *= fReducedVisualisationFactor;
205                     }
206                 }
207 
208                 // calculate logic render size in world coordinates for usage in renderer
209                 basegfx::B2DVector aLogicRenderSize(
210                     aDiscreteRange.getWidth() * fReduceFactor,
211                     aDiscreteRange.getHeight() * fReduceFactor);
212                 aLogicRenderSize *= rViewInformation.getInverseObjectToViewTransformation();
213 
214                 // determine the oversample value
215                 static sal_uInt16 nDefaultOversampleValue(3);
216                 const sal_uInt16 nOversampleValue(aDrawinglayerOpt.IsAntiAliasing() ? nDefaultOversampleValue : 0);
217 
218                 // use default 3D primitive processor to create BitmapEx for aUnitVisiblePart and process
219                 processor3d::ZBufferProcessor3D aZBufferProcessor3D(
220                     getViewInformation3D(),
221                     rViewInformation,
222                     getSdrSceneAttribute(),
223                     getSdrLightingAttribute(),
224                     aLogicRenderSize.getX(),
225                     aLogicRenderSize.getY(),
226                     aUnitVisibleRange,
227                     nOversampleValue);
228 
229                 aZBufferProcessor3D.process(getChildren3D());
230                 aZBufferProcessor3D.finish();
231 
232                 const_cast< ScenePrimitive2D* >(this)->maOldRenderedBitmap = aZBufferProcessor3D.getBitmapEx();
233                 const Size aBitmapSizePixel(maOldRenderedBitmap.GetSizePixel());
234 
235                 if(aBitmapSizePixel.getWidth() && aBitmapSizePixel.getHeight())
236                 {
237                     // create transform for the created bitmap in discrete coordinates first.
238                     basegfx::B2DHomMatrix aNew2DTransform;
239 
240                     aNew2DTransform.set(0, 0, aVisibleDiscreteRange.getWidth());
241                     aNew2DTransform.set(1, 1, aVisibleDiscreteRange.getHeight());
242                     aNew2DTransform.set(0, 2, aVisibleDiscreteRange.getMinX());
243                     aNew2DTransform.set(1, 2, aVisibleDiscreteRange.getMinY());
244 
245                     // transform back to world coordinates for usage in primitive creation
246                     aNew2DTransform *= rViewInformation.getInverseObjectToViewTransformation();
247 
248                     // create bitmap primitive and add
249                     const Primitive2DReference xRef(new BitmapPrimitive2D(maOldRenderedBitmap, aNew2DTransform));
250                     appendPrimitive2DReferenceToPrimitive2DSequence(aRetval, xRef);
251 
252                     // test: Allow to add an outline in the debugger when tests are needed
253                     static bool bAddOutlineToCreated3DSceneRepresentation(false);
254 
255                     if(bAddOutlineToCreated3DSceneRepresentation)
256                     {
257                         basegfx::B2DPolygon aOutline(basegfx::tools::createUnitPolygon());
258                         aOutline.transform(aNew2DTransform);
259                         const Primitive2DReference xRef2(new PolygonHairlinePrimitive2D(aOutline, basegfx::BColor(1.0, 0.0, 0.0)));
260                         appendPrimitive2DReferenceToPrimitive2DSequence(aRetval, xRef2);
261                     }
262                 }
263             }
264 
265             return aRetval;
266         }
267 
268         Primitive2DSequence ScenePrimitive2D::getGeometry2D() const
269         {
270             Primitive2DSequence aRetval;
271 
272             // create 2D projected geometry from 3D geometry
273             if(getChildren3D().hasElements())
274             {
275                 // create 2D geometry extraction processor
276                 processor3d::Geometry2DExtractingProcessor aGeometryProcessor(
277                     getViewInformation3D(),
278                     getObjectTransformation());
279 
280                 // process local primitives
281                 aGeometryProcessor.process(getChildren3D());
282 
283                 // fetch result
284                 aRetval = aGeometryProcessor.getPrimitive2DSequence();
285             }
286 
287             return aRetval;
288         }
289 
290         Primitive2DSequence ScenePrimitive2D::getShadow2D(const geometry::ViewInformation2D& rViewInformation) const
291         {
292             Primitive2DSequence aRetval;
293 
294             // create 2D shadows from contained 3D primitives
295             if(impGetShadow3D(rViewInformation))
296             {
297                 // add extracted 2d shadows (before 3d scene creations itself)
298                 aRetval = maShadowPrimitives;
299             }
300 
301             return aRetval;
302         }
303 
304         bool ScenePrimitive2D::tryToCheckLastVisualisationDirectHit(const basegfx::B2DPoint& rLogicHitPoint, bool& o_rResult) const
305         {
306             if(!maOldRenderedBitmap.IsEmpty() && !maOldUnitVisiblePart.isEmpty())
307             {
308                 basegfx::B2DHomMatrix aInverseSceneTransform(getObjectTransformation());
309                 aInverseSceneTransform.invert();
310                 const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * rLogicHitPoint);
311 
312                 if(maOldUnitVisiblePart.isInside(aRelativePoint))
313                 {
314                     // calculate coordinates relative to visualized part
315                     double fDivisorX(maOldUnitVisiblePart.getWidth());
316                     double fDivisorY(maOldUnitVisiblePart.getHeight());
317 
318                     if(basegfx::fTools::equalZero(fDivisorX))
319                     {
320                         fDivisorX = 1.0;
321                     }
322 
323                     if(basegfx::fTools::equalZero(fDivisorY))
324                     {
325                         fDivisorY = 1.0;
326                     }
327 
328                     const double fRelativeX((aRelativePoint.getX() - maOldUnitVisiblePart.getMinX()) / fDivisorX);
329                     const double fRelativeY((aRelativePoint.getY() - maOldUnitVisiblePart.getMinY()) / fDivisorY);
330 
331                     // combine with real BitmapSizePixel to get bitmap coordinates
332                     const Size aBitmapSizePixel(maOldRenderedBitmap.GetSizePixel());
333                     const sal_Int32 nX(basegfx::fround(fRelativeX * aBitmapSizePixel.Width()));
334                     const sal_Int32 nY(basegfx::fround(fRelativeY * aBitmapSizePixel.Height()));
335 
336                     // try to get a statement about transparency in that pixel
337                     o_rResult = (0xff != maOldRenderedBitmap.GetTransparency(nX, nY));
338                     return true;
339                 }
340             }
341 
342             return false;
343         }
344 
345         ScenePrimitive2D::ScenePrimitive2D(
346             const primitive3d::Primitive3DSequence& rxChildren3D,
347             const attribute::SdrSceneAttribute& rSdrSceneAttribute,
348             const attribute::SdrLightingAttribute& rSdrLightingAttribute,
349             const basegfx::B2DHomMatrix& rObjectTransformation,
350             const geometry::ViewInformation3D& rViewInformation3D)
351         :   BufferedDecompositionPrimitive2D(),
352             mxChildren3D(rxChildren3D),
353             maSdrSceneAttribute(rSdrSceneAttribute),
354             maSdrLightingAttribute(rSdrLightingAttribute),
355             maObjectTransformation(rObjectTransformation),
356             maViewInformation3D(rViewInformation3D),
357             maShadowPrimitives(),
358             mbShadow3DChecked(false),
359             mfOldDiscreteSizeX(0.0),
360             mfOldDiscreteSizeY(0.0),
361             maOldUnitVisiblePart(),
362             maOldRenderedBitmap()
363         {
364         }
365 
366         bool ScenePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
367         {
368             if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
369             {
370                 const ScenePrimitive2D& rCompare = (ScenePrimitive2D&)rPrimitive;
371 
372                 return (primitive3d::arePrimitive3DSequencesEqual(getChildren3D(), rCompare.getChildren3D())
373                     && getSdrSceneAttribute() == rCompare.getSdrSceneAttribute()
374                     && getSdrLightingAttribute() == rCompare.getSdrLightingAttribute()
375                     && getObjectTransformation() == rCompare.getObjectTransformation()
376                     && getViewInformation3D() == rCompare.getViewInformation3D());
377             }
378 
379             return false;
380         }
381 
382         basegfx::B2DRange ScenePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
383         {
384             // transform unit range to discrete coordinate range
385             basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0);
386             aRetval.transform(rViewInformation.getObjectToViewTransformation() * getObjectTransformation());
387 
388             // force to discrete expanded bounds (it grows, so expanding works perfectly well)
389             aRetval.expand(basegfx::B2DTuple(floor(aRetval.getMinX()), floor(aRetval.getMinY())));
390             aRetval.expand(basegfx::B2DTuple(ceil(aRetval.getMaxX()), ceil(aRetval.getMaxY())));
391 
392             // transform back from discrete (view) to world coordinates
393             aRetval.transform(rViewInformation.getInverseObjectToViewTransformation());
394 
395             // expand by evtl. existing shadow primitives
396             if(impGetShadow3D(rViewInformation))
397             {
398                 const basegfx::B2DRange aShadow2DRange(getB2DRangeFromPrimitive2DSequence(maShadowPrimitives, rViewInformation));
399 
400                 if(!aShadow2DRange.isEmpty())
401                 {
402                     aRetval.expand(aShadow2DRange);
403                 }
404             }
405 
406             return aRetval;
407         }
408 
409         Primitive2DSequence ScenePrimitive2D::get2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const
410         {
411             ::osl::MutexGuard aGuard( m_aMutex );
412 
413             // get the involved ranges (see helper method calculateDiscreteSizes for details)
414             basegfx::B2DRange aDiscreteRange;
415             basegfx::B2DRange aUnitVisibleRange;
416             bool bNeedNewDecomposition(false);
417             bool bDiscreteSizesAreCalculated(false);
418 
419             if(getBuffered2DDecomposition().hasElements())
420             {
421                 basegfx::B2DRange aVisibleDiscreteRange;
422                 calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange);
423                 bDiscreteSizesAreCalculated = true;
424 
425                 // needs to be painted when the new part is not part of the last
426                 // decomposition
427                 if(!maOldUnitVisiblePart.isInside(aUnitVisibleRange))
428                 {
429                     bNeedNewDecomposition = true;
430                 }
431 
432                 // display has changed and cannot be reused when resolution got bigger. It
433                 // can be reused when resolution got smaller, though.
434                 if(!bNeedNewDecomposition)
435                 {
436                     if(basegfx::fTools::more(aDiscreteRange.getWidth(), mfOldDiscreteSizeX) ||
437                         basegfx::fTools::more(aDiscreteRange.getHeight(), mfOldDiscreteSizeY))
438                     {
439                         bNeedNewDecomposition = true;
440                     }
441                 }
442             }
443 
444             if(bNeedNewDecomposition)
445             {
446                 // conditions of last local decomposition have changed, delete
447                 const_cast< ScenePrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DSequence());
448             }
449 
450             if(!getBuffered2DDecomposition().hasElements())
451             {
452                 if(!bDiscreteSizesAreCalculated)
453                 {
454                     basegfx::B2DRange aVisibleDiscreteRange;
455                     calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange);
456                 }
457 
458                 // remember last used NewDiscreteSize and NewUnitVisiblePart
459                 ScenePrimitive2D* pThat = const_cast< ScenePrimitive2D* >(this);
460                 pThat->mfOldDiscreteSizeX = aDiscreteRange.getWidth();
461                 pThat->mfOldDiscreteSizeY = aDiscreteRange.getHeight();
462                 pThat->maOldUnitVisiblePart = aUnitVisibleRange;
463             }
464 
465             // use parent implementation
466             return BufferedDecompositionPrimitive2D::get2DDecomposition(rViewInformation);
467         }
468 
469         // provide unique ID
470         ImplPrimitrive2DIDBlock(ScenePrimitive2D, PRIMITIVE2D_ID_SCENEPRIMITIVE2D)
471 
472     } // end of namespace primitive2d
473 } // end of namespace drawinglayer
474 
475 //////////////////////////////////////////////////////////////////////////////
476 // eof
477