Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/svx/source/customshapes/EnhancedCustomShape3d.cxx
Line
Count
Source (jump to first uncovered line)
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 "EnhancedCustomShape3d.hxx"
21
#include <o3tl/unit_conversion.hxx>
22
#include <svx/deflt3d.hxx>
23
#include <svx/svdmodel.hxx>
24
#include <tools/poly.hxx>
25
#include <svx/svditer.hxx>
26
#include <svx/svdobj.hxx>
27
#include <svx/svdoashp.hxx>
28
#include <svl/itemset.hxx>
29
#include <svl/whiter.hxx>
30
#include <svx/xfillit0.hxx>
31
#include <svx/xlineit0.hxx>
32
#include <svx/xsflclit.hxx>
33
#include <svx/xbtmpit.hxx>
34
#include <svx/xflclit.hxx>
35
#include <svx/svdopath.hxx>
36
#include <svx/svddef.hxx>
37
#include <svx/svx3ditems.hxx>
38
#include <extrud3d.hxx>
39
#include <svx/xflbmtit.hxx>
40
#include <svx/xlnclit.hxx>
41
#include <svx/sdasitm.hxx>
42
#include <svx/scene3d.hxx>
43
#include <com/sun/star/drawing/Position3D.hpp>
44
#include <com/sun/star/drawing/Direction3D.hpp>
45
#include <com/sun/star/drawing/NormalsKind.hpp>
46
#include <com/sun/star/drawing/ShadeMode.hpp>
47
#include <svx/sdr/properties/properties.hxx>
48
#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
49
#include <com/sun/star/drawing/EnhancedCustomShapeMetalType.hpp>
50
#include <com/sun/star/drawing/ProjectionMode.hpp>
51
#include <basegfx/color/bcolor.hxx>
52
#include <basegfx/polygon/b2dpolypolygontools.hxx>
53
#include <basegfx/polygon/b3dpolygon.hxx>
54
#include <basegfx/range/b2drange.hxx>
55
#include <sdr/primitive2d/sdrattributecreator.hxx>
56
#include <drawinglayer/attribute/sdrlineattribute.hxx>
57
#include <drawinglayer/attribute/sdrlinestartendattribute.hxx>
58
#include <svx/xlnwtit.hxx>
59
#include <svx/xlntrit.hxx>
60
#include <svx/xfltrit.hxx>
61
#include <comphelper/configuration.hxx>
62
63
using namespace com::sun::star;
64
using namespace com::sun::star::uno;
65
66
namespace {
67
68
void GetOrigin( const SdrCustomShapeGeometryItem& rItem, double& rOriginX, double& rOriginY )
69
500
{
70
500
    css::drawing::EnhancedCustomShapeParameterPair aOriginParaPair;
71
500
    const Any* pAny = rItem.GetPropertyValueByName( u"Extrusion"_ustr, u"Origin"_ustr );
72
500
    if ( ! ( pAny && ( *pAny >>= aOriginParaPair ) && ( aOriginParaPair.First.Value >>= rOriginX ) && ( aOriginParaPair.Second.Value >>= rOriginY ) ) )
73
46
    {
74
46
        rOriginX = 0.50;
75
46
        rOriginY =-0.50;
76
46
    }
77
500
}
78
79
void GetRotateAngle( const SdrCustomShapeGeometryItem& rItem, double& rAngleX, double& rAngleY )
80
3.19k
{
81
3.19k
    css::drawing::EnhancedCustomShapeParameterPair aRotateAngleParaPair;
82
3.19k
    const Any* pAny = rItem.GetPropertyValueByName( u"Extrusion"_ustr, u"RotateAngle"_ustr );
83
3.19k
    if ( ! ( pAny && ( *pAny >>= aRotateAngleParaPair ) && ( aRotateAngleParaPair.First.Value >>= rAngleX ) && ( aRotateAngleParaPair.Second.Value >>= rAngleY ) ) )
84
3.03k
    {
85
3.03k
        rAngleX = 0.0;
86
3.03k
        rAngleY = 0.0;
87
3.03k
    }
88
3.19k
    rAngleX = basegfx::deg2rad(rAngleX);
89
3.19k
    rAngleY = basegfx::deg2rad(rAngleY);
90
3.19k
}
91
92
void GetSkew( const SdrCustomShapeGeometryItem& rItem, double& rSkewAmount, double& rSkewAngle )
93
2.69k
{
94
2.69k
    css::drawing::EnhancedCustomShapeParameterPair aSkewParaPair;
95
2.69k
    const Any* pAny = rItem.GetPropertyValueByName( u"Extrusion"_ustr, u"Skew"_ustr );
96
2.69k
    if ( ! ( pAny && ( *pAny >>= aSkewParaPair ) && ( aSkewParaPair.First.Value >>= rSkewAmount ) && ( aSkewParaPair.Second.Value >>= rSkewAngle ) ) )
97
0
    {
98
0
        rSkewAmount = 50;
99
        // ODF default is 45, but older ODF documents expect -135 as default. For intermediate
100
        // solution see tdf#141301 and tdf#141127.
101
        // MS Office default -135 is set in msdffimp.cxx to make import independent from setting here.
102
0
        rSkewAngle = -135;
103
0
    }
104
2.69k
    rSkewAngle = basegfx::deg2rad(rSkewAngle);
105
2.69k
}
106
107
void GetExtrusionDepth( const SdrCustomShapeGeometryItem& rItem, const double* pMap, double& rBackwardDepth, double& rForwardDepth )
108
3.19k
{
109
3.19k
    css::drawing::EnhancedCustomShapeParameterPair aDepthParaPair;
110
3.19k
    double fDepth = 0, fFraction = 0;
111
3.19k
    const Any* pAny = rItem.GetPropertyValueByName( u"Extrusion"_ustr, u"Depth"_ustr );
112
3.19k
    if ( pAny && ( *pAny >>= aDepthParaPair ) && ( aDepthParaPair.First.Value >>= fDepth ) && ( aDepthParaPair.Second.Value >>= fFraction ) )
113
685
    {
114
685
        rForwardDepth = fDepth * fFraction;
115
685
        rBackwardDepth = fDepth - rForwardDepth;
116
685
    }
117
2.50k
    else
118
2.50k
    {
119
2.50k
        rBackwardDepth = 1270;
120
2.50k
        rForwardDepth = 0;
121
2.50k
    }
122
3.19k
    if ( pMap )
123
0
    {
124
0
        double fMap = *pMap;
125
0
        rBackwardDepth *= fMap;
126
0
        rForwardDepth *= fMap;
127
0
    }
128
3.19k
}
129
130
double GetDouble( const SdrCustomShapeGeometryItem& rItem, const OUString& rPropertyName, double fDefault )
131
19.1k
{
132
19.1k
    double fRetValue = fDefault;
133
19.1k
    const Any* pAny = rItem.GetPropertyValueByName( u"Extrusion"_ustr, rPropertyName );
134
19.1k
    if ( pAny )
135
13.1k
        *pAny >>= fRetValue;
136
19.1k
    return fRetValue;
137
19.1k
}
138
139
drawing::ShadeMode GetShadeMode( const SdrCustomShapeGeometryItem& rItem, const drawing::ShadeMode eDefault )
140
3.19k
{
141
3.19k
    drawing::ShadeMode eRet( eDefault );
142
3.19k
    const Any* pAny = rItem.GetPropertyValueByName( u"Extrusion"_ustr, u"ShadeMode"_ustr );
143
3.19k
    if ( pAny )
144
135
    {
145
135
        if (!(*pAny >>= eRet))
146
0
        {
147
0
            sal_Int32 nEnum = 0;
148
0
            if(*pAny >>= nEnum)
149
0
            {
150
0
                eRet = static_cast<drawing::ShadeMode>(nEnum);
151
0
            }
152
0
        }
153
135
    }
154
3.19k
    return eRet;
155
3.19k
}
156
157
bool GetBool( const SdrCustomShapeGeometryItem& rItem, const OUString& rPropertyName, const bool bDefault )
158
60.9k
{
159
60.9k
    bool bRetValue = bDefault;
160
60.9k
    const Any* pAny = rItem.GetPropertyValueByName( u"Extrusion"_ustr, rPropertyName );
161
60.9k
    if ( pAny )
162
19.1k
        *pAny >>= bRetValue;
163
60.9k
    return bRetValue;
164
60.9k
}
165
166
drawing::Position3D GetPosition3D( const SdrCustomShapeGeometryItem& rItem, const OUString& rPropertyName,
167
                                    const drawing::Position3D& rDefault, const double* pMap )
168
500
{
169
500
    drawing::Position3D aRetValue( rDefault );
170
500
    const Any* pAny = rItem.GetPropertyValueByName( u"Extrusion"_ustr, rPropertyName );
171
500
    if ( pAny )
172
500
        *pAny >>= aRetValue;
173
500
    if ( pMap )
174
0
    {
175
0
        aRetValue.PositionX *= *pMap;
176
0
        aRetValue.PositionY *= *pMap;
177
0
        aRetValue.PositionZ *= *pMap;
178
0
    }
179
500
    return aRetValue;
180
500
}
181
182
drawing::Direction3D GetDirection3D( const SdrCustomShapeGeometryItem& rItem, const OUString& rPropertyName, const drawing::Direction3D& rDefault )
183
9.57k
{
184
9.57k
    drawing::Direction3D aRetValue( rDefault );
185
9.57k
    const Any* pAny = rItem.GetPropertyValueByName( u"Extrusion"_ustr, rPropertyName );
186
9.57k
    if ( pAny )
187
952
        *pAny >>= aRetValue;
188
9.57k
    return aRetValue;
189
9.57k
}
190
191
sal_Int16 GetMetalType(const SdrCustomShapeGeometryItem& rItem, const sal_Int16 eDefault)
192
3.19k
{
193
3.19k
    sal_Int16 aRetValue(eDefault);
194
3.19k
    const Any* pAny = rItem.GetPropertyValueByName(u"Extrusion"_ustr, u"MetalType"_ustr);
195
3.19k
    if (pAny)
196
3.19k
        *pAny >>= aRetValue;
197
3.19k
    return aRetValue;
198
3.19k
}
199
200
// Calculates the light directions for the additional lights, which are used to emulate soft
201
// lights of MS Office. Method needs to be documented in the Wiki
202
// https://wiki.documentfoundation.org/Development/ODF_Implementer_Notes in part
203
// List_of_LibreOffice_ODF_implementation-defined_items
204
// The method expects vector rLight to be normalized and results normalized vectors.
205
void lcl_SoftLightsDirection(const basegfx::B3DVector& rLight, basegfx::B3DVector& rSoftUp,
206
                             basegfx::B3DVector& rSoftDown, basegfx::B3DVector& rSoftRight,
207
                             basegfx::B3DVector& rSoftLeft)
208
3.16k
{
209
3.16k
    constexpr double fAngle = basegfx::deg2rad(60); // angle between regular light and soft light
210
211
    // We first create directions around (0|0|1) and then rotate them to the light position.
212
3.16k
    rSoftUp = basegfx::B3DVector(0.0, sin(fAngle), cos(fAngle));
213
3.16k
    rSoftDown = basegfx::B3DVector(0.0, -sin(fAngle), cos(fAngle));
214
3.16k
    rSoftRight = basegfx::B3DVector(sin(fAngle), 0.0, cos(fAngle));
215
3.16k
    rSoftLeft = basegfx::B3DVector(-sin(fAngle), 0.0, cos(fAngle));
216
217
3.16k
    basegfx::B3DHomMatrix aRotateMat;
218
3.16k
    aRotateMat.rotate(0.0, 0.0, M_PI_4);
219
3.16k
    if (rLight.getX() == 0.0 && rLight.getZ() == 0.0)
220
0
    {
221
        // Special case with light from top or bottom
222
0
        if (rLight.getY() >= 0.0)
223
0
            aRotateMat.rotate(-M_PI_2, 0.0, 0.0);
224
0
        else
225
0
            aRotateMat.rotate(M_PI_2, 0.0, 0.0);
226
0
    }
227
3.16k
    else
228
3.16k
    {
229
        // Azimuth from z-axis to x-axis. (0|0|1) to (1|0|0) is 90deg.
230
3.16k
        double fAzimuth = atan2(rLight.getX(), rLight.getZ());
231
        // Elevation from xz-plane to y-axis. (0|0|1) to (0|1|0) is 90deg.
232
3.16k
        double fElevation = atan2(rLight.getY(), std::hypot(rLight.getX(), rLight.getZ()));
233
3.16k
        aRotateMat.rotate(-fElevation, fAzimuth, 0.0);
234
3.16k
    }
235
236
3.16k
    rSoftUp = aRotateMat * rSoftUp;
237
3.16k
    rSoftDown = aRotateMat * rSoftDown;
238
3.16k
    rSoftRight = aRotateMat * rSoftRight;
239
3.16k
    rSoftLeft = aRotateMat * rSoftLeft;
240
3.16k
}
241
}
242
243
rtl::Reference<SdrObject> EnhancedCustomShape3d::Create3DObject(
244
    const SdrObject* pShape2d,
245
    const SdrObjCustomShape& rSdrObjCustomShape)
