1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
11 #include <drawingml/scene3dhelper.hxx>
13 #include <basegfx/matrix/b3dhommatrix.hxx>
14 #include <basegfx/numeric/ftools.hxx>
15 #include <basegfx/vector/b3dvector.hxx>
16 #include <oox/drawingml/drawingmltypes.hxx>
17 #include <oox/helper/propertymap.hxx>
18 #include <oox/token/properties.hxx>
19 #include <oox/token/tokens.hxx>
21 #include <com/sun/star/drawing/Direction3D.hpp>
22 #include <com/sun/star/drawing/EnhancedCustomShapeParameter.hpp>
23 #include <com/sun/star/drawing/EnhancedCustomShapeMetalType.hpp>
24 #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
25 #include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
26 #include <com/sun/star/drawing/Position3D.hpp>
27 #include <com/sun/star/drawing/ProjectionMode.hpp>
28 #include <com/sun/star/drawing/ShadeMode.hpp>
34 /** This struct is used to hold values from the OOXML camera preset types.*/
37 struct PrstCameraValues
39 std::u16string_view msCameraPrstName
; // identifies the value set
43 // values as shown in the UI of MS Office, converted to 1/60000 deg
44 double mfRotateAngleX
; // unit 1/60000 degree
45 double mfRotateAngleY
; // unit 1/60000 degree
46 double mfRotateAngleZ
; // unit 1/60000 degree
48 // Position of origin relative to the bounding box of the transformed 2D shape.
49 // LibreOffice can handle values outside the ODF range.
50 double mfOriginX
; // ODF range [-0.5 (left).. 0.5 (right)], fraction of width
51 double mfOriginY
; // ODF range [-0.5 (top) 0.5 (bottom)], fraction of height
53 // mandatory for PARALLEL, ignored for PERSPECTIVE
54 double mfSkewAmount
; // range 0 to 100, percent of depth used as slant length
55 double mfSkewAngle
; // unit degree
57 // mandatory for PERSPECTIVE, ignored for PARALLEL
58 // API type ::com::sun::star::drawing::Position3D; unit 1/100 mm
59 double mfViewPointX
; // shift from Origin
60 double mfViewPointY
; // shift from Origin
61 double mfViewPointZ
; // absolute z-coordinate
63 // The OOXML camera attribute "zoom" is not contained, because it is not set in preset camera
64 // types and LO cannot render it in custom shape extrusion scene.
66 } // end anonymous namespace
68 // The values were found experimental using MS Office. A spreadsheet with remarks is attached
70 constexpr sal_uInt16
nCameraPresetCount(62); // Fixed, specified in OOXML standard.
71 constexpr PrstCameraValues aPrstCameraValuesArray
[nCameraPresetCount
] = {
72 { u
"isometricBottomDown", true, 2124000, 18882000, 17988000, 0, 0, 0, 0, 0, 0, 0 },
73 { u
"isometricBottomUp", true, 2124000, 2718000, 3612000, 0, 0, 0, 0, 0, 0, 0 },
74 { u
"isometricLeftDown", true, 2100000, 2700000, 0, 0, 0, 0, 0, 0, 0, 0 },
75 { u
"isometricLeftUp", true, 19500000, 2700000, 0, 0, 0, 0, 0, 0, 0, 0 },
76 { u
"isometricOffAxis1Left", true, 1080000, 3840000, 0, 0, 0, 0, 0, 0, 0, 0 },
77 { u
"isometricOffAxis1Right", true, 1080000, 20040000, 0, 0, 0, 0, 0, 0, 0, 0 },
78 { u
"isometricOffAxis1Top", true, 18078000, 18390000, 3456000, 0, 0, 0, 0, 0, 0, 0 },
79 { u
"isometricOffAxis2Left", true, 1080000, 1560000, 0, 0, 0, 0, 0, 0, 0, 0 },
80 { u
"isometricOffAxis2Right", true, 1080000, 17760000, 0, 0, 0, 0, 0, 0, 0, 0 },
81 { u
"isometricOffAxis2Top", true, 18078000, 3210000, 18144000, 0, 0, 0, 0, 0, 0, 0 },
82 { u
"isometricOffAxis3Bottom", true, 3522000, 18390000, 18144000, 0, 0, 0, 0, 0, 0, 0 },
83 { u
"isometricOffAxis3Left", true, 20520000, 3840000, 0, 0, 0, 0, 0, 0, 0, 0 },
84 { u
"isometricOffAxis3Right", true, 20520000, 20040000, 0, 0, 0, 0, 0, 0, 0, 0 },
85 { u
"isometricOffAxis4Bottom", true, 3522000, 3210000, 3456000, 0, 0, 0, 0, 0, 0, 0 },
86 { u
"isometricOffAxis4Left", true, 20520000, 1560000, 0, 0, 0, 0, 0, 0, 0, 0 },
87 { u
"isometricOffAxis4Right", true, 20520000, 17760000, 0, 0, 0, 0, 0, 0, 0, 0 },
88 { u
"isometricRightDown", true, 19500000, 18900000, 0, 0, 0, 0, 0, 0, 0, 0 },
89 { u
"isometricRightUp", true, 2100000, 18900000, 0, 0, 0, 0, 0, 0, 0, 0 },
90 { u
"isometricTopDown", true, 19476000, 2718000, 17988000, 0, 0, 0, 0, 0, 0, 0 },
91 { u
"isometricTopUp", true, 19476000, 18882000, 3612000, 0, 0, 0, 0, 0, 0, 0 },
92 { u
"legacyObliqueBottom", true, 0, 0, 0, 0, 0.5, 50, 90, 0, 0, 0 },
93 { u
"legacyObliqueBottomLeft", true, 0, 0, 0, -0.5, 0.5, 50, 45, 0, 0, 0 },
94 { u
"legacyObliqueBottomRight", true, 0, 0, 0, 0.5, 0.5, 50, 135, 0, 0, 0 },
95 { u
"legacyObliqueFront", true, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
96 { u
"legacyObliqueLeft", true, 0, 0, 0, -0.5, 0, 50, -360, 0, 0, 0 },
97 { u
"legacyObliqueRight", true, 0, 0, 0, 0.5, 0, 50, 180, 0, 0, 0 },
98 { u
"legacyObliqueTop", true, 0, 0, 0, 0, -0.5, 50, -90, 0, 0, 0 },
99 { u
"legacyObliqueTopLeft", true, 0, 0, 0, -0.5, -0.5, 50, -45, 0, 0, 0 },
100 { u
"legacyObliqueTopRight", true, 0, 0, 0, 0.5, -0.5, 50, -135, 0, 0, 0 },
101 { u
"legacyPerspectiveBottom", false, 0, 0, 0, 0, 0.5, 50, 90, 0, 3472, 25000 },
102 { u
"legacyPerspectiveBottomLeft", false, 0, 0, 0, -0.5, 0.5, 50, 45, -3472, 3472, 25000 },
103 { u
"legacyPerspectiveBottomRight", false, 0, 0, 0, 0.5, 0.5, 50, 135, 3472, 3472, 25000 },
104 { u
"legacyPerspectiveFront", false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25000 },
105 { u
"legacyPerspectiveLeft", false, 0, 0, 0, -0.5, 0, 50, -360, -3472, 0, 25000 },
106 { u
"legacyPerspectiveRight", false, 0, 0, 0, 0.5, 0, 50, 180, 3472, 0, 25000 },
107 { u
"legacyPerspectiveTop", false, 0, 0, 0, 0, -0.5, 50, -90, 0, -3472, 25000 },
108 { u
"legacyPerspectiveTopLeft", false, 0, 0, 0, -0.5, -0.5, 50, -45, -3472, -3472, 25000 },
109 { u
"legacyPerspectiveTopRight", false, 0, 0, 0, 0.5, -0.5, 50, -135, 3472, -3472, 25000 },
110 { u
"obliqueBottom", true, 0, 0, 0, 0, 0.5, 30, 90, 0, 0, 0 },
111 { u
"obliqueBottomLeft", true, 0, 0, 0, -0.5, 0.5, 30, 45, 0, 0, 0 },
112 { u
"obliqueBottomRight", true, 0, 0, 0, 0.5, 0.5, 30, 135, 0, 0, 0 },
113 { u
"obliqueLeft", true, 0, 0, 0, -0.5, 0, 30, -360, 0, 0, 0 },
114 { u
"obliqueRight", true, 0, 0, 0, 0.5, 0, 30, 180, 0, 0, 0 },
115 { u
"obliqueTop", true, 0, 0, 0, 0, -0.5, 30, -90, 0, 0, 0 },
116 { u
"obliqueTopLeft", true, 0, 0, 0, -0.5, -0.5, 30, -45, 0, 0, 0 },
117 { u
"obliqueTopRight", true, 0, 0, 0, 0.5, -0.5, 30, -135, 0, 0, 0 },
118 { u
"orthographicFront", true, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
119 { u
"perspectiveAbove", false, 20400000, 0, 0, 0, 0, 0, 0, 0, 0, 38451 },
120 { u
"perspectiveAboveLeftFacing", false, 2358000, 858000, 20466000, 0, 0, 0, 0, 0, 0, 38451 },
121 { u
"perspectiveAboveRightFacing", false, 2358000, 20742000, 1134000, 0, 0, 0, 0, 0, 0, 38451 },
122 { u
"perspectiveBelow", false, 1200000, 0, 0, 0, 0, 0, 0, 0, 0, 38451 },
123 { u
"perspectiveContrastingLeftFacing", false, 624000, 2634000, 21384000, 0, 0, 0, 0, 0, 0,
125 { u
"perspectiveContrastingRightFacing", false, 624000, 18966000, 216000, 0, 0, 0, 0, 0, 0,
127 { u
"perspectiveFront", false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38451 },
128 { u
"perspectiveHeroicExtremeLeftFacing", false, 486000, 2070000, 21426000, 0, 0, 0, 0, 0, 0,
130 { u
"perspectiveHeroicExtremeRightFacing", false, 486000, 19530000, 174000, 0, 0, 0, 0, 0, 0,
132 { u
"perspectiveHeroicLeftFacing", false, 20940000, 858000, 156000, 0, 0, 0, 0, 0, 0, 38451 },
133 { u
"perspectiveHeroicRightFacing", false, 20940000, 20742000, 21444000, 0, 0, 0, 0, 0, 0,
135 { u
"perspectiveLeft", false, 0, 1200000, 0, 0, 0, 0, 0, 0, 0, 38451 },
136 { u
"perspectiveRelaxed", false, 18576000, 0, 0, 0, 0, 0, 0, 0, 0, 38451 },
137 { u
"perspectiveRelaxedModerately", false, 19488000, 0, 0, 0, 0, 0, 0, 0, 0, 38451 },
138 { u
"perspectiveRight", false, 0, 20400000, 0, 0, 0, 0, 0, 0, 0, 38451 }
143 /** Searches for the item in aPrstCameraValuesArray with given sPresetName.
144 @param [in] sPresetName name as specified in OOXML standard
145 @return returns the index if item exists, otherwise -1*/
146 sal_Int16
getPrstCameraIndex(std::u16string_view sPresetName
)
149 while (nIt
< nCameraPresetCount
&& aPrstCameraValuesArray
[nIt
].msCameraPrstName
!= sPresetName
)
151 if (nIt
>= nCameraPresetCount
)
153 nIt
= -1; // Error is handled by caller
157 } // end anonymous namespace
159 void Scene3DHelper::getAPIAnglesFromOOXAngle(const sal_Int32 nLat
, const sal_Int32 nLon
,
160 const sal_Int32 nRev
, double& fX
, double& fY
,
163 // MS Office applies the rotations in the order first around y-axis by nLon, then around x-axis
164 // by nLat and last around z-axis by nRev. The extrusion mode in ODF and also the API
165 // first rotate around the z-axis, then around the y-axis and last around the x-axis. In ODF, the
166 // rotation around the z-axis is integrated into the shape transformation and the others are
167 // specified in the enhanced geometry of the shape.
168 // The orientation of the resulting angles equals the orientation in API, but the angles are in
171 // First we build the total rotation matrix from the OOX angles. y-axis points down.
172 basegfx::B3DHomMatrix aXMat
;
173 const double fLatRad
= basegfx::deg2rad
<60000>(nLat
);
174 aXMat
.set(1, 1, cos(fLatRad
));
175 aXMat
.set(2, 2, cos(fLatRad
));
176 aXMat
.set(1, 2, sin(fLatRad
));
177 aXMat
.set(2, 1, -sin(fLatRad
));
179 basegfx::B3DHomMatrix aYMat
;
180 const double fLonRad
= basegfx::deg2rad
<60000>(nLon
);
181 aYMat
.set(0, 0, cos(fLonRad
));
182 aYMat
.set(2, 2, cos(fLonRad
));
183 aYMat
.set(0, 2, -sin(fLonRad
));
184 aYMat
.set(2, 0, sin(fLonRad
));
186 basegfx::B3DHomMatrix aZMat
;
187 const double fRevRad
= basegfx::deg2rad
<60000>(nRev
);
188 aZMat
.set(0, 0, cos(fRevRad
));
189 aZMat
.set(1, 1, cos(fRevRad
));
190 aZMat
.set(0, 1, sin(fRevRad
));
191 aZMat
.set(1, 0, -sin(fRevRad
));
192 basegfx::B3DHomMatrix aTotalMat
= aZMat
* aXMat
* aYMat
;
194 // Now we decompose it so that rotation around z-axis is the first rotation. We know it is a
195 // orthonormal matrix, so some steps seen in B3DHomMatrix::decompose() are not needed.
196 // The solution fY2 = pi - fY results in the same projection, thus we do not consider it.
197 fY
= std::asin(-aTotalMat
.get(0, 2));
199 if (basegfx::fTools::equalZero(cos(fY
)))
201 // This case has zeros at positions (0,0), (0,1), (1,2) and (2,2) in aTotalMat.
202 // This special case means, that the shape is rotated around the y-axis so, that the user
203 // looks on the extruded faces. Front face and back face are orthogonal to the xy-plane. The
204 // rotation around the x-axis cannot be distinguished from an initial rotation of the shape
205 // outside 3D. Thus there exist no unique solution.
207 fZ
= std::atan2(aTotalMat
.get(2, 1), aTotalMat
.get(1, 1));
211 fX
= std::atan2(-aTotalMat
.get(1, 2) / cos(fY
), aTotalMat
.get(2, 2) / cos(fY
));
212 fZ
= std::atan2(aTotalMat
.get(0, 1) / cos(fY
), aTotalMat
.get(0, 0) / cos(fY
));
216 void Scene3DHelper::getAPIAnglesFrom3DProperties(
217 const oox::drawingml::Shape3DPropertiesPtr p3DProperties
, const sal_Int32
& rnMSOShapeRotation
,
218 double& fX
, double& fY
, double& fZ
)
223 // on x-axis, unit 1/60000 deg
224 sal_Int32 nLatitude
= (*p3DProperties
).maCameraRotation
.mnLatitude
.value_or(0);
225 // on y-axis, unit 1/60000 deg
226 sal_Int32 nLongitude
= (*p3DProperties
).maCameraRotation
.mnLongitude
.value_or(0);
227 // on z-axis, unit 1/60000 deg
228 sal_Int32 nRevolution
= (*p3DProperties
).maCameraRotation
.mnRevolution
.value_or(0);
230 // Some projection types need special treatment:
231 if (29 <= mnPrstCameraIndex
&& mnPrstCameraIndex
<= 37)
233 // legacyPerspective. MS Office does not use z-rotation but writes it to file. We need to
234 // ignore it. The preset cameras have no rotation.
237 else if (47 <= mnPrstCameraIndex
)
239 assert(mnPrstCameraIndex
<= 61
240 && "by definition we don't set anything >= nCameraPresetCount (62)");
241 // perspective. MS Office has a strange rendering behavior: If the shape rotation is not zero
242 // and the angle for rotation on x-axis (=latitude) is >90deg and <=270deg, then MSO renders
243 // the shape with an addition 180deg rotation on the z-axis. This happens only with user
245 if (rnMSOShapeRotation
!= 0 && nLatitude
> 5400000 && nLatitude
<= 16200000)
246 nRevolution
+= 10800000;
249 // In case attributes lat, lon and rev of the <rot> child element of the <scene3d> element in
250 // OOXML markup are given, they overwrite the values from the preset camera type. Otherwise the
251 // values from the preset camera are used. OOXML requires that all three attributes must exist at
252 // the same time. Thus it is enough to test one of them.
253 if (!(*p3DProperties
).maCameraRotation
.mnRevolution
.has_value())
255 // The angles are given in 1/60000 deg in aPrstCameraValuesArray.
256 nLatitude
= aPrstCameraValuesArray
[mnPrstCameraIndex
].mfRotateAngleX
;
257 nLongitude
= aPrstCameraValuesArray
[mnPrstCameraIndex
].mfRotateAngleY
;
258 nRevolution
= aPrstCameraValuesArray
[mnPrstCameraIndex
].mfRotateAngleZ
;
261 // MS Office applies the shape rotation after the rotations from camera in case of non-legacy
262 // cameras, and before for legacy cameras. ODF specifies to first rotate the shape. Thus we need
263 // to add shape rotation to nRevolution in case of non-legacy cameras. The shape rotation has
264 // opposite orientation than camera z-rotation.
265 bool bIsLegacyCamera
= 20 <= mnPrstCameraIndex
&& mnPrstCameraIndex
<= 37;
266 if (!bIsLegacyCamera
)
267 nRevolution
-= rnMSOShapeRotation
;
269 // Now calculate the angles for LO rotation order and orientation.
270 Scene3DHelper::getAPIAnglesFromOOXAngle(nLatitude
, nLongitude
, nRevolution
, fX
, fY
, fZ
);
273 fZ
-= basegfx::deg2rad
<60000>(rnMSOShapeRotation
);
276 void Scene3DHelper::addRotateAngleToMap(oox::PropertyMap
& rPropertyMap
, const double fX
,
279 css::drawing::EnhancedCustomShapeParameterPair aAnglePair
;
280 aAnglePair
.First
.Value
<<= basegfx::rad2deg(fX
);
281 aAnglePair
.First
.Type
= css::drawing::EnhancedCustomShapeParameterType::NORMAL
;
282 aAnglePair
.Second
.Value
<<= basegfx::rad2deg(fY
);
283 aAnglePair
.Second
.Type
= css::drawing::EnhancedCustomShapeParameterType::NORMAL
;
284 rPropertyMap
.setAnyProperty(oox::PROP_RotateAngle
, css::uno::Any(aAnglePair
));
287 void Scene3DHelper::addExtrusionDepthToMap(const oox::drawingml::Shape3DPropertiesPtr p3DProperties
,
288 oox::PropertyMap
& rPropertyMap
)
290 // Amount of extrusion and its position relative to the original shape face. This moves the
291 // shape inside the scene.
292 // The GetExtrusionDepth() method in EnhancedCustomShape3d.cxx expects type double for both.
293 sal_Int32 nDepthAmount
= (*p3DProperties
).mnExtrusionH
.value_or(0); // unit EMU
294 double fDepthAmount
= o3tl::convert(nDepthAmount
, o3tl::Length::emu
, o3tl::Length::mm100
);
295 sal_Int32 nZPosition
= (*p3DProperties
).mnShapeZ
.value_or(0); // unit EMU
296 double fZPosition
= o3tl::convert(nZPosition
, o3tl::Length::emu
, o3tl::Length::mm100
);
297 double fDepthRelPos
= 0.0;
298 if (nDepthAmount
== 0 && nZPosition
!= 0)
300 // We cannot express the position relative to the extrusion depth.
301 // Use an artificial, small depth of 1Hmm
302 fDepthRelPos
= fZPosition
;
303 fDepthAmount
= 1.0; // unit Hmm
305 else if (nDepthAmount
!= 0)
306 fDepthRelPos
= fZPosition
/ fDepthAmount
;
308 css::drawing::EnhancedCustomShapeParameterPair aPair
;
309 css::drawing::EnhancedCustomShapeParameter
& rDepthAmount
= aPair
.First
;
310 rDepthAmount
.Value
<<= fDepthAmount
;
311 rDepthAmount
.Type
= css::drawing::EnhancedCustomShapeParameterType::NORMAL
;
312 css::drawing::EnhancedCustomShapeParameter
& rDepthFraction
= aPair
.Second
;
313 rDepthFraction
.Value
<<= fDepthRelPos
;
314 rDepthFraction
.Type
= css::drawing::EnhancedCustomShapeParameterType::NORMAL
;
315 rPropertyMap
.setProperty(oox::PROP_Depth
, aPair
);
318 void Scene3DHelper::addProjectionGeometryToMap(
319 const oox::drawingml::Shape3DPropertiesPtr p3DProperties
, oox::PropertyMap
& rPropertyMap
,
320 const bool bIsParallel
, const sal_Int32 rnMSOShapeRotation
)
322 // origin is needed for parallel and perspective as well
323 css::drawing::EnhancedCustomShapeParameterPair aOrigin
;
324 aOrigin
.First
.Value
<<= aPrstCameraValuesArray
[mnPrstCameraIndex
].mfOriginX
;
325 aOrigin
.First
.Type
= css::drawing::EnhancedCustomShapeParameterType::NORMAL
;
326 aOrigin
.Second
.Value
<<= aPrstCameraValuesArray
[mnPrstCameraIndex
].mfOriginY
;
327 aOrigin
.Second
.Type
= css::drawing::EnhancedCustomShapeParameterType::NORMAL
;
328 rPropertyMap
.setProperty(oox::PROP_Origin
, aOrigin
);
332 // PARALLEL needs API property Skew.
333 // orthographicFront and isometric projections do not use skew. We write it nevertheless
334 // to prevent LO defaults. Zeros are contained in aPrstCameraValuesArray for these cases.
335 double fSkewAngle
= aPrstCameraValuesArray
[mnPrstCameraIndex
].mfSkewAngle
; // unit degree
336 double fSkewAmount
= aPrstCameraValuesArray
[mnPrstCameraIndex
].mfSkewAmount
;
337 // oblique projections (index [38..45]) need special treatment. MS Office rotates around the
338 // z-axis after the projection was created. Thus the rotation affects the skew direction. ODF
339 // rotates the shape before creating the projection. Thus we need to incorporate the shape
340 // rotation into the skew angle.
341 if (38 <= mnPrstCameraIndex
&& mnPrstCameraIndex
<= 45)
343 fSkewAngle
-= rnMSOShapeRotation
/ 60000.0;
345 css::drawing::EnhancedCustomShapeParameterPair aSkew
;
346 aSkew
.First
.Value
<<= fSkewAmount
;
347 aSkew
.First
.Type
= css::drawing::EnhancedCustomShapeParameterType::NORMAL
;
348 aSkew
.Second
.Value
<<= fSkewAngle
;
349 aSkew
.Second
.Type
= css::drawing::EnhancedCustomShapeParameterType::NORMAL
;
350 rPropertyMap
.setProperty(oox::PROP_Skew
, aSkew
);
354 // PERSPECTIVE needs API property ViewPoint.
355 css::drawing::Position3D aViewPoint
;
357 // x- and y-coordinate depend on preset camera type.
358 aViewPoint
.PositionX
= aPrstCameraValuesArray
[mnPrstCameraIndex
].mfViewPointX
;
359 aViewPoint
.PositionY
= aPrstCameraValuesArray
[mnPrstCameraIndex
].mfViewPointY
;
361 // The z-coordinate is determined bei a field of view angle in OOXML and by a
362 // distance in LibreOffice. MS Office users can change its value.
363 if ((*p3DProperties
).mfFieldOfVision
.has_value())
365 double fFov
= (*p3DProperties
).mfFieldOfVision
.value();
366 fFov
= std::clamp(fFov
, 0.5, 179.5);
367 // 15976 = 25000 * tan(32.5°) as in legacy. Better ideas to calculate the distance are
369 aViewPoint
.PositionZ
= 15976.0 / tan(basegfx::deg2rad(fFov
/ 2.0));
372 aViewPoint
.PositionZ
= aPrstCameraValuesArray
[mnPrstCameraIndex
].mfViewPointZ
;
374 rPropertyMap
.setProperty(oox::PROP_ViewPoint
, aViewPoint
);
376 // ToDo: It is possible in OOXML to set a 3D-scene on a group. It is not clear yet how that can
377 // be mimicked in LO. In case of perspective projection, it produces a horizontal or vertical
378 // shift of the viewpoint in relation to the shapes of the group, for example.
381 bool Scene3DHelper::setExtrusionProperties(const oox::drawingml::Shape3DPropertiesPtr p3DProperties
,
382 const sal_Int32
& rnMSOShapeRotation
,
383 oox::PropertyMap
& rPropertyMap
, double& rRotZ
,
384 oox::drawingml::Color
& rExtrusionColor
,
385 const bool bBlockExtrusion
)
387 // We convert rnMSOShapeRotation, so that Shape::createAndInsert() can use rRotZ the same way in
389 rRotZ
= basegfx::deg2rad
<60000>(-rnMSOShapeRotation
);
391 if (!p3DProperties
|| !(*p3DProperties
).mnPreset
.has_value())
394 const sal_Int32
nCameraPrstID((*p3DProperties
).mnPreset
.value());
395 sal_Int16 nPrstCameraIndex
396 = getPrstCameraIndex(oox::drawingml::Generic3DProperties::getCameraPrstName(nCameraPrstID
));
397 if (nPrstCameraIndex
< 0 or nPrstCameraIndex
>= nCameraPresetCount
)
398 return false; // error in document. OOXML specifies a fixed set of preset camera types.
399 mnPrstCameraIndex
= nPrstCameraIndex
;
401 // Extrusion color is not handled as extrusion property but as shape property. Thus deliver it
402 // in any case, so that Shape::createAndInsert() knows about it.
403 rExtrusionColor
= (*p3DProperties
).maExtrusionColor
;
405 // Even if we do not extrude an image, we still want to get the z-Rotation.
408 rRotZ
= basegfx::deg2rad
<60000>((*p3DProperties
).maCameraRotation
.mnRevolution
.value_or(0)
409 - rnMSOShapeRotation
);
413 // We use extrusion, if there is a rotation around x-axis or y-axis,
414 // or if there is no such rotation but we have a perspective projection with true depth,
415 // or we have a parallel projection other than a 'front' type.
416 // In other cases the rendering as normal shape is better than any current extrusion.
419 Scene3DHelper::getAPIAnglesFrom3DProperties(p3DProperties
, rnMSOShapeRotation
, fX
, fY
, rRotZ
);
420 sal_Int32 nDepthAmount
= (*p3DProperties
).mnExtrusionH
.value_or(0);
421 bool bIsParallel
= aPrstCameraValuesArray
[mnPrstCameraIndex
].mbIsParallel
;
422 bool bIsParallelFrontType
423 = (nCameraPrstID
== XML_legacyObliqueFront
) || (nCameraPrstID
== XML_orthographicFront
);
424 bool bCreateExtrusion
= (!basegfx::fTools::equalZero(fX
) || !basegfx::fTools::equalZero(fY
))
425 || (!bIsParallel
&& nDepthAmount
> 0)
426 || (bIsParallel
&& !bIsParallelFrontType
);
428 if (!bCreateExtrusion
)
431 // Create the extrusion properties in rPropertyMap so that they can be directly used.
433 rPropertyMap
.setProperty(oox::PROP_Extrusion
, true);
435 // Dummy value. Will be changed from evaluating the material properties.
436 rPropertyMap
.setProperty(oox::PROP_Diffusion
, 100.0);
439 css::drawing::ProjectionMode eProjectionMode
= bIsParallel
440 ? css::drawing::ProjectionMode_PARALLEL
441 : css::drawing::ProjectionMode_PERSPECTIVE
;
442 rPropertyMap
.setProperty(oox::PROP_ProjectionMode
, eProjectionMode
);
444 Scene3DHelper::addRotateAngleToMap(rPropertyMap
, fX
, fY
);
446 Scene3DHelper::addProjectionGeometryToMap(p3DProperties
, rPropertyMap
, bIsParallel
,
450 Scene3DHelper::addExtrusionDepthToMap(p3DProperties
, rPropertyMap
);
452 // The 'automatic' extrusion color is different in MS Office. Thus we enable it in any case.
453 // CreateAndInsert method will set a suitable 'automatic' color, if rExtrusionColor is not used.
454 rPropertyMap
.setProperty(oox::PROP_Color
, true);
455 // ToDo: Some materials might need ShadeMode_Smooth or ShadeMode_PHONG.
456 rPropertyMap
.setProperty(oox::PROP_ShadeMode
, css::drawing::ShadeMode_FLAT
);
463 /* This struct is used to hold light properties for a light in a preset light rig.*/
466 // Values are as specified in [MS-OI29500], see commit message.
467 // The color is specified as RGBA, but alpha value is always 1.0 and ignored anyway, so it is
468 // dropped here. The RGB values are in decimal, but might exceed the usual [0;1] range.
472 // MSO uses 4 decimals precision, some light directions are not normalized.
473 double fMSOLightDirectionX
;
474 double fMSOLightDirectionY
;
475 double fMSOLightDirectionZ
;
482 /* This struct is used to hold properties of a light rig*/
483 struct PrstLightRigValues
485 // values are as specified in [MS-OI29500], see commit message.
486 std::u16string_view sLightRigName
; // identifies the light rig, mandatory in OOXML
487 // The ambient color is specified as RGBA, but alpha value is always 1.0 and R = B = G. Thus we
488 // store here only one value.
489 std::optional
<double> fAmbient
;
490 // Each rig has at least one light and maximal four lights
492 std::optional
<MSOLight
> aLight2
;
493 std::optional
<MSOLight
> aLight3
;
494 std::optional
<MSOLight
> aLight4
;
495 // Light rig rotation is not contained in the presets.
497 } // end anonymous namespace
499 // The values are taken from [MS-OI29500]. For details see the spreadsheet attached to
500 // tdf#70039 and the commit message.
501 constexpr sal_uInt16
nLightRigPresetCount(27); // Fix value, specified in OOXML standard.
502 constexpr PrstLightRigValues aPrstLightRigValuesArray
[nLightRigPresetCount
] = {
505 { 1.05, 1.05, 1.05, 0.5263, -0.4092, -0.7453, 1, 0, true, true },
506 { { 1, 1, 1, -0.9386, 0.3426, -0.041, 1, 0, true, true } },
507 { { 0.5, 0.5, 0.5, 0.0934, 0.763, 0.6396, 1, 0, true, true } },
511 { 1, 1, 1, 0, -1, 0, 1, 0, false, true },
512 { { 1, 1, 1, 0.8227, -0.1882, -0.5364, 1, 0, true, false } },
513 { { -0.5, -0.5, -0.5, 0, 0, -1, 1, 0, false, true } },
514 { { 0.5, 0.5, 0.5, 0, 1, 0, 1, 0, false, true } } },
517 { 0.31, 0.32, 0.32, 0.6574, -0.7316, -0.1806, 1, 0, true, true },
518 { { 0.45, 0.45, 0.45, -0.3539, -0.1505, -0.9231, 1, 0, false, true } },
519 { { 1.03, 1.02, 1.15, 0.672, -0.6185, -0.4073, 1, 0, false, true } },
520 { { 0.41, 0.45, 0.48, -0.5781, 0.7976, 0.1722, 1, 0, true, true } } },
523 { 1, 1, 1, 0, -1, 0, 1, 0, true, false },
524 { { 1, 1, 1, 0, 1, 0, 1, 0, true, false } },
529 { 0.821, 0.821, 0.821, -0.9546, -0.1619, -0.2502, 1, 0, true, false },
530 { { 2.072, 2.54, 2.91, 0.0009, 0.8605, 0.5095, 1, 0, true, false } },
531 { { 3.843, 3.843, 3.843, 0.6574, -0.7316, -0.1806, 1, 0, true, false } },
535 { 1.1, 1.1, 1.1, 0.5685, -0.7651, -0.3022, 1, 0, true, true },
536 { { 1.1, 1.1, 1.1, -0.2366, -0.9595, -0.1531, 1, 0, true, true } },
537 { { 0.55, 0.55, 0.55, -0.8982, 0.1386, -0.4171, 1, 0, true, true } },
541 { 0.53, 0.567, 0.661, 0.6574, -0.7316, -0.1806, 1, 0, true, true },
542 { { 0.37, 0.461, 0.461, -0.2781, -0.4509, -0.8482, 1, 0, false, true } },
543 { { 0.649, 0.638, 0.904, 0.672, -0.6185, -0.4073, 1, 0, false, true } },
544 { { 0.971, 1.19, 1.363, -0.1825, 0.968, 0.1722, 1, 0, true, true } } },
547 { 1, 1, 1, 0, -1, 0, 1, 0, true, true },
548 { { 0.7, 0.7, 0.7, 0, 1, 0, 1, 0, true, true } },
553 { 0.88, 0.88, 0.88, 0.6689, -0.6755, -0.3104, 1, 0, true, true },
554 { { 0.88, 0.88, 0.88, -0.592, -0.7371, -0.326, 1, 0, true, true } },
559 { 0.58, 0.58, 0.58, 0, 0, -0.2, 1, 0, true, true },
560 { { 0.58, 0.58, 0.58, 0, 0, -0.2, 0.5, 0, false, true } },
565 { 0.58, 0.58, 0.58, -1, -1, -0.2, 1, 0, true, true },
566 { { 0.58, 0.58, 0.58, 0, 1, -0.2, 0.5, 0, false, true } },
571 { 0.58, 0.58, 0.58, 0, -1, -0.2, 1, 0, true, true },
572 { { 0.58, 0.58, 0.58, 0, 1, -0.2, 0.5, 0, false, true } },
577 { 0.58, 0.58, 0.58, 1, -1, -0.2, 1, 0, true, true },
578 { { 0.58, 0.58, 0.58, 0, 1, -0.2, 0.5, 0, false, true } },
583 { 0.793, 0.793, 0.793, 0, 0, -0.2, 1, 0, true, true },
584 { { 0.214, 0.214, 0.214, 0, 0, -0.2, 1, 0, false, true } },
589 { 0.793, 0.793, 0.793, -1, -1, -0.2, 1, 0, true, true },
590 { { 0.214, 0.214, 0.214, 0, 1, -0.2, 1, 0, false, true } },
595 { 0.793, 0.793, 0.793, 0, -1, -0.2, 1, 0, true, true },
596 { { 0.214, 0.214, 0.214, 0, 1, -0.2, 1, 0, false, true } },
601 { 0.793, 0.793, 0.793, 1, -1, -0.2, 1, 0, true, true },
602 { { 0.214, 0.214, 0.214, 0, 1, -0.2, 1, 0, false, true } },
607 { 0.671, 0.671, 0.671, 0, 0, -0.2, 1, 0, true, true },
608 { { 0.366, 0.366, 0.366, 0, 0, -0.2, 0.5, 0, false, true } },
613 { 0.671, 0.671, 0.671, -1, -1, -0.2, 1, 0, true, true },
614 { { 0.366, 0.366, 0.366, 0, 1, -0.2, 0.5, 0, false, true } },
619 { 0.671, 0.671, 0.671, 0, -1, -0.2, 1, 0, true, true },
620 { { 0.366, 0.366, 0.366, 0, 1, -0.2, 0.5, 0, false, true } },
625 { 0.671, 0.671, 0.671, 1, -1, -0.2, 1, 0, true, true },
626 { { 0.366, 0.366, 0.366, 0, 1, -0.2, 0.5, 0, false, true } },
631 { 0.669, 0.648, 0.596, 0.6574, -0.7316, -0.1806, 0.5, 0.5, true, true },
632 { { 0.459, 0.454, 0.385, -0.2781, -0.4509, -0.8482, 1, 0, false, true } },
633 { { 0.9, 0.86, 0.83, 0.672, -0.6185, -0.4073, 1, 0, false, true } },
634 { { 0.911, 0.846, 0.728, -0.1825, 0.968, 0.1722, 1, 0, true, true } } },
635 { u
"soft", { 0.3 }, { 0.8, 0.8, 0.8, -0.6897, 0.2484, -0.6802, 1, 0, true, true }, {}, {}, {} },
638 { 0.667, 0.63, 0.527, 0.6574, -0.7316, -0.1806, 1, 0, true, true },
639 { { 0.459, 0.459, 0.371, -0.2781, -0.4509, -0.8482, 1, 0, false, true } },
640 { { 0.826, 0.712, 0.638, 0.672, -0.6185, -0.4073, 1, 0, false, true } },
641 { { 1.511, 1.319, 0.994, -0.1825, 0.968, 0.1722, 1, 0, false, true } } },
644 { 0.672, 0.169, 0.169, 0.6574, -0.7316, -0.1806, 1, 0, true, true },
645 { { 0.459, 0.448, 0.327, 0.0922, -0.3551, -0.9303, 1, 0, false, true } },
646 { { 0.775, 0.612, 0.502, 0.672, -0.6185, -0.4073, 1, 0, false, true } },
647 { { 0.761, 0.69, 0.397, -0.424, 0.8891, 0.1722, 1, 0, false, true } } },
650 { 1.141, 1.141, 1.141, -0.6515, -0.2693, -0.7093, 1, 0, true, true },
651 { { 0.5, 0.5, 0.5, 0.8482, 0.2469, -0.4686, 1, 0, true, true } },
652 { { 1, 1, 1, 0.5634, -0.2812, 0.7769, 1, 0, true, true } },
656 { 0.84, 0.84, 0.84, 0.5266, -0.4089, -0.7454, 0, 0, true, true },
657 { { 0.3, 0.3, 0.3, -0.8983, 0.2365, -0.3704, 1, 0, true, true } },
664 /** Searches for the item in aPrstLightRigValuesArray with given sPresetName.
665 @param [in] sPresetName name as specified in OOXML standard
666 @return returns the index if item exists, otherwise -1.*/
667 sal_Int16
lcl_getPrstLightRigIndex(std::u16string_view sPresetName
)
670 while (nIt
< nLightRigPresetCount
&& aPrstLightRigValuesArray
[nIt
].sLightRigName
!= sPresetName
)
672 if (nIt
>= nLightRigPresetCount
)
674 nIt
= -1; // Error is handled by caller
679 /** Extracts the light directions from the preset lightRig.
680 @param [in] rLightRig from which the lights are extracted
681 @param [out] rLightDirVec contains the preset lights but each as B3DVector*/
682 void lcl_getLightDirectionsFromRig(const PrstLightRigValues
& rLightRig
,
683 std::vector
<basegfx::B3DVector
>& rLightDirVec
)
685 auto addLightDir
= [&](const MSOLight
& aMSOLight
) {
686 basegfx::B3DVector
aLightDir(aMSOLight
.fMSOLightDirectionX
, aMSOLight
.fMSOLightDirectionY
,
687 aMSOLight
.fMSOLightDirectionZ
);
688 rLightDirVec
.push_back(std::move(aLightDir
));
690 // aLight1 always exists, the others are optional
691 addLightDir(rLightRig
.aLight1
);
692 if (rLightRig
.aLight2
.has_value())
693 addLightDir(rLightRig
.aLight2
.value());
694 if (rLightRig
.aLight3
.has_value())
695 addLightDir(rLightRig
.aLight3
.value());
696 if (rLightRig
.aLight4
.has_value())
697 addLightDir(rLightRig
.aLight4
.value());
700 /** Converts the directions from MSO specification to coordinates in the shape coordinate system.
701 @details The extruded shape uses a left-hand Cartesian coordinate system with x-axis right, y-axis
702 down and z-axis towards observer. When L(Lx,Ly,Lz) is the specified light direction, then
703 V(-Ly, -Lx, Lz) is the direction in the shape coordinate system.
704 @param [in,out] rLightDirVec contains for each individual light its direction.*/
705 void lcl_AdaptAndNormalizeLightDirections(std::vector
<basegfx::B3DVector
>& rLightDirVec
)
707 basegfx::B3DHomMatrix aTransform
; // unit matrix
708 aTransform
.set(0, 0, 0.0);
709 aTransform
.set(0, 1, -1.0);
710 aTransform
.set(1, 0, -1.0);
711 aTransform
.set(1, 1, 0.0);
712 for (auto& rDirection
: rLightDirVec
)
714 rDirection
*= aTransform
;
715 rDirection
.normalize();
719 /** Gets the rotation angles fX and fY from the extrusion property RotateAngle in the map.
720 Does nothing if property does not exist.
721 @param [in] rPropertyMap should contain valid value in RotateAngle property
722 @param [out] fX, fY rotation angle in unit rad with orientation as in API.*/
723 void lcl_getXYAnglesFromMap(oox::PropertyMap
& rPropertyMap
, double& rfX
, double& rfY
)
725 if (!rPropertyMap
.hasProperty(oox::PROP_RotateAngle
))
727 css::drawing::EnhancedCustomShapeParameterPair aAnglePair
;
728 css::uno::Any aAny
= rPropertyMap
.getProperty(oox::PROP_RotateAngle
);
729 if (aAny
>>= aAnglePair
)
731 rfX
= basegfx::deg2rad(aAnglePair
.First
.Value
.get
<double>());
732 rfY
= basegfx::deg2rad(aAnglePair
.Second
.Value
.get
<double>());
736 /** Applies the rotations given in fX, fY, fZ to the light directions.
737 @details The rotations are applied in the order fZ, fY, fX. All angles have unit rad. The
738 orientation of the angles fX and fY is the same as in the extrusion property RotateAngle in
739 API. The orientation of angle fZ is the same as in shape property RotateAngle in API.
740 @param [in, out] rLightDirVec contains the to be transformed light directions
741 @param [in] fX angle for rotation around x-axis
742 @param [in] fY angle for rotation around y-axis
743 @param {in] fZ angle for rotation around z-axis*/
744 void lcl_ApplyShapeRotationToLights(std::vector
<basegfx::B3DVector
>& rLightDirVec
, const double& fX
,
745 const double& fY
, const double& fZ
)
747 basegfx::B3DHomMatrix aTransform
; // unit matrix
748 // rotate has the order first x, then y, last z. We need order z, y, x.
749 aTransform
.rotate(0.0, 0.0, -fZ
);
750 aTransform
.rotate(0.0, -fY
, 0.0);
751 aTransform
.rotate(fX
, 0.0, 0.0);
752 for (auto it
= rLightDirVec
.begin(); it
!= rLightDirVec
.end(); ++it
)
756 /** Applies the light rig rotation to the directions of the individual lights
757 @details A light rig has a mandatory attribute 'dir' for rotating the rig in 45deg steps. It might
758 have an element 'rot', that describes a rotation by spherical coordinates 'lat', 'lon' and
759 'rev'. The element has precedence over the attribute.
760 @param [in] p3DProperties contains info about light rig.
761 @param {in, out] rLightDirVec contains for each individual light its direction in shape coordinate
762 system with x-axis right, y-axis down, z-axis toward observer.*/
763 void lcl_IncorporateRigRotationIntoLightDirections(
764 const oox::drawingml::Shape3DPropertiesPtr p3DProperties
,
765 std::vector
<basegfx::B3DVector
>& rLightDirVec
)
767 basegfx::B3DHomMatrix aTransform
; // unit matrix
768 // if a 'rot' element exists, then all of 'lat', 'lon' and 'rev' needs to exist.
769 if ((*p3DProperties
).maLightRigRotation
.mnLatitude
.has_value())
772 = basegfx::deg2rad
<60000>((*p3DProperties
).maLightRigRotation
.mnLatitude
.value_or(0));
774 = basegfx::deg2rad
<60000>((*p3DProperties
).maLightRigRotation
.mnLongitude
.value_or(0));
776 = basegfx::deg2rad
<60000>((*p3DProperties
).maLightRigRotation
.mnRevolution
.value_or(0));
777 aTransform
.rotate(0.0, 0.0, fRev
);
778 aTransform
.rotate(fLat
, fLon
, 0.0);
783 switch ((*p3DProperties
).mnLightRigDirection
.value_or(XML_t
))
812 // Rotation is always only around z-axis
813 aTransform
.rotate(0.0, 0.0, basegfx::deg2rad(nDir
));
815 for (auto& rDirection
: rLightDirVec
)
816 rDirection
*= aTransform
;
819 /** The lights in OOXML are basically incompatible with our lights. We try to tweak some rigs to
820 reduce obvious problems.
821 @param [in, out] rLightDirVec light directions with already incorporated rotations
822 @param [in, out] rLightRig the to be tweaked rig
824 void lcl_tweakLightRig(std::vector
<basegfx::B3DVector
>& rLightDirVec
, PrstLightRigValues
& rLightRig
)
826 if (rLightRig
.sLightRigName
== u
"brightRoom")
828 // The fourth light has more significant direction.
829 if (rLightDirVec
.size() >= 4 && rLightRig
.aLight2
.has_value()
830 && rLightRig
.aLight4
.has_value())
832 std::swap(rLightDirVec
[1], rLightDirVec
[3]);
833 // swap fourth and second in light rig too, swap their other properties too.
834 MSOLight aTemp
= rLightRig
.aLight4
.value();
835 rLightRig
.aLight4
= rLightRig
.aLight2
.value();
836 rLightRig
.aLight2
= aTemp
;
837 // and make it brighter, 1.0 instead of 0.5
838 rLightRig
.aLight2
.value().fMSOColorR
= 1.0;
839 rLightRig
.aLight2
.value().fMSOColorG
= 1.0;
840 rLightRig
.aLight2
.value().fMSOColorB
= 1.0;
842 // The object is far too bright.
843 rLightRig
.fAmbient
= 0.6; // instead 1.5
845 else if (rLightRig
.sLightRigName
== u
"chilly" || rLightRig
.sLightRigName
== u
"flood")
847 // They are too dark.
848 rLightRig
.fAmbient
= 0.35; // instead 0.11 resp. 0.13
850 else if (rLightRig
.sLightRigName
== u
"freezing" || rLightRig
.sLightRigName
== u
"morning"
851 || rLightRig
.sLightRigName
== u
"sunrise")
853 // These rigs have no ambient color but four lights. The objects are too dark with only
855 rLightRig
.fAmbient
= 0.4;
857 else if (rLightRig
.sLightRigName
== u
"sunset")
859 // The fourth light is more significant.
860 if (rLightDirVec
.size() >= 4 && rLightRig
.aLight4
.has_value())
862 MSOLight aTemp
= rLightRig
.aLight2
.value();
863 rLightRig
.aLight2
= rLightRig
.aLight4
.value();
864 rLightRig
.aLight4
= aTemp
;
865 std::swap(rLightDirVec
[1], rLightDirVec
[3]);
868 else if (rLightRig
.sLightRigName
== u
"soft")
870 // This is the only modern light rig with Scale=0.5 and Offset=0.5. It would be harsh=false
871 // and specular=true at the same time. We switch specular off as that is used to set harsh on.
872 rLightRig
.aLight1
.bSpecular
= false;
876 } // end anonymous namespace
878 void Scene3DHelper::setLightingProperties(const oox::drawingml::Shape3DPropertiesPtr p3DProperties
,
879 const double& rfRotZ
, oox::PropertyMap
& rPropertyMap
)
881 if (!p3DProperties
|| !(*p3DProperties
).mnLightRigType
.has_value())
884 // get index of light rig in aPrstLightRigValuesArray
885 const sal_Int32
nLightRigPrstID((*p3DProperties
).mnLightRigType
.value()); // token
886 sal_Int16 nPrstLightRigIndex
= lcl_getPrstLightRigIndex(
887 oox::drawingml::Generic3DProperties::getLightRigName(nLightRigPrstID
));
888 if (nPrstLightRigIndex
< 0 or nPrstLightRigIndex
>= nLightRigPresetCount
)
889 return; // error in document. OOXML specifies a fixed set of preset light rig types.
891 // The light rig is copied because it might be tweaked later.
892 PrstLightRigValues aLightRig
= aPrstLightRigValuesArray
[nPrstLightRigIndex
];
894 std::vector
<basegfx::B3DVector
> aLightDirVec
;
895 aLightDirVec
.reserve(4);
896 lcl_getLightDirectionsFromRig(aLightRig
, aLightDirVec
);
897 lcl_AdaptAndNormalizeLightDirections(aLightDirVec
);
899 lcl_IncorporateRigRotationIntoLightDirections(p3DProperties
, aLightDirVec
);
901 // Parts (1) to (6) are workarounds for the problem that our current model as well as API and
902 // ODF are not able to describe or use the capabilities of extruded custom shapes of MS Office.
903 // If the implementation is improved one day, the parts will need to be adapted.
905 // (1) Moving the camera around does not change shape or light directions for modern cameras in
906 // MS Office. For legacy cameras MS Office behaves same as LibreOffice: Not the camera is moved
907 // but the shape is rotated. For modern cameras we need to rotate the light rig the same way as
908 // the shape to get a similar illumination as in MS Office.
909 if (mnPrstCameraIndex
< 20 || 37 < mnPrstCameraIndex
)
911 double fX
= 0.0; // unit rad, orientation as in API
912 double fY
= 0.0; // unit rad, orientation as in API
913 lcl_getXYAnglesFromMap(rPropertyMap
, fX
, fY
);
914 lcl_ApplyShapeRotationToLights(aLightDirVec
, fX
, fY
, rfRotZ
);
917 // (2) We try to tweak some light rigs a little bit, e.g. make sure the first light is specular
918 // or add some ambient light instead of not possible third or forth light.
919 lcl_tweakLightRig(aLightDirVec
, aLightRig
);
921 rPropertyMap
.setProperty(oox::PROP_Brightness
, aLightRig
.fAmbient
.value_or(0) * 100);
923 // (3) A 3D-scene of an extruded custom shape has currently no colored light, but only a
924 // level. We get the level from Red.
925 rPropertyMap
.setProperty(oox::PROP_FirstLightLevel
, aLightRig
.aLight1
.fMSOColorR
* 100);
927 // (4) 'Specular' and 'Diffuse' in the MSO specification belong to modern 3D geometry. That is not
928 // available in our legacy one. Here we treat 'Specular' as property 'Harsh' and ignore 'Diffuse'.
929 rPropertyMap
.setProperty(oox::PROP_FirstLightHarsh
, aLightRig
.aLight1
.bSpecular
);
931 // (5) In fact we have stored position in FirstLightDirection and SecondLightDirection,
932 // not direction, thus the minus sign.
933 css::drawing::Direction3D aLightPos
;
934 aLightPos
.DirectionX
= -aLightDirVec
[0].getX();
935 aLightPos
.DirectionY
= -aLightDirVec
[0].getY();
936 aLightPos
.DirectionZ
= -aLightDirVec
[0].getZ();
937 rPropertyMap
.setProperty(oox::PROP_FirstLightDirection
, aLightPos
);
939 // (6) For extruded custom shapes only two lights are possible although our rendering engine has
940 // eight lights. We will loose lights.
941 if (aLightDirVec
.size() > 1)
943 rPropertyMap
.setProperty(oox::PROP_SecondLightLevel
,
944 aLightRig
.aLight2
.value().fMSOColorR
* 100);
945 rPropertyMap
.setProperty(oox::PROP_SecondLightHarsh
, aLightRig
.aLight2
.value().bSpecular
);
946 aLightPos
.DirectionX
= -aLightDirVec
[1].getX();
947 aLightPos
.DirectionY
= -aLightDirVec
[1].getY();
948 aLightPos
.DirectionZ
= -aLightDirVec
[1].getZ();
949 rPropertyMap
.setProperty(oox::PROP_SecondLightDirection
, aLightPos
);
952 rPropertyMap
.setProperty(oox::PROP_SecondLightLevel
, 0.0); // prevent defaults.
956 /** This struct is used to hold material values for extruded custom shapes. Because we cannot yet
957 render all material properties MS Office uses, the values are adapted to our current abilities.*/
959 struct MaterialValues
961 std::u16string_view msMaterialPrstName
; // identifies the material type
962 // Corresponds to MS Office 'Diffuse Color' and 'Ambient Color'.
964 double fSpecularity
; // Corresponds to MS Office 'Specular Color'.
965 // Corresponds to our 'Shininess' as 2^(Shininess/10) = nSpecularPower.
966 sal_uInt8 nSpecularPower
;
967 bool bMetal
; // Corresponds to MS Office 'Metal'
968 // constants com::sun::star::drawing::EnhancedCustomShapeMetalType
969 // MetalMSCompatible belongs to 'legacyMetal' material type.
970 std::optional
<sal_Int16
> oMetalType
; // MetalODF = 0, MetalMSCompatible = 1
971 // MS Office properties 'Emissive Color', 'Diffuse Fresnel', 'Alpha Fresnel' and 'Blinn Highlight'
972 // are not contained.
974 } // end anonymous namespace
976 // OOXML standard has a fixed amount of 15 material types. The type 'legacyWireframe' is special and
977 // thus is handled separately. A spreadsheet with further remarks is attached to tdf#70039.
978 constexpr sal_uInt16
nPrstMaterialCount(14);
979 constexpr MaterialValues aPrstMaterialArray
[nPrstMaterialCount
]
980 = { { u
"clear", 100, 60, 20, false, {} },
981 { u
"dkEdge", 70, 100, 35, false, {} },
982 { u
"flat", 100, 80, 50, false, {} },
983 { u
"legacyMatte", 100, 0, 0, false, {} },
984 { u
"legacyMetal", 66.69921875, 122.0703125, 32, true, { 1 } },
985 { u
"legacyPlastic", 100, 122.0703125, 32, false, {} },
986 { u
"matte", 100, 0, 0, false, {} },
987 { u
"metal", 100, 100, 12, true, { 0 } },
988 { u
"plastic", 100, 60, 12, true, { 0 } },
989 { u
"powder", 100, 30, 10, false, {} },
990 { u
"softEdge", 100, 100, 35, false, {} },
991 { u
"softmetal", 100, 100, 8, true, { 0 } },
992 { u
"translucentPowder", 100, 30, 10, true, { 0 } },
993 { u
"warmMatte", 100, 30, 8, false, {} } };
995 void Scene3DHelper::setMaterialProperties(const oox::drawingml::Shape3DPropertiesPtr p3DProperties
,
996 oox::PropertyMap
& rPropertyMap
)
1001 // PowerPoint does not write aus prstMaterial="warmMatte", but handles it as default.
1002 const sal_Int32 nMaterialID
= (*p3DProperties
).mnMaterial
.value_or(XML_warmMatte
); // token
1004 // special handling for 'legacyWireframe'
1005 if (nMaterialID
== XML_legacyWireframe
)
1007 // This is handled via shade mode of the scene.
1008 rPropertyMap
.setProperty(oox::PROP_ShadeMode
, css::drawing::ShadeMode_DRAFT
);
1009 // Notice, the color of the strokes will be different from MS Office, because LO uses the
1010 // shape line color even if the line style is 'none', whereas MS Office uses contour color or
1015 sal_Int16
nIdx(0); // Index into aPrstMaterialArray
1016 while (nIdx
< nPrstMaterialCount
1017 && aPrstMaterialArray
[nIdx
].msMaterialPrstName
1018 != oox::drawingml::Generic3DProperties::getPresetMaterialTypeString(nMaterialID
))
1020 if (nIdx
>= nPrstMaterialCount
)
1021 return; // error in document
1023 // extrusion-diffuse, extrusion-specularity-loext
1024 rPropertyMap
.setProperty(oox::PROP_Diffusion
, aPrstMaterialArray
[nIdx
].fDiffusion
);
1025 rPropertyMap
.setProperty(oox::PROP_Specularity
, aPrstMaterialArray
[nIdx
].fSpecularity
);
1027 // extrusion-shininess
1028 double fShininess
= 0.0;
1029 // Conversion 2^(fShininess/10) = nSpecularPower
1030 if (aPrstMaterialArray
[nIdx
].nSpecularPower
> 0)
1031 fShininess
= 10.0 * std::log2(aPrstMaterialArray
[nIdx
].nSpecularPower
);
1032 rPropertyMap
.setProperty(oox::PROP_Shininess
, fShininess
);
1034 // extrusion-metal, extrusion-metal-type
1035 rPropertyMap
.setProperty(oox::PROP_Metal
, aPrstMaterialArray
[nIdx
].bMetal
);
1036 if (aPrstMaterialArray
[nIdx
].bMetal
)
1038 sal_Int16 eMetalType
= aPrstMaterialArray
[nIdx
].oMetalType
.value_or(0) == 1
1039 ? css::drawing::EnhancedCustomShapeMetalType::MetalMSCompatible
1040 : css::drawing::EnhancedCustomShapeMetalType::MetalODF
;
1041 rPropertyMap
.setProperty(oox::PROP_MetalType
, eMetalType
);
1045 } // end namespace oox
1047 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */