Coverage Report

Created: 2026-11-06 11:29

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