246
44.9k
{
247
44.9k
    rtl::Reference<SdrObject> pRet;
248
44.9k
    const SdrCustomShapeGeometryItem& rGeometryItem(rSdrObjCustomShape.GetMergedItem(SDRATTR_CUSTOMSHAPE_GEOMETRY));
249
44.9k
    double fMap(1.0), *pMap = nullptr;
250
251
44.9k
    if ( rSdrObjCustomShape.getSdrModelFromSdrObject().GetScaleUnit() != MapUnit::Map100thMM )
252
0
    {
253
0
        DBG_ASSERT( rSdrObjCustomShape.getSdrModelFromSdrObject().GetScaleUnit() == MapUnit::MapTwip, "EnhancedCustomShape3d::Current MapMode is Unsupported" );
254
        // But we could use MapToO3tlUnit from <tools/UnitConversion> ... ?
255
0
        fMap *= o3tl::convert(1.0, o3tl::Length::mm100, o3tl::Length::twip);
256
0
        pMap = &fMap;
257
0
    }
258
259
44.9k
    if ( GetBool( rGeometryItem, u"Extrusion"_ustr, false ) )
260
3.19k
    {
261
3.19k
        bool bIsMirroredX(rSdrObjCustomShape.IsMirroredX());
262
3.19k
        bool bIsMirroredY(rSdrObjCustomShape.IsMirroredY());
263
3.19k
        tools::Rectangle aSnapRect(rSdrObjCustomShape.GetLogicRect());
264
3.19k
        Degree100 nObjectRotation(rSdrObjCustomShape.GetRotateAngle());
265
3.19k
        if ( nObjectRotation )
266
1.14k
        {
267
1.14k
            double a = toRadians(36000_deg100 - nObjectRotation);
268
1.14k
            tools::Long dx = aSnapRect.Right() - aSnapRect.Left();
269
1.14k
            tools::Long dy = aSnapRect.Bottom()- aSnapRect.Top();
270
1.14k
            Point aP( aSnapRect.TopLeft() );
271
1.14k
            RotatePoint( aP, rSdrObjCustomShape.GetSnapRect().Center(), sin( a ), cos( a ) );
272
1.14k
            aSnapRect.SetLeft( aP.X() );
273
1.14k
            aSnapRect.SetTop( aP.Y() );
274
1.14k
            aSnapRect.SetRight( aSnapRect.Left() + dx );
275
1.14k
            aSnapRect.SetBottom( aSnapRect.Top() + dy );
276
1.14k
        }
277
3.19k
        Point aCenter( aSnapRect.Center() );
278
279
3.19k
        SfxItemSet aSet( rSdrObjCustomShape.GetMergedItemSet() );
280
281
        // tdf#146360 If the ItemSet of the source SdrObject has a parent
282
        // (which means it has a StyleSheet), we need to do some old-style
283
        // 'BurnInStyleSheetAttributes' action.
284
        // That means to set all Items which are set in the StyleSheet
285
        // directly in the ItemSet.
286
        // This is okay here since the 3D SdrObjects created are
287
        // placeholders that get rendered, but never reach the
288
        // surface/the user. If attributes for the source SdrObject
289
        // change, these will be recreated.
290
        // The problem is that while "aSet" still has a ptr to the style's
291
        // ItemSet, this gets lost at the ItemSet of the SdrObject when
292
        // an ItemSet gets set at the 3D SdrObject, like in diverse
293
        // SetMergedItemSet calls below. This leads to fetching the wrong
294
        // (default) FillBitmap in the calls p3DObj->GetMergedItem below
295
        // (which is 32x32 white, that's what you see without the fix).
296
        // This could also be fixed (tried it) by either
297
        // - using rSdrObjCustomShape.GetMergedItem
298
        // - setting the StyleSheet at 3D SdrObjects ASAP (done at caller)
299
        // but both solutions contain the risk to not find all places, so
300
        // it's just more safe to merge the StyleSheet attributes to the
301
        // ItemSet used for the whole creation.
302
3.19k
        if(nullptr != aSet.GetParent())
303
0
        {
304
0
            SfxWhichIter aIter(aSet);
305
0
            sal_uInt16 nWhich(aIter.FirstWhich());
306
0
            const SfxPoolItem *pItem(nullptr);
307
308
0
            while(nWhich)
309
0
            {
310
                // this may look at 1st look like doing nothing, but it converts
311
                // items set in parent/style to SfxItemState::SET items in the
312
                // ItemSet (see AttributeProperties::ForceStyleToHardAttributes())
313
0
                if(SfxItemState::SET == aSet.GetItemState(nWhich, true, &pItem))
314
0
                {
315
0
                    aSet.Put(*pItem);
316
0
                }
317
318
0
                nWhich = aIter.NextWhich();
319
0
            }
320
321
0
            aSet.SetParent(nullptr);
322
0
        }
323
324
        //SJ: vertical writing is not required, by removing this item no outliner is created
325
3.19k
        aSet.ClearItem( SDRATTR_TEXTDIRECTION );
326
327
        // #i105323# For 3D AutoShapes, the shadow attribute has to be applied to each
328
        // created visualisation helper model shape individually. The shadow itself
329
        // will then be rendered from the 3D renderer correctly for the whole 3D scene
330
        // (and thus behind all objects of which the visualisation may be built). So,
331
        // do NOT remove it from the ItemSet here.
332
        // aSet.ClearItem(SDRATTR_SHADOW);
333
334
3.19k
        std::vector< E3dCompoundObject* > aPlaceholderObjectList;
335
336
3.19k
        double fExtrusionBackward, fExtrusionForward;
337
3.19k
        GetExtrusionDepth( rGeometryItem, pMap, fExtrusionBackward, fExtrusionForward );
338
3.19k
        double fDepth = fExtrusionBackward + fExtrusionForward;
339
3.19k
        if ( fDepth < 1.0 )
340
7
            fDepth = 1.0;
341
342
3.19k
        drawing::ProjectionMode eProjectionMode( drawing::ProjectionMode_PARALLEL );
343
3.19k
        const Any* pAny = rGeometryItem.GetPropertyValueByName( u"Extrusion"_ustr, u"ProjectionMode"_ustr );
344
3.19k
        if (pAny)
345
3.19k
        {
346
3.19k
            if(!(*pAny >>= eProjectionMode))
347
0
            {
348
0
                sal_Int32 nEnum = 0;
349
0
                if(*pAny >>= nEnum)
350
0
                {
351
0
                    eProjectionMode = static_cast<drawing::ProjectionMode>(nEnum);
352
0
                }
353
0
            }
354
3.19k
        }
355
        // pShape2d Convert in scenes which include 3D Objects
356
3.19k
        E3dDefaultAttributes a3DDefaultAttr;
357
3.19k
        a3DDefaultAttr.SetDefaultLatheCharacterMode( true );
358
3.19k
        a3DDefaultAttr.SetDefaultExtrudeCharacterMode( true );
359
360
3.19k
        rtl::Reference<E3dScene> pScene = new E3dScene(rSdrObjCustomShape.getSdrModelFromSdrObject());
361
362
3.19k
        bool bSceneHasObjects ( false );
363
3.19k
        bool bUseTwoFillStyles( false );
364
365
3.19k
        drawing::ShadeMode eShadeMode( GetShadeMode( rGeometryItem, drawing::ShadeMode_FLAT ) );
366
3.19k
        bool bUseExtrusionColor = GetBool( rGeometryItem, u"Color"_ustr, false );
367
368
3.19k
        drawing::FillStyle eFillStyle( aSet.Get(XATTR_FILLSTYLE).GetValue() );
369
3.19k
        pScene->GetProperties().SetObjectItem( Svx3DShadeModeItem(static_cast<sal_uInt16>(eShadeMode)));
370
3.19k
        aSet.Put( makeSvx3DPercentDiagonalItem( 0 ) );
371
3.19k
        aSet.Put( Svx3DTextureModeItem( 1 ) );
372
        // SPECIFIC needed for ShadeMode_SMOOTH and ShadeMode_PHONG, otherwise FLAT is faster.
373
3.19k
        if (eShadeMode == drawing::ShadeMode_SMOOTH || eShadeMode == drawing::ShadeMode_PHONG)
374
0
            aSet.Put( Svx3DNormalsKindItem(static_cast<sal_uInt16>(drawing::NormalsKind_SPECIFIC)));
375
3.19k
        else
376
3.19k
            aSet.Put( Svx3DNormalsKindItem(static_cast<sal_uInt16>(drawing::NormalsKind_FLAT)));
377
378
3.19k
        if ( eShadeMode == drawing::ShadeMode_DRAFT )
379
0
        {
380
0
            aSet.Put( XLineStyleItem( drawing::LineStyle_SOLID ) );
381
0
            aSet.Put( XFillStyleItem ( drawing::FillStyle_NONE ) );
382
0
            aSet.Put( makeSvx3DDoubleSidedItem( true ) );
383
0
        }
384
3.19k
        else
385
3.19k
        {
386
3.19k
            aSet.Put( XLineStyleItem( drawing::LineStyle_NONE ) );
387
3.19k
            if ( eFillStyle == drawing::FillStyle_NONE )
388
1.45k
                aSet.Put( XFillStyleItem( drawing::FillStyle_SOLID ) );
389
1.73k
            else if ( ( eFillStyle == drawing::FillStyle_BITMAP ) || ( eFillStyle == drawing::FillStyle_GRADIENT ) || bUseExtrusionColor )
390
683
                bUseTwoFillStyles = true;
391
392
            // If shapes are mirrored once (mirroring two times correct geometry again)
393
            // double-sided at the object and two-sided-lighting at the scene need to be set.
394
395
            // #i122777# Also use double sided for two fill styles since there several 3d objects get
396
            // created with a depth of 0; one of them is the backside which needs double-sided to
397
            // get visible
398
3.19k
            if(bUseTwoFillStyles || (bIsMirroredX && !bIsMirroredY) || (!bIsMirroredX && bIsMirroredY))
399
2.06k
            {
400
2.06k
                aSet.Put( makeSvx3DDoubleSidedItem( true ) );
401
2.06k
                pScene->GetProperties().SetObjectItem( makeSvx3DTwoSidedLightingItem( true ) );
402
2.06k
            }
403
3.19k
        }
404
405
3.19k
        tools::Rectangle aBoundRect2d;
406
3.19k
        basegfx::B2DPolyPolygon aTotalPolyPoly;
407
3.19k
        SdrObjListIter aIter( *pShape2d, SdrIterMode::DeepNoGroups );
408
3.19k
        const bool bMultipleSubObjects(aIter.Count() > 1);
409
3.19k
        const bool bFuzzing(comphelper::IsFuzzing());
410
411
11.3k
        while( aIter.IsMore() )
412
8.11k
        {
413
8.11k
            const SdrObject* pNext = aIter.Next();
414
8.11k
            bool bIsPlaceholderObject = (pNext->GetMergedItem( XATTR_FILLSTYLE ).GetValue() == drawing::FillStyle_NONE )
415
8.11k
                                        && (pNext->GetMergedItem( XATTR_LINESTYLE ).GetValue() == drawing::LineStyle_NONE );
416
8.11k
            basegfx::B2DPolyPolygon aPolyPoly;
417
8.11k
            SfxItemSet aLocalSet(aSet);
418
8.11k
            drawing::FillStyle aLocalFillStyle(eFillStyle);
419
420
8.11k
            if ( auto pPathObj = dynamic_cast<const SdrPathObj*>(pNext) )
421
8.10k
            {
422
8.10k
                const SfxItemSet& rSet = pNext->GetMergedItemSet();
423
8.10k
                bool bNeedToConvertToContour(false);
424
425
                // do conversion only for single line objects; for all others a fill and a
426
                // line object get created. When we have fill, we want no line. That line has
427
                // always been there, but since it was never converted to contour, it kept
428
                // invisible (all this 'hidden' logic should be migrated to primitives).
429
8.10k
                if(!bMultipleSubObjects)
430
1.77k
                {
431
1.77k
                    const drawing::FillStyle eStyle(rSet.Get(XATTR_FILLSTYLE).GetValue());
432
433
1.77k
                    if(drawing::FillStyle_NONE == eStyle)
434
1.46k
                    {
435
1.46k
                        const drawinglayer::attribute::SdrLineAttribute aLine(
436
1.46k
                            drawinglayer::primitive2d::createNewSdrLineAttribute(rSet));
437
438
1.46k
                        bNeedToConvertToContour = (0.0 < aLine.getWidth() || 0.0 != aLine.getFullDotDashLen());
439
440
1.46k
                        if(!bNeedToConvertToContour && !aLine.isDefault())
441
0
                        {
442
0
                            const drawinglayer::attribute::SdrLineStartEndAttribute aLineStartEnd(
443
0
                                drawinglayer::primitive2d::createNewSdrLineStartEndAttribute(rSet, aLine.getWidth()));
444
445
0
                            if((aLineStartEnd.getStartWidth() && aLineStartEnd.isStartActive())
446
0
                                || (aLineStartEnd.getEndWidth() && aLineStartEnd.isEndActive()))
447
0
                            {
448
0
                                bNeedToConvertToContour = true;
449
0
                            }
450
0
                        }
451
1.46k
                    }
452
1.77k
                }
453
454
8.10k
                if (bNeedToConvertToContour && !bFuzzing)
455
0
                {
456
0
                    rtl::Reference<SdrObject> pNewObj = pNext->ConvertToContourObj(const_cast< SdrObject* >(pNext));
457
0
                    SdrPathObj* pNewPathObj = dynamic_cast< SdrPathObj* >(pNewObj.get());
458
459
0
                    if(pNewPathObj)
460
0
                    {
461
0
                        aPolyPoly = pNewPathObj->GetPathPoly();
462
463
0
                        if(aPolyPoly.isClosed())
464
0
                        {
465
                            // correct item properties from line to fill style
466
0
                            if(eShadeMode == drawing::ShadeMode_DRAFT)
467
0
                            {
468
                                // for draft, create wireframe with fixed line width
469
0
                                aLocalSet.Put(XLineStyleItem(drawing::LineStyle_SOLID));
470
0
                                aLocalSet.Put(XLineWidthItem(40));
471
0
                                aLocalFillStyle = drawing::FillStyle_NONE;
472
0
                            }
473
0
                            else
474
0
                            {
475
                                // switch from line to fill, copy line attr to fill attr (color, transparence)
476
0
                                aLocalSet.Put(XLineWidthItem(0));
477
0
                                aLocalSet.Put(XLineStyleItem(drawing::LineStyle_NONE));
478
0
                                aLocalSet.Put(XFillColorItem(OUString(), aLocalSet.Get(XATTR_LINECOLOR).GetColorValue()));
479
0
                                aLocalSet.Put(XFillStyleItem(drawing::FillStyle_SOLID));
480
0
                                aLocalSet.Put(XFillTransparenceItem(aLocalSet.Get(XATTR_LINETRANSPARENCE).GetValue()));
481
0
                                aLocalFillStyle = drawing::FillStyle_SOLID;
482
0
                            }
483
0
                        }
484
0
                        else
485
0
                        {
486
                            // correct item properties to hairlines
487
0
                            aLocalSet.Put(XLineWidthItem(0));
488
0
                            aLocalSet.Put(XLineStyleItem(drawing::LineStyle_SOLID));
489
0
                        }
490
0
                    }
491
0
                }
492
8.10k
                else
493
8.10k
                {
494
8.10k
                    aPolyPoly = pPathObj->GetPathPoly();
495
8.10k
                }
496
8.10k
            }
497
1
            else
498
1
            {
499
1
                rtl::Reference<SdrObject> pNewObj = pNext->ConvertToPolyObj( false, false );
500
1
                SdrPathObj* pPath = dynamic_cast<SdrPathObj*>( pNewObj.get() );
501
1
                if ( pPath )
502
1
                    aPolyPoly = pPath->GetPathPoly();
503
1
            }
504
505
8.11k
            if( aPolyPoly.count() )
506
8.11k
            {
507
8.11k
                if(aPolyPoly.areControlPointsUsed())
508
2.52k
                {
509
2.52k
                    aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
510
2.52k
                }
511
512
8.11k
                const basegfx::B2DRange aTempRange(basegfx::utils::getRange(aPolyPoly));
513
8.11k
                const tools::Rectangle aBoundRect(basegfx::fround<tools::Long>(aTempRange.getMinX()), basegfx::fround<tools::Long>(aTempRange.getMinY()), basegfx::fround<tools::Long>(aTempRange.getMaxX()), basegfx::fround<tools::Long>(aTempRange.getMaxY()));
514
8.11k
                aTotalPolyPoly.append(aPolyPoly);
515
8.11k
                aBoundRect2d.Union( aBoundRect );
516
517
                // #i122777# depth 0 is okay for planes when using double-sided
518
8.11k
                rtl::Reference<E3dCompoundObject> p3DObj = new E3dExtrudeObj(
519
8.11k
                    rSdrObjCustomShape.getSdrModelFromSdrObject(),
520
8.11k
                    a3DDefaultAttr,
521
8.11k
                    aPolyPoly,
522
8.11k
                    bUseTwoFillStyles ? 0 : fDepth );
523
524
8.11k
                p3DObj->NbcSetLayer( pShape2d->GetLayer() );
525
8.11k
                p3DObj->SetMergedItemSet( aLocalSet );
526
527
8.11k
                if ( bIsPlaceholderObject )
528
1.02k
                    aPlaceholderObjectList.push_back( p3DObj.get() );
529
7.09k
                else if ( bUseTwoFillStyles )
530
1.15k
                {
531
1.15k
                    BitmapEx aFillBmp;
532
1.15k
                    bool bFillBmpTile = p3DObj->GetMergedItem( XATTR_FILLBMP_TILE ).GetValue();
533
1.15k
                    if ( bFillBmpTile )
534
1.01k
                    {
535
1.01k
                        const XFillBitmapItem& rBmpItm = p3DObj->GetMergedItem(XATTR_FILLBITMAP);
536
1.01k
                        aFillBmp = rBmpItm.GetGraphicObject().GetGraphic().GetBitmapEx();
537
538
                        // #i122777# old adaptation of FillStyle bitmap size to 5-times the original size; this is not needed
539
                        // anymore and was used in old times to male the fill look better when converting to 3D. Removed
540
                        // from regular 3D objects for some time, also needs to be removed from CustomShapes
541
542
                        //Size aLogicalSize = aFillBmp.GetPrefSize();
543
                        //if ( aFillBmp.GetPrefMapMode() == MapUnit::MapPixel )
544
                        //  aLogicalSize = Application::GetDefaultDevice()->PixelToLogic( aLogicalSize, MapUnit::Map100thMM );
545
                        //else
546
                        //  aLogicalSize = OutputDevice::LogicToLogic( aLogicalSize, aFillBmp.GetPrefMapMode(), MapUnit::Map100thMM );
547
                        //aLogicalSize.Width()  *= 5;           ;//             :-(     nice scaling, look at engine3d/obj3d.cxx
548
                        //aLogicalSize.Height() *= 5;
549
                        //aFillBmp.SetPrefSize( aLogicalSize );
550
                        //aFillBmp.SetPrefMapMode( MapUnit::Map100thMM );
551
                        //p3DObj->SetMergedItem(XFillBitmapItem(String(), Graphic(aFillBmp)));
552
1.01k
                    }
553
132
                    else
554
132
                    {
555
132
                        if ( aSnapRect != aBoundRect && aSnapRect.GetWidth() > 0 && aSnapRect.GetHeight() > 0)
556
132
                        {
557
132
                            const XFillBitmapItem& rBmpItm = p3DObj->GetMergedItem(XATTR_FILLBITMAP);
558
132
                            aFillBmp = rBmpItm.GetGraphicObject().GetGraphic().GetBitmapEx();
559
132
                            Size aBmpSize( aFillBmp.GetSizePixel() );
560
132
                            double fXScale = static_cast<double>(aBoundRect.GetWidth()) / static_cast<double>(aSnapRect.GetWidth());
561
132
                            double fYScale = static_cast<double>(aBoundRect.GetHeight()) / static_cast<double>(aSnapRect.GetHeight());
562
563
132
                            Point aPt( static_cast<sal_Int32>( static_cast<double>( aBoundRect.Left() - aSnapRect.Left() )* static_cast<double>(aBmpSize.Width()) / static_cast<double>(aSnapRect.GetWidth()) ),
564
132
                                                static_cast<sal_Int32>( static_cast<double>( aBoundRect.Top() - aSnapRect.Top() ) * static_cast<double>(aBmpSize.Height()) / static_cast<double>(aSnapRect.GetHeight()) ) );
565
132
                            Size aSize( static_cast<sal_Int32>( aBmpSize.Width() * fXScale ),
566
132
                                                    static_cast<sal_Int32>( aBmpSize.Height() * fYScale ) );
567
132
                            tools::Rectangle aCropRect( aPt, aSize );
568
132
                            aFillBmp.Crop( aCropRect );
569
132
                            p3DObj->SetMergedItem(XFillBitmapItem(OUString(), Graphic(aFillBmp)));
570
132
                        }
571
132
                    }
572
1.15k
                    pScene->InsertObject( p3DObj.get() );
573
1.15k
                    p3DObj = new E3dExtrudeObj(
574
1.15k
                        rSdrObjCustomShape.getSdrModelFromSdrObject(),
575
1.15k
                        a3DDefaultAttr,
576
1.15k
                        aPolyPoly,
577
1.15k
                        fDepth);
578
1.15k
                    p3DObj->NbcSetLayer( pShape2d->GetLayer() );
579
1.15k
                    p3DObj->SetMergedItemSet( aLocalSet );
580
1.15k
                    if ( bUseExtrusionColor )
581
953
                        p3DObj->SetMergedItem( XFillColorItem( u""_ustr, rSdrObjCustomShape.GetMergedItem( XATTR_SECONDARYFILLCOLOR ).GetColorValue() ) );
582
1.15k
                    p3DObj->SetMergedItem( XFillStyleItem( drawing::FillStyle_SOLID ) );
583
1.15k
                    p3DObj->SetMergedItem( Svx3DCloseFrontItem( false ) );
584
1.15k
                    p3DObj->SetMergedItem( Svx3DCloseBackItem( false ) );
585
1.15k
                    pScene->InsertObject( p3DObj.get() );
586
587
                    // #i122777# depth 0 is okay for planes when using double-sided
588
1.15k
                    p3DObj = new E3dExtrudeObj(
589
1.15k
                        rSdrObjCustomShape.getSdrModelFromSdrObject(),
590
1.15k
                        a3DDefaultAttr,
591
1.15k
                        std::move(aPolyPoly),
592
1.15k
                        0);
593
594
1.15k
                    p3DObj->NbcSetLayer( pShape2d->GetLayer() );
595
1.15k
                    p3DObj->SetMergedItemSet( aLocalSet );
596
597
1.15k
                    basegfx::B3DHomMatrix aFrontTransform( p3DObj->GetTransform() );
598
1.15k
                    aFrontTransform.translate( 0.0, 0.0, fDepth );
599
1.15k
                    p3DObj->NbcSetTransform( aFrontTransform );
600
601
1.15k
                    if ( ( aLocalFillStyle == drawing::FillStyle_BITMAP ) && !aFillBmp.IsEmpty() )
602
134
                    {
603
134
                        p3DObj->SetMergedItem(XFillBitmapItem(OUString(), Graphic(aFillBmp)));
604
134
                    }
605
1.15k
                }
606
5.94k
                else if ( aLocalFillStyle == drawing::FillStyle_NONE )
607
2.08k
                {
608
2.08k
                    const XLineColorItem& rLineColor = p3DObj->GetMergedItem( XATTR_LINECOLOR );
609
2.08k
                    p3DObj->SetMergedItem( XFillColorItem( u""_ustr, rLineColor.GetColorValue() ) );
610
2.08k
                    p3DObj->SetMergedItem( makeSvx3DDoubleSidedItem( true ) );
611
2.08k
                    p3DObj->SetMergedItem( Svx3DCloseFrontItem( false ) );
612
2.08k
                    p3DObj->SetMergedItem( Svx3DCloseBackItem( false ) );
613
2.08k
                }
614
8.11k
                pScene->InsertObject( p3DObj.get() );
615
8.11k
                bSceneHasObjects = true;
616
8.11k
            }
617
8.11k
        }
618
619
3.19k
        if ( bSceneHasObjects ) // is the SdrObject properly converted
620
3.19k
        {
621
            // then we can change the return value
622
3.19k
            pRet = pScene;
623
624
            // Camera settings, Perspective ...
625
3.19k
            Camera3D rCamera = pScene->GetCamera();
626
3.19k
            pScene->NbcSetSnapRect( aSnapRect );
627
628
            // InitScene replacement
629
3.19k
            double fW = aBoundRect2d.getOpenWidth();
630
3.19k
            double fH = aBoundRect2d.getOpenHeight();
631
3.19k
            rCamera.SetAutoAdjustProjection( false );
632
3.19k
            rCamera.SetViewWindow( -fW / 2, - fH / 2, fW, fH);
633
3.19k
            basegfx::B3DPoint aLookAt( 0.0, 0.0, 0.0 );
634
3.19k
            basegfx::B3DPoint aCamPos( 0.0, 0.0, 100.0 );
635
3.19k
            rCamera.SetPosAndLookAt( aCamPos, aLookAt );
636
3.19k
            rCamera.SetFocalLength( 1.0 );
637
3.19k
            ProjectionType eProjectionType( eProjectionMode == drawing::ProjectionMode_PARALLEL ? ProjectionType::Parallel : ProjectionType::Perspective );
638
3.19k
            rCamera.SetProjection( eProjectionType );
639
3.19k
            pScene->SetCamera( rCamera );
640
3.19k
            pScene->SetBoundAndSnapRectsDirty();
641
642
3.19k
            basegfx::B3DHomMatrix aNewTransform( pScene->GetTransform() );
643
3.19k
            basegfx::B2DHomMatrix aPolyPolyTransform;
644
            // Apply flip and z-rotation to scene transformation (y up). At same time transform
645
            // aTotalPolyPoly (y down) which will be used for 2D boundRect of shape having 2D
646
            // transformations applied.
647
648
            // API values use shape center as origin. Move scene so, that shape center is origin.
649
3.19k
            aNewTransform.translate( -aCenter.X(), aCenter.Y(), -fExtrusionBackward);
650
3.19k
            aPolyPolyTransform.translate(-aCenter.X(), -aCenter.Y());
651
652
3.19k
            double fZRotate(basegfx::deg2rad(rSdrObjCustomShape.GetObjectRotation()));
653
3.19k
            if ( fZRotate != 0.0 )
654
1.14k
            {
655
1.14k
                aNewTransform.rotate( 0.0, 0.0, fZRotate );
656
1.14k
                aPolyPolyTransform.rotate(-fZRotate);
657
1.14k
            }
658
3.19k
            if ( bIsMirroredX )
659
831
            {
660
831
                aNewTransform.scale( -1.0, 1, 1 );
661
831
                aPolyPolyTransform.scale(-1.0, 1);
662
831
            }
663
3.19k
            if ( bIsMirroredY )
664
1.60k
            {
665
1.60k
                aNewTransform.scale( 1, -1.0, 1 );
666
1.60k
                aPolyPolyTransform.scale(1, -1.0);
667
1.60k
            }
668
3.19k
            aPolyPolyTransform.translate(aCenter.X(), aCenter.Y());
669
3.19k
            aTotalPolyPoly.transform(aPolyPolyTransform);
670
671
            // x- and y-rotation have an own rotation center. x- and y-value of rotation center are
672
            // fractions of shape size, z-value is in Hmm in property. Shape center is (0 0 0).
673
            // Values in property are in custom shape extrusion space with y-axis down.
674
3.19k
            double fXRotate, fYRotate;
675
3.19k
            GetRotateAngle( rGeometryItem, fXRotate, fYRotate );
676
3.19k
            drawing::Direction3D aRotationCenterDefault( 0, 0, 0 );
677
3.19k
            drawing::Direction3D aRotationCenter( GetDirection3D( rGeometryItem, u"RotationCenter"_ustr, aRotationCenterDefault ) );
678
3.19k
            aRotationCenter.DirectionX *= aSnapRect.getOpenWidth();
679
3.19k
            aRotationCenter.DirectionY *= aSnapRect.getOpenHeight();
680
3.19k
            if (pMap)
681
0
            {
682
0
                aRotationCenter.DirectionZ *= *pMap;
683
0
            }
684
3.19k
            aNewTransform.translate( -aRotationCenter.DirectionX, aRotationCenter.DirectionY, -aRotationCenter.DirectionZ );
685
3.19k
            if( fYRotate != 0.0 )
686
158
                aNewTransform.rotate( 0.0, -fYRotate, 0.0 );
687
3.19k
            if( fXRotate != 0.0 )
688
145
                aNewTransform.rotate( -fXRotate, 0.0, 0.0 );
689
3.19k
            aNewTransform.translate(aRotationCenter.DirectionX, -aRotationCenter.DirectionY, aRotationCenter.DirectionZ);
690
691
            // oblique parallel projection is done by shearing the object, not by moving the camera
692
3.19k
            if (eProjectionMode == drawing::ProjectionMode_PARALLEL)
693
2.69k
            {
694
2.69k
                double fSkew, fAlpha;
695
2.69k
                GetSkew( rGeometryItem, fSkew, fAlpha );
696
2.69k
                if ( fSkew != 0.0 )
697
2.68k
                {
698
2.68k
                    double fInvTanBeta( fSkew / 100.0 );
699
2.68k
                    if(fInvTanBeta)
700
2.68k
                    {
701
2.68k
                        aNewTransform.shearXY(
702
2.68k
                            fInvTanBeta * cos(fAlpha),
703
2.68k
                            fInvTanBeta * sin(fAlpha));
704
2.68k
                    }
705
2.68k
                }
706
2.69k
            }
707
708
3.19k
            pScene->NbcSetTransform( aNewTransform );
709
710
            // These values are used later again, so declare them outside the if-statement. They will
711
            // contain the absolute values of ViewPoint in 3D scene coordinate system, y-axis up.
712
3.19k
            double fViewPointX = 0; // dummy values
713
3.19k
            double fViewPointY = 0;
714
3.19k
            double fViewPointZ = 25000;
715
3.19k
            if (eProjectionMode == drawing::ProjectionMode_PERSPECTIVE)
716
500
            {
717
500
                double fOriginX, fOriginY;
718
                // Calculate BoundRect of shape, including flip and z-rotation, from aTotalPolyPoly.
719
500
                tools::Rectangle aBoundAfter2DTransform; // aBoundAfter2DTransform has y-axis down.
720
500
                basegfx::B2DRange aTotalPolyPolyRange(aTotalPolyPoly.getB2DRange());
721
500
                aBoundAfter2DTransform.SetLeft(aTotalPolyPolyRange.getMinX());
722
500
                aBoundAfter2DTransform.SetTop(aTotalPolyPolyRange.getMinY());
723
500
                aBoundAfter2DTransform.SetRight(aTotalPolyPolyRange.getMaxX());
724
500
                aBoundAfter2DTransform.SetBottom(aTotalPolyPolyRange.getMaxY());
725
726
                // Property "Origin" in API is relative to bounding box of shape after 2D
727
                // transformations. Range is [-0.5;0.5] with center of bounding box as 0.
728
                // Resolve "Origin" fractions to length
729
500
                GetOrigin( rGeometryItem, fOriginX, fOriginY );
730
500
                fOriginX *= aBoundAfter2DTransform.GetWidth();
731
500
                fOriginY *= aBoundAfter2DTransform.GetHeight();
732
                // Resolve length to absolute value for 3D
733
500
                fOriginX += aBoundAfter2DTransform.Center().X();
734
500
                fOriginY += aBoundAfter2DTransform.Center().Y();
735
500
                fOriginY = - fOriginY;
736
                // Scene is translated so that shape center is origin of coordinate system.
737
                // Translate point "Origin" too.
738
500
                fOriginX -= aCenter.X();
739
500
                fOriginY -= -aCenter.Y();
740
                // API ViewPoint values are relative to point "Origin" and have y-axis down.
741
                // ToDo: These default ViewPoint values are used as default by MS Office. But ODF
742
                // default is (3500, -3500, 25000), details in tdf#146192.
743
500
                drawing::Position3D aViewPointDefault( 3472, -3472, 25000 );
744
500
                drawing::Position3D aViewPoint( GetPosition3D( rGeometryItem, u"ViewPoint"_ustr, aViewPointDefault, pMap ) );
745
500
                fViewPointX = aViewPoint.PositionX + fOriginX;
746
500
                fViewPointY = - aViewPoint.PositionY + fOriginY;
747
500
                fViewPointZ = aViewPoint.PositionZ;
748
500
            }
749
750
            // now set correct camera position
751
3.19k
            if (eProjectionMode == drawing::ProjectionMode_PARALLEL)
752
2.69k
            {
753
2.69k
                basegfx::B3DPoint _aLookAt( 0.0, 0.0, 0.0 );
754
2.69k
                basegfx::B3DPoint _aNewCamPos( 0.0, 0.0, 25000.0 );
755
2.69k
                rCamera.SetPosAndLookAt( _aNewCamPos, _aLookAt );
756
2.69k
                pScene->SetCamera( rCamera );
757
2.69k
            }
758
500
            else
759
500
            {
760
500
                basegfx::B3DPoint _aLookAt(fViewPointX, fViewPointY, 0.0);
761
500
                basegfx::B3DPoint aNewCamPos(fViewPointX, fViewPointY, fViewPointZ);
762
500
                rCamera.SetPosAndLookAt( aNewCamPos, _aLookAt );
763
500
                pScene->SetCamera( rCamera );
764
500
            }
765
766
            // NbcSetTransform has not updated the scene 2D rectangles.
767
            // Idea: Get a bound volume as polygon from bound rectangle of shape without 2D
768
            // transformations. Calculate its projection to the XY-plane. Then calculate the bounding
769
            // rectangle of the projection and convert this rectangle back to absolute 2D coordinates.
770
            // Set that as 2D rectangle of the scene.
771
3.19k
            const tools::Polygon aPolygon(aBoundRect2d); // y-up
772
3.19k
            basegfx::B3DPolygon aPolygonBoundVolume; // y-down, scene coordinates
773
15.9k
            for (sal_uInt16 i = 0; i < 4; i++ )
774
12.7k
            {
775
12.7k
                aPolygonBoundVolume.append(basegfx::B3DPoint(aPolygon[i].X(), -aPolygon[i].Y(), 0));
776
12.7k
            }
777
15.9k
            for (sal_uInt16 i = 0; i < 4; i++ )
778
12.7k
            {
779
12.7k
                aPolygonBoundVolume.append(basegfx::B3DPoint(aPolygon[i].X(), -aPolygon[i].Y(), fDepth));
780
12.7k
            }
781
3.19k
            aPolygonBoundVolume.transform(aNewTransform);
782
783
            // projection
784
3.19k
            tools::Polygon a2DProjectionResult(8); // in fact 3D points with z=0
785
28.7k
            for (sal_uInt16 i = 0; i < 8; i++ )
786
25.5k
            {
787
25.5k
                const basegfx::B3DPoint aPoint3D(aPolygonBoundVolume.getB3DPoint(i));
788
789
25.5k
                if (eProjectionMode == drawing::ProjectionMode_PARALLEL)
790
21.5k
                {
791
21.5k
                    a2DProjectionResult[i].setX(aPoint3D.getX());
792
21.5k
                    a2DProjectionResult[i].setY(aPoint3D.getY());
793
21.5k
                }
794
4.00k
                else
795
4.00k
                {
796
                    // skip point if line from viewpoint to point is parallel to xy-plane
797
4.00k
                    if (double fDiv = aPoint3D.getZ() - fViewPointZ; fDiv != 0.0)
798
3.98k
                    {
799
3.98k
                        double f = (- fViewPointZ) / fDiv;
800
3.98k
                        double fX = (aPoint3D.getX() - fViewPointX) * f + fViewPointX;
801
3.98k
                        double fY = (aPoint3D.getY() - fViewPointY) * f + fViewPointY;;
802
3.98k
                        a2DProjectionResult[i].setX(static_cast<sal_Int32>(fX));
803
3.98k
                        a2DProjectionResult[i].setY(static_cast<sal_Int32>(fY));
804
3.98k
                    }
805
4.00k
                }
806
25.5k
            }
807
            // Convert to y-axis down
808
28.7k
            for (sal_uInt16 i = 0; i < 8; i++ )
809
25.5k
            {
810
25.5k
                a2DProjectionResult[i].setY(- a2DProjectionResult[i].Y());
811
25.5k
            }
812
            // Shift back to shape center
813
3.19k
            a2DProjectionResult.Translate(aCenter);
814
815
3.19k
            pScene->SetLogicRect(a2DProjectionResult.GetBoundRect());
816
817
818
            // light and material
819
820
            // "LightFace" has nothing corresponding in 3D rendering engine.
821
            /* bool bLightFace = */ GetBool(rGeometryItem, u"LightFace"_ustr, true); // default in ODF
822
823
            // Light directions
824
825
3.19k
            drawing::Direction3D aFirstLightDirectionDefault(50000.0, 0.0, 10000.0);
826
3.19k
            drawing::Direction3D aFirstLightDirection(GetDirection3D( rGeometryItem, u"FirstLightDirection"_ustr, aFirstLightDirectionDefault));
827
3.19k
            if (aFirstLightDirection.DirectionX == 0.0 && aFirstLightDirection.DirectionY == 0.0
828
3.19k
                && aFirstLightDirection.DirectionZ == 0.0)
829
0
                aFirstLightDirection.DirectionZ = 1.0;
830
3.19k
            basegfx::B3DVector aLight1Vector(aFirstLightDirection.DirectionX, -aFirstLightDirection.DirectionY, aFirstLightDirection.DirectionZ);
831
3.19k
            aLight1Vector.normalize();
832
833
3.19k
            drawing::Direction3D aSecondLightDirectionDefault(-50000.0, 0.0, 10000.0);
834
3.19k
            drawing::Direction3D aSecondLightDirection(GetDirection3D( rGeometryItem, u"SecondLightDirection"_ustr, aSecondLightDirectionDefault));
835
3.19k
            if (aSecondLightDirection.DirectionX == 0.0 && aSecondLightDirection.DirectionY == 0.0
836
3.19k
                && aSecondLightDirection.DirectionZ == 0.0)
837
0
                aSecondLightDirection.DirectionZ = 1.0;
838
3.19k
            basegfx::B3DVector aLight2Vector(aSecondLightDirection.DirectionX, -aSecondLightDirection.DirectionY, aSecondLightDirection.DirectionZ);
839
3.19k
            aLight2Vector.normalize();
840
841
            // tdf#160421 a single flip inverts the light directions currently (March 2024). So invert
842
            // their directions here for rendering.
843
3.19k
            if (bIsMirroredX != bIsMirroredY)
844
1.65k
            {
845
1.65k
                aLight1Vector *= -1.0;
846
1.65k
                aLight2Vector *= -1.0;
847
1.65k
            }
848
849
            // Light Intensity
850
851
            // For "FirstLight" the 3D-Scene light "1" is regularly used. In case of surface "Matte"
852
            // the light 4 is used instead. For "SecondLight" the 3D-Scene light "2" is regularly used.
853
            // In case first or second light is not harsh, the lights 5 to 8 are used in addition
854
            // to get a soft light appearance.
855
            // The 3D-Scene light "3" is currently not used.
856
857
            // ODF default 66%. MS Office default 38000/65536=0.579 is set in import filter.
858
3.19k
            double fLight1Intensity = GetDouble(rGeometryItem, u"FirstLightLevel"_ustr, 66) / 100.0;
859
            // ODF and MS Office have both default 'true'.
860
3.19k
            bool bFirstLightHarsh = GetBool(rGeometryItem, u"FirstLightHarsh"_ustr, true);
861
            // ODF default 66%. MS Office default 38000/65536=0.579 is set in import filter
862
3.19k
            double fLight2Intensity = GetDouble(rGeometryItem, u"SecondLightLevel"_ustr, 66) / 100.0;
863
            // ODF has default 'true'. MS Office default 'false' is set in import.
864
3.19k
            bool bSecondLightHarsh = GetBool(rGeometryItem, u"SecondLightHarsh"_ustr, true);
865
866
            // ODF default 33%. MS Office default 20000/65536=0.305 is set in import filter.
867
3.19k
            double fAmbientIntensity = GetDouble(rGeometryItem, u"Brightness"_ustr, 33) / 100.0;
868
869
3.19k
            double fLight1IntensityForSpecular(fLight1Intensity); // remember original value
870
3.19k
            if (!bFirstLightHarsh || !bSecondLightHarsh) // might need softing lights
871
3.16k
            {
872
3.16k
                bool bNeedSoftLights(false); // catch case of lights with zero intensity.
873
3.16k
                basegfx::B3DVector aLight5Vector;
874
3.16k
                basegfx::B3DVector aLight6Vector;
875
3.16k
                basegfx::B3DVector aLight7Vector;
876
3.16k
                basegfx::B3DVector aLight8Vector;
877
                // The needed light intensities depend on the angle between regular light and
878
                // additional lights, currently for 60deg.
879
3.16k
                Color aHoriSoftLightColor;
880
3.16k
                Color aVertSoftLightColor;
881
882
3.16k
                if (!bSecondLightHarsh && fLight2Intensity > 0.0
883
3.16k
                    && (bFirstLightHarsh || fLight1Intensity == 0.0)) // only second light soft
884
3.14k
                {
885
                    // That is default for shapes generated in the UI, for LO and MS Office as well.
886
3.14k
                    bNeedSoftLights = true;
887
3.14k
                    double fLight2SoftIntensity = fLight2Intensity * 0.40;
888
3.14k
                    aHoriSoftLightColor = Color(basegfx::BColor(fLight2SoftIntensity).clamp());
889
3.14k
                    aVertSoftLightColor = aHoriSoftLightColor;
890
3.14k
                    fLight2Intensity *= 0.2;
891
892
3.14k
                    lcl_SoftLightsDirection(aLight2Vector, aLight5Vector, aLight6Vector,
893
3.14k
                                            aLight7Vector, aLight8Vector);
894
3.14k
                }
895
22
                else if (!bFirstLightHarsh && fLight1Intensity > 0.0
896
22
                         && (bSecondLightHarsh || fLight2Intensity == 0.0)) // only first light soft
897
15
                {
898
15
                    bNeedSoftLights = true;
899
15
                    double fLight1SoftIntensity = fLight1Intensity * 0.40;
900
15
                    aHoriSoftLightColor = Color(basegfx::BColor(fLight1SoftIntensity).clamp());
901
15
                    aVertSoftLightColor = aHoriSoftLightColor;
902
15
                    fLight1Intensity *= 0.2;
903
904
15
                    lcl_SoftLightsDirection(aLight1Vector, aLight5Vector, aLight6Vector,
905
15
                                            aLight7Vector, aLight8Vector);
906
15
                }
907
7
                else if (!bFirstLightHarsh && fLight1Intensity > 0.0 && !bSecondLightHarsh
908
7
                         && fLight2Intensity > 0.0) // both lights soft
909
4
                {
910
4
                    bNeedSoftLights = true;
911
                    // We do not hat enough lights. We use two soft lights for FirstLight and two for
912
                    // SecondLight and double intensity.
913
4
                    double fLight1SoftIntensity = fLight1Intensity * 0.8;
914
4
                    fLight1Intensity *= 0.4;
915
4
                    aHoriSoftLightColor = Color(basegfx::BColor(fLight1SoftIntensity).clamp());
916
4
                    basegfx::B3DVector aDummy1, aDummy2;
917
4
                    lcl_SoftLightsDirection(aLight1Vector, aDummy1, aDummy2, aLight7Vector,
918
4
                                            aLight8Vector);
919
920
4
                    double fLight2SoftIntensity = fLight2Intensity * 0.8;
921
4
                    aVertSoftLightColor = Color(basegfx::BColor(fLight2SoftIntensity).clamp());
922
4
                    fLight2Intensity *= 0.4;
923
4
                    lcl_SoftLightsDirection(aLight2Vector, aLight5Vector, aLight6Vector, aDummy1,
924
4
                                            aDummy2);
925
4
                }
926
927
3.16k
                if (bNeedSoftLights)
928
3.15k
                {
929
3.15k
                    pScene->GetProperties().SetObjectItem(
930
3.15k
                        makeSvx3DLightDirection5Item(aLight5Vector));
931
3.15k
                    pScene->GetProperties().SetObjectItem(
932
3.15k
                        makeSvx3DLightcolor5Item(aVertSoftLightColor));
933
3.15k
                    pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff5Item(true));
934
3.15k
                    pScene->GetProperties().SetObjectItem(
935
3.15k
                        makeSvx3DLightDirection6Item(aLight6Vector));
936
3.15k
                    pScene->GetProperties().SetObjectItem(
937
3.15k
                        makeSvx3DLightcolor6Item(aVertSoftLightColor));
938
3.15k
                    pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff6Item(true));
939
3.15k
                    pScene->GetProperties().SetObjectItem(
940
3.15k
                        makeSvx3DLightDirection7Item(aLight7Vector));
941
3.15k
                    pScene->GetProperties().SetObjectItem(
942
3.15k
                        makeSvx3DLightcolor7Item(aHoriSoftLightColor));
943
3.15k
                    pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff7Item(true));
944
3.15k
                    pScene->GetProperties().SetObjectItem(
945
3.15k
                        makeSvx3DLightDirection8Item(aLight8Vector));
946
3.15k
                    pScene->GetProperties().SetObjectItem(
947
3.15k
                        makeSvx3DLightcolor8Item(aHoriSoftLightColor));
948
3.15k
                    pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff8Item(true));
949
3.15k
                }
950
3.16k
            }
951
952
            // ToDo: MSO seems to add half of the surplus to ambient color. ODF restricts value to <1.
953
3.19k
            if (fLight1Intensity > 1.0)
954
21
            {
955
21
                fAmbientIntensity += (fLight1Intensity - 1.0) / 2.0;
956
21
            }
957
958
            // ToDo: How to handle fAmbientIntensity larger 1.0 ? Perhaps lighten object color?
959
960
            // Now set the regularly 3D-scene light attributes.
961
3.19k
            Color aAmbientColor(basegfx::BColor(fAmbientIntensity).clamp());
962
3.19k
            pScene->GetProperties().SetObjectItem(makeSvx3DAmbientcolorItem(aAmbientColor));
963
964
3.19k
            pScene->GetProperties().SetObjectItem(makeSvx3DLightDirection1Item(aLight1Vector));
965
3.19k
            pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff1Item(fLight1Intensity > 0.0));
966
3.19k
            Color aLight1Color(basegfx::BColor(fLight1Intensity).clamp());
967
3.19k
            pScene->GetProperties().SetObjectItem(makeSvx3DLightcolor1Item(aLight1Color));
968
969
3.19k
            pScene->GetProperties().SetObjectItem(makeSvx3DLightDirection2Item(aLight2Vector));
970
3.19k
            pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff2Item(fLight2Intensity > 0.0));
971
3.19k
            Color aLight2Color(basegfx::BColor(fLight2Intensity).clamp());
972
3.19k
            pScene->GetProperties().SetObjectItem(makeSvx3DLightcolor2Item(aLight2Color));
973
974
            // Object reactions on light
975
            // Diffusion, Specular-Color and -Intensity are object properties, not scene properties.
976
            // Surface flag "Metal" is an object property too.
977
978
            // Property "Diffusion" would correspond to style attribute "drd3:diffuse-color".
979
            // But that is not implemented. We cannot ignore the attribute because MS Office sets
980
            // attribute c3DDiffuseAmt to 43712 (Type Fixed 16.16, approx 66,9%) instead of MSO
981
            // default 65536 (100%), if the user sets surface 'Metal' in the UI of MS Office.
982
            // We will change the material color of the 3D object as ersatz.
983
            // ODF data type is percent with default 0%. MSO default is set in import filter.
984
3.19k
            double fDiffusion = GetDouble(rGeometryItem, u"Diffusion"_ustr, 0.0) / 100.0;
985
986
            // ODF standard specifies for value true: "the specular color for the shading of an
987
            // extruded shape is gray (red, green and blue values of 200) instead of white and 15% is
988
            // added to the specularity."
989
            // Neither 'specularity' nor 'specular color' is clearly defined in the standard. ODF term
990
            // 'specularity' seems to correspond to UI field 'Specular Intensity' for 3D scenes.
991
            // MS Office uses current material color in case 'Metal' is set. To detect, whether
992
            // rendering similar to MS Office has to be used the property 'MetalType' is used. It is
993
            // set on import and in the extrusion bar.
994
3.19k
            bool bMetal = GetBool(rGeometryItem, u"Metal"_ustr, false);
995
3.19k
            sal_Int16 eMetalType(
996
3.19k
                GetMetalType(rGeometryItem, drawing::EnhancedCustomShapeMetalType::MetalODF));
997
3.19k
            bool bMetalMSCompatible
998
3.19k
                = eMetalType == drawing::EnhancedCustomShapeMetalType::MetalMSCompatible;
999
1000
            // Property "Specularity" corresponds to 3D object style attribute dr3d:specular-color.
1001
3.19k
            double fSpecularity = GetDouble(rGeometryItem, u"Specularity"_ustr, 0) / 100.0;
1002
1003
3.19k
            if (bMetal && !bMetalMSCompatible)
1004
0
            {
1005
0
                fSpecularity *= 200.0 / 255.0;
1006
0
            }
1007
1008
            // MS Office seems to render as if 'Specular Color' = Specularity * Light1Intensity.
1009
3.19k
            double fShadingFactor = fLight1IntensityForSpecular * fSpecularity;
1010
3.19k
            Color aSpecularCol(basegfx::BColor(fShadingFactor).clamp());
1011
            // In case of bMetalMSCompatible the color will be recalculated in the below loop.
1012
1013
            // Shininess ODF default 50 (unit %). MS Office default 5, import filter makes *10.
1014
            // Shininess corresponds to "Specular Intensity" with the nonlinear relationship
1015
            // "Specular Intensity" = 2^c3DShininess = 2^("Shininess" / 10)
1016
3.19k
            double fShininess = GetDouble(rGeometryItem, u"Shininess"_ustr, 50) / 10.0;
1017
3.19k
            fShininess = std::clamp<double>(pow(2, fShininess), 0.0, 100.0);
1018
3.19k
            sal_uInt16 nIntensity = static_cast<sal_uInt16>(basegfx::fround(fShininess));
1019
3.19k
            if (bMetal && !bMetalMSCompatible)
1020
0
            {
1021
0
                nIntensity += 15; // as specified in ODF
1022
0
                nIntensity = std::clamp<sal_uInt16>(nIntensity, 0, 100);
1023
0
            }
1024
1025
3.19k
            SdrObjListIter aSceneIter(*pScene, SdrIterMode::DeepNoGroups);
1026
13.6k
            while (aSceneIter.IsMore())
1027
10.4k
            {
1028
10.4k
                const SdrObject* pNext = aSceneIter.Next();
1029
1030
                // Change material color as ersatz for missing style attribute "drd3:diffuse-color".
1031
                // For this ersatz we exclude case fDiffusion == 0.0, because for older documents this
1032
                // attribute is not written out to draw:extrusion-diffusion and ODF default 0 would
1033
                // produce black objects.
1034
10.4k
                const Color& rMatColor
1035
10.4k
                    = pNext->GetProperties().GetItem(XATTR_FILLCOLOR).GetColorValue();
1036
10.4k
                Color aOldMatColor(rMatColor);
1037
10.4k
                if (fDiffusion > 0.0 && !basegfx::fTools::equalZero(fDiffusion)
1038
10.4k
                    && !basegfx::fTools::equal(fDiffusion, 1.0))
1039
2.53k
                {
1040
                    // Occurs e.g. with MS surface preset 'Metal'.
1041
2.53k
                    sal_uInt16 nHue;
1042
2.53k
                    sal_uInt16 nSaturation;
1043
2.53k
                    sal_uInt16 nBrightness;
1044
2.53k
                    rMatColor.RGBtoHSB(nHue, nSaturation, nBrightness);
1045
2.53k
                    nBrightness
1046
2.53k
                        = static_cast<sal_uInt16>(static_cast<double>(nBrightness) * fDiffusion);
1047
2.53k
                    nBrightness = std::clamp<sal_uInt16>(nBrightness, 0, 100);
1048
2.53k
                    Color aNewMatColor = Color::HSBtoRGB(nHue, nSaturation, nBrightness);
1049
2.53k
                    pNext->GetProperties().SetObjectItem(XFillColorItem(u""_ustr, aNewMatColor));
1050
2.53k
                }
1051
1052
                // Using material color instead of gray in case of MS Office compatible rendering.
1053
10.4k
                if (bMetal && bMetalMSCompatible)
1054
9.56k
                {
1055
9.56k
                    sal_uInt16 nHue;
1056
9.56k
                    sal_uInt16 nSaturation;
1057
9.56k
                    sal_uInt16 nBrightness;
1058
9.56k
                    aOldMatColor.RGBtoHSB(nHue, nSaturation, nBrightness);
1059
9.56k
                    nBrightness = static_cast<sal_uInt16>(static_cast<double>(nBrightness)
1060
9.56k
                                                          * fShadingFactor);
1061
9.56k
                    nBrightness = std::clamp<sal_uInt16>(nBrightness, 0, 100);
1062
9.56k
                    aSpecularCol = Color::HSBtoRGB(nHue, nSaturation, nBrightness);
1063
9.56k
                }
1064
1065
10.4k
                pNext->GetProperties().SetObjectItem(makeSvx3DMaterialSpecularItem(aSpecularCol));
1066
10.4k
                pNext->GetProperties().SetObjectItem(
1067
10.4k
                    makeSvx3DMaterialSpecularIntensityItem(nIntensity));
1068
10.4k
            }
1069
1070
            // fSpecularity = 0 is used to indicate surface preset "Matte".
1071
3.19k
            if (basegfx::fTools::equalZero(fSpecularity))
1072
2.89k
            {
1073
                // First light in LO 3D engine is always specular, all other lights are never specular.
1074
                // We copy light1 values to light4 and use it instead of light1 in the 3D scene.
1075
2.89k
                pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff1Item(false));
1076
2.89k
                pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff4Item(true));
1077
2.89k
                pScene->GetProperties().SetObjectItem(makeSvx3DLightcolor4Item(aLight1Color));
1078
2.89k
                pScene->GetProperties().SetObjectItem(makeSvx3DLightDirection4Item(aLight1Vector));
1079
2.89k
            }
1080
1081
            // removing placeholder objects
1082
3.19k
            for (E3dCompoundObject* pTemp : aPlaceholderObjectList)
1083
1.02k
            {
1084
1.02k
                pScene->RemoveObject( pTemp->GetOrdNum() );
1085
1.02k
            }
1086
3.19k
        }
1087
3.19k
    }
1088
44.9k
    return pRet;
1089
44.9k
}
1090
1091
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */