/src/libreoffice/oox/source/drawingml/scene3dhelper.cxx
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | * |
9 | | */ |
10 | | |
11 | | #include <drawingml/scene3dhelper.hxx> |
12 | | |
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> |
20 | | |
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> |
29 | | |
30 | | #include <cmath> |
31 | | |
32 | | namespace oox |
33 | | { |
34 | | /** This struct is used to hold values from the OOXML camera preset types.*/ |
35 | | namespace |
36 | | { |
37 | | struct PrstCameraValues |
38 | | { |
39 | | std::u16string_view msCameraPrstName; // identifies the value set |
40 | | |
41 | | bool mbIsParallel; |
42 | | |
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 |
47 | | |
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 |
52 | | |
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 |
56 | | |
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 |
62 | | |
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. |
65 | | }; |
66 | | } // end anonymous namespace |
67 | | |
68 | | // The values were found experimental using MS Office. A spreadsheet with remarks is attached |
69 | | // to tdf#70039. |
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, |
124 | | 38451 }, |
125 | | { u"perspectiveContrastingRightFacing", false, 624000, 18966000, 216000, 0, 0, 0, 0, 0, 0, |
126 | | 38451 }, |
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, |
129 | | 18981 }, |
130 | | { u"perspectiveHeroicExtremeRightFacing", false, 486000, 19530000, 174000, 0, 0, 0, 0, 0, 0, |
131 | | 18981 }, |
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, |
134 | | 38451 }, |
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 } |
139 | | }; |
140 | | |
141 | | namespace |
142 | | { |
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) |
147 | 26 | { |
148 | 26 | sal_Int16 nIt(0); |
149 | 728 | while (nIt < nCameraPresetCount && aPrstCameraValuesArray[nIt].msCameraPrstName != sPresetName) |
150 | 702 | ++nIt; |
151 | 26 | if (nIt >= nCameraPresetCount) |
152 | 0 | { |
153 | 0 | nIt = -1; // Error is handled by caller |
154 | 0 | } |
155 | 26 | return nIt; |
156 | 26 | } |
157 | | } // end anonymous namespace |
158 | | |
159 | | void Scene3DHelper::getAPIAnglesFromOOXAngle(const sal_Int32 nLat, const sal_Int32 nLon, |
160 | | const sal_Int32 nRev, double& fX, double& fY, |
161 | | double& fZ) |
162 | 16 | { |
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 |
169 | | // radians. |
170 | | |
171 | | // First we build the total rotation matrix from the OOX angles. y-axis points down. |
172 | 16 | basegfx::B3DHomMatrix aXMat; |
173 | 16 | const double fLatRad = basegfx::deg2rad<60000>(nLat); |
174 | 16 | aXMat.set(1, 1, cos(fLatRad)); |
175 | 16 | aXMat.set(2, 2, cos(fLatRad)); |
176 | 16 | aXMat.set(1, 2, sin(fLatRad)); |
177 | 16 | aXMat.set(2, 1, -sin(fLatRad)); |
178 | | |
179 | 16 | basegfx::B3DHomMatrix aYMat; |
180 | 16 | const double fLonRad = basegfx::deg2rad<60000>(nLon); |
181 | 16 | aYMat.set(0, 0, cos(fLonRad)); |
182 | 16 | aYMat.set(2, 2, cos(fLonRad)); |
183 | 16 | aYMat.set(0, 2, -sin(fLonRad)); |
184 | 16 | aYMat.set(2, 0, sin(fLonRad)); |
185 | | |
186 | 16 | basegfx::B3DHomMatrix aZMat; |
187 | 16 | const double fRevRad = basegfx::deg2rad<60000>(nRev); |
188 | 16 | aZMat.set(0, 0, cos(fRevRad)); |
189 | 16 | aZMat.set(1, 1, cos(fRevRad)); |
190 | 16 | aZMat.set(0, 1, sin(fRevRad)); |
191 | 16 | aZMat.set(1, 0, -sin(fRevRad)); |
192 | 16 | basegfx::B3DHomMatrix aTotalMat = aZMat * aXMat * aYMat; |
193 | | |
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 | 16 | fY = std::asin(-aTotalMat.get(0, 2)); |
198 | | |
199 | 16 | if (basegfx::fTools::equalZero(cos(fY))) |
200 | 0 | { |
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. |
206 | 0 | fX = 0.0; |
207 | 0 | fZ = std::atan2(aTotalMat.get(2, 1), aTotalMat.get(1, 1)); |
208 | 0 | } |
209 | 16 | else |
210 | 16 | { |
211 | 16 | fX = std::atan2(-aTotalMat.get(1, 2) / cos(fY), aTotalMat.get(2, 2) / cos(fY)); |
212 | 16 | fZ = std::atan2(aTotalMat.get(0, 1) / cos(fY), aTotalMat.get(0, 0) / cos(fY)); |
213 | 16 | } |
214 | 16 | } |
215 | | |
216 | | void Scene3DHelper::getAPIAnglesFrom3DProperties( |
217 | | const oox::drawingml::Shape3DPropertiesPtr p3DProperties, const sal_Int32& rnMSOShapeRotation, |
218 | | double& fX, double& fY, double& fZ) |
219 | 16 | { |
220 | 16 | if (!p3DProperties) |
221 | 0 | return; |
222 | | |
223 | | // on x-axis, unit 1/60000 deg |
224 | 16 | sal_Int32 nLatitude = (*p3DProperties).maCameraRotation.mnLatitude.value_or(0); |
225 | | // on y-axis, unit 1/60000 deg |
226 | 16 | sal_Int32 nLongitude = (*p3DProperties).maCameraRotation.mnLongitude.value_or(0); |
227 | | // on z-axis, unit 1/60000 deg |
228 | 16 | sal_Int32 nRevolution = (*p3DProperties).maCameraRotation.mnRevolution.value_or(0); |
229 | | |
230 | | // Some projection types need special treatment: |
231 | 16 | if (29 <= mnPrstCameraIndex && mnPrstCameraIndex <= 37) |
232 | 0 | { |
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. |
235 | 0 | nRevolution = 0; |
236 | 0 | } |
237 | 16 | else if (47 <= mnPrstCameraIndex) |
238 | 0 | { |
239 | 0 | assert(mnPrstCameraIndex <= 61 |
240 | 0 | && "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 |
244 | | // entered angles. |
245 | 0 | if (rnMSOShapeRotation != 0 && nLatitude > 5400000 && nLatitude <= 16200000) |
246 | 0 | nRevolution += 10800000; |
247 | 0 | } |
248 | | |
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 | 16 | if (!(*p3DProperties).maCameraRotation.mnRevolution.has_value()) |
254 | 0 | { |
255 | | // The angles are given in 1/60000 deg in aPrstCameraValuesArray. |
256 | 0 | nLatitude = aPrstCameraValuesArray[mnPrstCameraIndex].mfRotateAngleX; |
257 | 0 | nLongitude = aPrstCameraValuesArray[mnPrstCameraIndex].mfRotateAngleY; |
258 | 0 | nRevolution = aPrstCameraValuesArray[mnPrstCameraIndex].mfRotateAngleZ; |
259 | 0 | } |
260 | | |
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 | 16 | bool bIsLegacyCamera = 20 <= mnPrstCameraIndex && mnPrstCameraIndex <= 37; |
266 | 16 | if (!bIsLegacyCamera) |
267 | 13 | nRevolution -= rnMSOShapeRotation; |
268 | | |
269 | | // Now calculate the angles for LO rotation order and orientation. |
270 | 16 | Scene3DHelper::getAPIAnglesFromOOXAngle(nLatitude, nLongitude, nRevolution, fX, fY, fZ); |
271 | | |
272 | 16 | if (bIsLegacyCamera) |
273 | 3 | fZ -= basegfx::deg2rad<60000>(rnMSOShapeRotation); |
274 | 16 | } |
275 | | |
276 | | void Scene3DHelper::addRotateAngleToMap(oox::PropertyMap& rPropertyMap, const double fX, |
277 | | const double fY) |
278 | 3 | { |
279 | 3 | css::drawing::EnhancedCustomShapeParameterPair aAnglePair; |
280 | 3 | aAnglePair.First.Value <<= basegfx::rad2deg(fX); |
281 | 3 | aAnglePair.First.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; |
282 | 3 | aAnglePair.Second.Value <<= basegfx::rad2deg(fY); |
283 | 3 | aAnglePair.Second.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; |
284 | 3 | rPropertyMap.setAnyProperty(oox::PROP_RotateAngle, css::uno::Any(aAnglePair)); |
285 | 3 | } |
286 | | |
287 | | void Scene3DHelper::addExtrusionDepthToMap(const oox::drawingml::Shape3DPropertiesPtr p3DProperties, |
288 | | oox::PropertyMap& rPropertyMap) |
289 | 3 | { |
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 | 3 | sal_Int32 nDepthAmount = (*p3DProperties).mnExtrusionH.value_or(0); // unit EMU |
294 | 3 | double fDepthAmount = o3tl::convert(nDepthAmount, o3tl::Length::emu, o3tl::Length::mm100); |
295 | 3 | sal_Int32 nZPosition = (*p3DProperties).mnShapeZ.value_or(0); // unit EMU |
296 | 3 | double fZPosition = o3tl::convert(nZPosition, o3tl::Length::emu, o3tl::Length::mm100); |
297 | 3 | double fDepthRelPos = 0.0; |
298 | 3 | if (nDepthAmount == 0 && nZPosition != 0) |
299 | 0 | { |
300 | | // We cannot express the position relative to the extrusion depth. |
301 | | // Use an artificial, small depth of 1Hmm |
302 | 0 | fDepthRelPos = fZPosition; |
303 | 0 | fDepthAmount = 1.0; // unit Hmm |
304 | 0 | } |
305 | 3 | else if (nDepthAmount != 0) |
306 | 3 | fDepthRelPos = fZPosition / fDepthAmount; |
307 | | |
308 | 3 | css::drawing::EnhancedCustomShapeParameterPair aPair; |
309 | 3 | css::drawing::EnhancedCustomShapeParameter& rDepthAmount = aPair.First; |
310 | 3 | rDepthAmount.Value <<= fDepthAmount; |
311 | 3 | rDepthAmount.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; |
312 | 3 | css::drawing::EnhancedCustomShapeParameter& rDepthFraction = aPair.Second; |
313 | 3 | rDepthFraction.Value <<= fDepthRelPos; |
314 | 3 | rDepthFraction.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; |
315 | 3 | rPropertyMap.setProperty(oox::PROP_Depth, aPair); |
316 | 3 | } |
317 | | |
318 | | void Scene3DHelper::addProjectionGeometryToMap( |
319 | | const oox::drawingml::Shape3DPropertiesPtr p3DProperties, oox::PropertyMap& rPropertyMap, |
320 | | const bool bIsParallel, const sal_Int32 rnMSOShapeRotation) |
321 | 3 | { |
322 | | // origin is needed for parallel and perspective as well |
323 | 3 | css::drawing::EnhancedCustomShapeParameterPair aOrigin; |
324 | 3 | aOrigin.First.Value <<= aPrstCameraValuesArray[mnPrstCameraIndex].mfOriginX; |
325 | 3 | aOrigin.First.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; |
326 | 3 | aOrigin.Second.Value <<= aPrstCameraValuesArray[mnPrstCameraIndex].mfOriginY; |
327 | 3 | aOrigin.Second.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; |
328 | 3 | rPropertyMap.setProperty(oox::PROP_Origin, aOrigin); |
329 | | |
330 | 3 | if (bIsParallel) |
331 | 3 | { |
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 | 3 | double fSkewAngle = aPrstCameraValuesArray[mnPrstCameraIndex].mfSkewAngle; // unit degree |
336 | 3 | 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 | 3 | if (38 <= mnPrstCameraIndex && mnPrstCameraIndex <= 45) |
342 | 0 | { |
343 | 0 | fSkewAngle -= rnMSOShapeRotation / 60000.0; |
344 | 0 | } |
345 | 3 | css::drawing::EnhancedCustomShapeParameterPair aSkew; |
346 | 3 | aSkew.First.Value <<= fSkewAmount; |
347 | 3 | aSkew.First.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; |
348 | 3 | aSkew.Second.Value <<= fSkewAngle; |
349 | 3 | aSkew.Second.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; |
350 | 3 | rPropertyMap.setProperty(oox::PROP_Skew, aSkew); |
351 | 3 | } |
352 | 0 | else |
353 | 0 | { |
354 | | // PERSPECTIVE needs API property ViewPoint. |
355 | 0 | css::drawing::Position3D aViewPoint; |
356 | | |
357 | | // x- and y-coordinate depend on preset camera type. |
358 | 0 | aViewPoint.PositionX = aPrstCameraValuesArray[mnPrstCameraIndex].mfViewPointX; |
359 | 0 | aViewPoint.PositionY = aPrstCameraValuesArray[mnPrstCameraIndex].mfViewPointY; |
360 | | |
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 | 0 | if ((*p3DProperties).mfFieldOfVision.has_value()) |
364 | 0 | { |
365 | 0 | double fFov = (*p3DProperties).mfFieldOfVision.value(); |
366 | 0 | fFov = std::clamp(fFov, 0.5, 179.5); |
367 | | // 15976 = 25000 * tan(32.5°) as in legacy. Better ideas to calculate the distance are |
368 | | // welcome. |
369 | 0 | aViewPoint.PositionZ = 15976.0 / tan(basegfx::deg2rad(fFov / 2.0)); |
370 | 0 | } |
371 | 0 | else |
372 | 0 | aViewPoint.PositionZ = aPrstCameraValuesArray[mnPrstCameraIndex].mfViewPointZ; |
373 | |
|
374 | 0 | rPropertyMap.setProperty(oox::PROP_ViewPoint, aViewPoint); |
375 | 0 | } |
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. |
379 | 3 | } |
380 | | |
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) |
386 | 82.4k | { |
387 | | // We convert rnMSOShapeRotation, so that Shape::createAndInsert() can use rRotZ the same way in |
388 | | // all cases. |
389 | 82.4k | rRotZ = basegfx::deg2rad<60000>(-rnMSOShapeRotation); |
390 | | |
391 | 82.4k | if (!p3DProperties || !(*p3DProperties).mnPreset.has_value()) |
392 | 82.4k | return false; |
393 | | |
394 | 26 | const sal_Int32 nCameraPrstID((*p3DProperties).mnPreset.value()); |
395 | 26 | sal_Int16 nPrstCameraIndex |
396 | 26 | = getPrstCameraIndex(oox::drawingml::Generic3DProperties::getCameraPrstName(nCameraPrstID)); |
397 | 26 | if (nPrstCameraIndex < 0 or nPrstCameraIndex >= nCameraPresetCount) |
398 | 0 | return false; // error in document. OOXML specifies a fixed set of preset camera types. |
399 | 26 | mnPrstCameraIndex = nPrstCameraIndex; |
400 | | |
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 | 26 | rExtrusionColor = (*p3DProperties).maExtrusionColor; |
404 | | |
405 | | // Even if we do not extrude an image, we still want to get the z-Rotation. |
406 | 26 | if (bBlockExtrusion) |
407 | 10 | { |
408 | 10 | rRotZ = basegfx::deg2rad<60000>((*p3DProperties).maCameraRotation.mnRevolution.value_or(0) |
409 | 10 | - rnMSOShapeRotation); |
410 | 10 | return false; |
411 | 10 | } |
412 | | |
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. |
417 | 16 | double fX = 0.0; |
418 | 16 | double fY = 0.0; |
419 | 16 | Scene3DHelper::getAPIAnglesFrom3DProperties(p3DProperties, rnMSOShapeRotation, fX, fY, rRotZ); |
420 | 16 | sal_Int32 nDepthAmount = (*p3DProperties).mnExtrusionH.value_or(0); |
421 | 16 | bool bIsParallel = aPrstCameraValuesArray[mnPrstCameraIndex].mbIsParallel; |
422 | 16 | bool bIsParallelFrontType |
423 | 16 | = (nCameraPrstID == XML_legacyObliqueFront) || (nCameraPrstID == XML_orthographicFront); |
424 | 16 | bool bCreateExtrusion = (!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY)) |
425 | 16 | || (!bIsParallel && nDepthAmount > 0) |
426 | 16 | || (bIsParallel && !bIsParallelFrontType); |
427 | | |
428 | 16 | if (!bCreateExtrusion) |
429 | 13 | return false; |
430 | | |
431 | | // Create the extrusion properties in rPropertyMap so that they can be directly used. |
432 | | // Turn extrusion on |
433 | 3 | rPropertyMap.setProperty(oox::PROP_Extrusion, true); |
434 | | |
435 | | // Dummy value. Will be changed from evaluating the material properties. |
436 | 3 | rPropertyMap.setProperty(oox::PROP_Diffusion, 100.0); |
437 | | |
438 | | // Camera properties |
439 | 3 | css::drawing::ProjectionMode eProjectionMode = bIsParallel |
440 | 3 | ? css::drawing::ProjectionMode_PARALLEL |
441 | 3 | : css::drawing::ProjectionMode_PERSPECTIVE; |
442 | 3 | rPropertyMap.setProperty(oox::PROP_ProjectionMode, eProjectionMode); |
443 | | |
444 | 3 | Scene3DHelper::addRotateAngleToMap(rPropertyMap, fX, fY); |
445 | | |
446 | 3 | Scene3DHelper::addProjectionGeometryToMap(p3DProperties, rPropertyMap, bIsParallel, |
447 | 3 | rnMSOShapeRotation); |
448 | | |
449 | | // Shape properties |
450 | 3 | Scene3DHelper::addExtrusionDepthToMap(p3DProperties, rPropertyMap); |
451 | | |
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 | 3 | rPropertyMap.setProperty(oox::PROP_Color, true); |
455 | | // ToDo: Some materials might need ShadeMode_Smooth or ShadeMode_PHONG. |
456 | 3 | rPropertyMap.setProperty(oox::PROP_ShadeMode, css::drawing::ShadeMode_FLAT); |
457 | | |
458 | 3 | return true; |
459 | 16 | } |
460 | | |
461 | | namespace |
462 | | { |
463 | | /* This struct is used to hold light properties for a light in a preset light rig.*/ |
464 | | struct MSOLight |
465 | | { |
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. |
469 | | double fMSOColorR; |
470 | | double fMSOColorG; |
471 | | double fMSOColorB; |
472 | | // MSO uses 4 decimals precision, some light directions are not normalized. |
473 | | double fMSOLightDirectionX; |
474 | | double fMSOLightDirectionY; |
475 | | double fMSOLightDirectionZ; |
476 | | double fScale; |
477 | | double fOffset; |
478 | | bool bSpecular; |
479 | | bool bDiffuse; |
480 | | }; |
481 | | |
482 | | /* This struct is used to hold properties of a light rig*/ |
483 | | struct PrstLightRigValues |
484 | | { |
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 |
491 | | MSOLight aLight1; |
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. |
496 | | }; |
497 | | } // end anonymous namespace |
498 | | |
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] = { |
503 | | { u"balanced", |
504 | | { 0.13 }, |
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 } }, |
508 | | {} }, |
509 | | { u"brightRoom", |
510 | | { 1.5 }, |
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 } } }, |
515 | | { u"chilly", |
516 | | { 0.11 }, |
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 } } }, |
521 | | { u"contrasting", |
522 | | { 1 }, |
523 | | { 1, 1, 1, 0, -1, 0, 1, 0, true, false }, |
524 | | { { 1, 1, 1, 0, 1, 0, 1, 0, true, false } }, |
525 | | {}, |
526 | | {} }, |
527 | | { u"flat", |
528 | | { 1 }, |
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 } }, |
532 | | {} }, |
533 | | { u"flood", |
534 | | { 0.13 }, |
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 } }, |
538 | | {} }, |
539 | | { u"freezing", |
540 | | {}, |
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 } } }, |
545 | | { u"glow", |
546 | | { 1 }, |
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 } }, |
549 | | {}, |
550 | | {} }, |
551 | | { u"harsh", |
552 | | { 0.28 }, |
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 } }, |
555 | | {}, |
556 | | {} }, |
557 | | { u"legacyFlat1", |
558 | | { 0.305 }, |
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 } }, |
561 | | {}, |
562 | | {} }, |
563 | | { u"legacyFlat2", |
564 | | { 0.305 }, |
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 } }, |
567 | | {}, |
568 | | {} }, |
569 | | { u"legacyFlat3", |
570 | | { 0.305 }, |
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 } }, |
573 | | {}, |
574 | | {} }, |
575 | | { u"legacyFlat4", |
576 | | { 0.305 }, |
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 } }, |
579 | | {}, |
580 | | {} }, |
581 | | { u"legacyHarsh1", |
582 | | { 0.061 }, |
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 } }, |
585 | | {}, |
586 | | {} }, |
587 | | { u"legacyHarsh2", |
588 | | { 0.061 }, |
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 } }, |
591 | | {}, |
592 | | {} }, |
593 | | { u"legacyHarsh3", |
594 | | { 0.061 }, |
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 } }, |
597 | | {}, |
598 | | {} }, |
599 | | { u"legacyHarsh4", |
600 | | { 0.061 }, |
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 } }, |
603 | | {}, |
604 | | {} }, |
605 | | { u"legacyNormal1", |
606 | | { 0.153 }, |
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 } }, |
609 | | {}, |
610 | | {} }, |
611 | | { u"legacyNormal2", |
612 | | { 0.153 }, |
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 } }, |
615 | | {}, |
616 | | {} }, |
617 | | { u"legacyNormal3", |
618 | | { 0.153 }, |
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 } }, |
621 | | {}, |
622 | | {} }, |
623 | | { u"legacyNormal4", |
624 | | { 0.153 }, |
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 } }, |
627 | | {}, |
628 | | {} }, |
629 | | { u"morning", |
630 | | {}, |
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 }, {}, {}, {} }, |
636 | | { u"sunrise", |
637 | | {}, |
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 } } }, |
642 | | { u"sunset", |
643 | | {}, |
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 } } }, |
648 | | { u"threePt", |
649 | | {}, |
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 } }, |
653 | | {} }, |
654 | | { u"twoPt", |
655 | | { 0.25 }, |
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 } }, |
658 | | {}, |
659 | | {} } |
660 | | }; |
661 | | |
662 | | namespace |
663 | | { |
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) |
668 | 3 | { |
669 | 3 | sal_Int16 nIt(0); |
670 | 30 | while (nIt < nLightRigPresetCount && aPrstLightRigValuesArray[nIt].sLightRigName != sPresetName) |
671 | 27 | ++nIt; |
672 | 3 | if (nIt >= nLightRigPresetCount) |
673 | 0 | { |
674 | 0 | nIt = -1; // Error is handled by caller |
675 | 0 | } |
676 | 3 | return nIt; |
677 | 3 | } |
678 | | |
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) |
684 | 3 | { |
685 | 6 | auto addLightDir = [&](const MSOLight& aMSOLight) { |
686 | 6 | basegfx::B3DVector aLightDir(aMSOLight.fMSOLightDirectionX, aMSOLight.fMSOLightDirectionY, |
687 | 6 | aMSOLight.fMSOLightDirectionZ); |
688 | 6 | rLightDirVec.push_back(std::move(aLightDir)); |
689 | 6 | }; |
690 | | // aLight1 always exists, the others are optional |
691 | 3 | addLightDir(rLightRig.aLight1); |
692 | 3 | if (rLightRig.aLight2.has_value()) |
693 | 3 | addLightDir(rLightRig.aLight2.value()); |
694 | 3 | if (rLightRig.aLight3.has_value()) |
695 | 0 | addLightDir(rLightRig.aLight3.value()); |
696 | 3 | if (rLightRig.aLight4.has_value()) |
697 | 0 | addLightDir(rLightRig.aLight4.value()); |
698 | 3 | } |
699 | | |
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) |
706 | 3 | { |
707 | 3 | basegfx::B3DHomMatrix aTransform; // unit matrix |
708 | 3 | aTransform.set(0, 0, 0.0); |
709 | 3 | aTransform.set(0, 1, -1.0); |
710 | 3 | aTransform.set(1, 0, -1.0); |
711 | 3 | aTransform.set(1, 1, 0.0); |
712 | 3 | for (auto& rDirection : rLightDirVec) |
713 | 6 | { |
714 | 6 | rDirection *= aTransform; |
715 | 6 | rDirection.normalize(); |
716 | 6 | } |
717 | 3 | } |
718 | | |
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) |
724 | 0 | { |
725 | 0 | if (!rPropertyMap.hasProperty(oox::PROP_RotateAngle)) |
726 | 0 | return; |
727 | 0 | css::drawing::EnhancedCustomShapeParameterPair aAnglePair; |
728 | 0 | css::uno::Any aAny = rPropertyMap.getProperty(oox::PROP_RotateAngle); |
729 | 0 | if (aAny >>= aAnglePair) |
730 | 0 | { |
731 | 0 | rfX = basegfx::deg2rad(aAnglePair.First.Value.get<double>()); |
732 | 0 | rfY = basegfx::deg2rad(aAnglePair.Second.Value.get<double>()); |
733 | 0 | } |
734 | 0 | } |
735 | | |
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) |
746 | 0 | { |
747 | 0 | basegfx::B3DHomMatrix aTransform; // unit matrix |
748 | | // rotate has the order first x, then y, last z. We need order z, y, x. |
749 | 0 | aTransform.rotate(0.0, 0.0, -fZ); |
750 | 0 | aTransform.rotate(0.0, -fY, 0.0); |
751 | 0 | aTransform.rotate(fX, 0.0, 0.0); |
752 | 0 | for (auto it = rLightDirVec.begin(); it != rLightDirVec.end(); ++it) |
753 | 0 | (*it) *= aTransform; |
754 | 0 | } |
755 | | |
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) |
766 | 3 | { |
767 | 3 | basegfx::B3DHomMatrix aTransform; // unit matrix |
768 | | // if a 'rot' element exists, then all of 'lat', 'lon' and 'rev' needs to exist. |
769 | 3 | if ((*p3DProperties).maLightRigRotation.mnLatitude.has_value()) |
770 | 0 | { |
771 | 0 | double fLat |
772 | 0 | = basegfx::deg2rad<60000>((*p3DProperties).maLightRigRotation.mnLatitude.value_or(0)); |
773 | 0 | double fLon |
774 | 0 | = basegfx::deg2rad<60000>((*p3DProperties).maLightRigRotation.mnLongitude.value_or(0)); |
775 | 0 | double fRev |
776 | 0 | = basegfx::deg2rad<60000>((*p3DProperties).maLightRigRotation.mnRevolution.value_or(0)); |
777 | 0 | aTransform.rotate(0.0, 0.0, fRev); |
778 | 0 | aTransform.rotate(fLat, fLon, 0.0); |
779 | 0 | } |
780 | 3 | else |
781 | 3 | { |
782 | 3 | sal_Int32 nDir = 0; |
783 | 3 | switch ((*p3DProperties).mnLightRigDirection.value_or(XML_t)) |
784 | 3 | { |
785 | 3 | case XML_t: |
786 | 3 | nDir = 0; |
787 | 3 | break; |
788 | 0 | case XML_tr: |
789 | 0 | nDir = 45; |
790 | 0 | break; |
791 | 0 | case XML_r: |
792 | 0 | nDir = 90; |
793 | 0 | break; |
794 | 0 | case XML_br: |
795 | 0 | nDir = 135; |
796 | 0 | break; |
797 | 0 | case XML_b: |
798 | 0 | nDir = 180; |
799 | 0 | break; // or -180 |
800 | 0 | case XML_bl: |
801 | 0 | nDir = -135; |
802 | 0 | break; |
803 | 0 | case XML_l: |
804 | 0 | nDir = -90; |
805 | 0 | break; |
806 | 0 | case XML_tl: |
807 | 0 | nDir = -45; |
808 | 0 | break; |
809 | 0 | default: |
810 | 0 | nDir = 0; |
811 | 3 | } |
812 | | // Rotation is always only around z-axis |
813 | 3 | aTransform.rotate(0.0, 0.0, basegfx::deg2rad(nDir)); |
814 | 3 | } |
815 | 3 | for (auto& rDirection : rLightDirVec) |
816 | 6 | rDirection *= aTransform; |
817 | 3 | } |
818 | | |
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 |
823 | | */ |
824 | | void lcl_tweakLightRig(std::vector<basegfx::B3DVector>& rLightDirVec, PrstLightRigValues& rLightRig) |
825 | 3 | { |
826 | 3 | if (rLightRig.sLightRigName == u"brightRoom") |
827 | 0 | { |
828 | | // The fourth light has more significant direction. |
829 | 0 | if (rLightDirVec.size() >= 4 && rLightRig.aLight2.has_value() |
830 | 0 | && rLightRig.aLight4.has_value()) |
831 | 0 | { |
832 | 0 | std::swap(rLightDirVec[1], rLightDirVec[3]); |
833 | | // swap fourth and second in light rig too, swap their other properties too. |
834 | 0 | MSOLight aTemp = rLightRig.aLight4.value(); |
835 | 0 | rLightRig.aLight4 = rLightRig.aLight2.value(); |
836 | 0 | rLightRig.aLight2 = aTemp; |
837 | | // and make it brighter, 1.0 instead of 0.5 |
838 | 0 | rLightRig.aLight2.value().fMSOColorR = 1.0; |
839 | 0 | rLightRig.aLight2.value().fMSOColorG = 1.0; |
840 | 0 | rLightRig.aLight2.value().fMSOColorB = 1.0; |
841 | 0 | } |
842 | | // The object is far too bright. |
843 | 0 | rLightRig.fAmbient = 0.6; // instead 1.5 |
844 | 0 | } |
845 | 3 | else if (rLightRig.sLightRigName == u"chilly" || rLightRig.sLightRigName == u"flood") |
846 | 0 | { |
847 | | // They are too dark. |
848 | 0 | rLightRig.fAmbient = 0.35; // instead 0.11 resp. 0.13 |
849 | 0 | } |
850 | 3 | else if (rLightRig.sLightRigName == u"freezing" || rLightRig.sLightRigName == u"morning" |
851 | 3 | || rLightRig.sLightRigName == u"sunrise") |
852 | 0 | { |
853 | | // These rigs have no ambient color but four lights. The objects are too dark with only |
854 | | // two lights. |
855 | 0 | rLightRig.fAmbient = 0.4; |
856 | 0 | } |
857 | 3 | else if (rLightRig.sLightRigName == u"sunset") |
858 | 0 | { |
859 | | // The fourth light is more significant. |
860 | 0 | if (rLightDirVec.size() >= 4 && rLightRig.aLight4.has_value()) |
861 | 0 | { |
862 | 0 | MSOLight aTemp = rLightRig.aLight2.value(); |
863 | 0 | rLightRig.aLight2 = rLightRig.aLight4.value(); |
864 | 0 | rLightRig.aLight4 = aTemp; |
865 | 0 | std::swap(rLightDirVec[1], rLightDirVec[3]); |
866 | 0 | } |
867 | 0 | } |
868 | 3 | else if (rLightRig.sLightRigName == u"soft") |
869 | 0 | { |
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 | 0 | rLightRig.aLight1.bSpecular = false; |
873 | 0 | } |
874 | 3 | } |
875 | | |
876 | | } // end anonymous namespace |
877 | | |
878 | | void Scene3DHelper::setLightingProperties(const oox::drawingml::Shape3DPropertiesPtr p3DProperties, |
879 | | const double& rfRotZ, oox::PropertyMap& rPropertyMap) |
880 | 3 | { |
881 | 3 | if (!p3DProperties || !(*p3DProperties).mnLightRigType.has_value()) |
882 | 0 | return; |
883 | | |
884 | | // get index of light rig in aPrstLightRigValuesArray |
885 | 3 | const sal_Int32 nLightRigPrstID((*p3DProperties).mnLightRigType.value()); // token |
886 | 3 | sal_Int16 nPrstLightRigIndex = lcl_getPrstLightRigIndex( |
887 | 3 | oox::drawingml::Generic3DProperties::getLightRigName(nLightRigPrstID)); |
888 | 3 | if (nPrstLightRigIndex < 0 or nPrstLightRigIndex >= nLightRigPresetCount) |
889 | 0 | return; // error in document. OOXML specifies a fixed set of preset light rig types. |
890 | | |
891 | | // The light rig is copied because it might be tweaked later. |
892 | 3 | PrstLightRigValues aLightRig = aPrstLightRigValuesArray[nPrstLightRigIndex]; |
893 | | |
894 | 3 | std::vector<basegfx::B3DVector> aLightDirVec; |
895 | 3 | aLightDirVec.reserve(4); |
896 | 3 | lcl_getLightDirectionsFromRig(aLightRig, aLightDirVec); |
897 | 3 | lcl_AdaptAndNormalizeLightDirections(aLightDirVec); |
898 | | |
899 | 3 | lcl_IncorporateRigRotationIntoLightDirections(p3DProperties, aLightDirVec); |
900 | | |
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. |
904 | | |
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 | 3 | if (mnPrstCameraIndex < 20 || 37 < mnPrstCameraIndex) |
910 | 0 | { |
911 | 0 | double fX = 0.0; // unit rad, orientation as in API |
912 | 0 | double fY = 0.0; // unit rad, orientation as in API |
913 | 0 | lcl_getXYAnglesFromMap(rPropertyMap, fX, fY); |
914 | 0 | lcl_ApplyShapeRotationToLights(aLightDirVec, fX, fY, rfRotZ); |
915 | 0 | } |
916 | | |
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 | 3 | lcl_tweakLightRig(aLightDirVec, aLightRig); |
920 | | |
921 | 3 | rPropertyMap.setProperty(oox::PROP_Brightness, aLightRig.fAmbient.value_or(0) * 100); |
922 | | |
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 | 3 | rPropertyMap.setProperty(oox::PROP_FirstLightLevel, aLightRig.aLight1.fMSOColorR * 100); |
926 | | |
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 | 3 | rPropertyMap.setProperty(oox::PROP_FirstLightHarsh, aLightRig.aLight1.bSpecular); |
930 | | |
931 | | // (5) In fact we have stored position in FirstLightDirection and SecondLightDirection, |
932 | | // not direction, thus the minus sign. |
933 | 3 | css::drawing::Direction3D aLightPos; |
934 | 3 | aLightPos.DirectionX = -aLightDirVec[0].getX(); |
935 | 3 | aLightPos.DirectionY = -aLightDirVec[0].getY(); |
936 | 3 | aLightPos.DirectionZ = -aLightDirVec[0].getZ(); |
937 | 3 | rPropertyMap.setProperty(oox::PROP_FirstLightDirection, aLightPos); |
938 | | |
939 | | // (6) For extruded custom shapes only two lights are possible although our rendering engine has |
940 | | // eight lights. We will loose lights. |
941 | 3 | if (aLightDirVec.size() > 1) |
942 | 3 | { |
943 | 3 | rPropertyMap.setProperty(oox::PROP_SecondLightLevel, |
944 | 3 | aLightRig.aLight2.value().fMSOColorR * 100); |
945 | 3 | rPropertyMap.setProperty(oox::PROP_SecondLightHarsh, aLightRig.aLight2.value().bSpecular); |
946 | 3 | aLightPos.DirectionX = -aLightDirVec[1].getX(); |
947 | 3 | aLightPos.DirectionY = -aLightDirVec[1].getY(); |
948 | 3 | aLightPos.DirectionZ = -aLightDirVec[1].getZ(); |
949 | 3 | rPropertyMap.setProperty(oox::PROP_SecondLightDirection, aLightPos); |
950 | 3 | } |
951 | 0 | else |
952 | 0 | rPropertyMap.setProperty(oox::PROP_SecondLightLevel, 0.0); // prevent defaults. |
953 | 3 | } |
954 | | |
955 | | namespace |
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.*/ |
958 | | { |
959 | | struct MaterialValues |
960 | | { |
961 | | std::u16string_view msMaterialPrstName; // identifies the material type |
962 | | // Corresponds to MS Office 'Diffuse Color' and 'Ambient Color'. |
963 | | double fDiffusion; |
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. |
973 | | }; |
974 | | } // end anonymous namespace |
975 | | |
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, {} } }; |
994 | | |
995 | | void Scene3DHelper::setMaterialProperties(const oox::drawingml::Shape3DPropertiesPtr p3DProperties, |
996 | | oox::PropertyMap& rPropertyMap) |
997 | 3 | { |
998 | 3 | if (!p3DProperties) |
999 | 0 | return; |
1000 | | |
1001 | | // PowerPoint does not write aus prstMaterial="warmMatte", but handles it as default. |
1002 | 3 | const sal_Int32 nMaterialID = (*p3DProperties).mnMaterial.value_or(XML_warmMatte); // token |
1003 | | |
1004 | | // special handling for 'legacyWireframe' |
1005 | 3 | if (nMaterialID == XML_legacyWireframe) |
1006 | 0 | { |
1007 | | // This is handled via shade mode of the scene. |
1008 | 0 | 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 |
1011 | | // Black. |
1012 | 0 | return; |
1013 | 0 | } |
1014 | | |
1015 | 3 | sal_Int16 nIdx(0); // Index into aPrstMaterialArray |
1016 | 12 | while (nIdx < nPrstMaterialCount |
1017 | 12 | && aPrstMaterialArray[nIdx].msMaterialPrstName |
1018 | 12 | != oox::drawingml::Generic3DProperties::getPresetMaterialTypeString(nMaterialID)) |
1019 | 9 | ++nIdx; |
1020 | 3 | if (nIdx >= nPrstMaterialCount) |
1021 | 0 | return; // error in document |
1022 | | |
1023 | | // extrusion-diffuse, extrusion-specularity-loext |
1024 | 3 | rPropertyMap.setProperty(oox::PROP_Diffusion, aPrstMaterialArray[nIdx].fDiffusion); |
1025 | 3 | rPropertyMap.setProperty(oox::PROP_Specularity, aPrstMaterialArray[nIdx].fSpecularity); |
1026 | | |
1027 | | // extrusion-shininess |
1028 | 3 | double fShininess = 0.0; |
1029 | | // Conversion 2^(fShininess/10) = nSpecularPower |
1030 | 3 | if (aPrstMaterialArray[nIdx].nSpecularPower > 0) |
1031 | 0 | fShininess = 10.0 * std::log2(aPrstMaterialArray[nIdx].nSpecularPower); |
1032 | 3 | rPropertyMap.setProperty(oox::PROP_Shininess, fShininess); |
1033 | | |
1034 | | // extrusion-metal, extrusion-metal-type |
1035 | 3 | rPropertyMap.setProperty(oox::PROP_Metal, aPrstMaterialArray[nIdx].bMetal); |
1036 | 3 | if (aPrstMaterialArray[nIdx].bMetal) |
1037 | 0 | { |
1038 | 0 | sal_Int16 eMetalType = aPrstMaterialArray[nIdx].oMetalType.value_or(0) == 1 |
1039 | 0 | ? css::drawing::EnhancedCustomShapeMetalType::MetalMSCompatible |
1040 | 0 | : css::drawing::EnhancedCustomShapeMetalType::MetalODF; |
1041 | 0 | rPropertyMap.setProperty(oox::PROP_MetalType, eMetalType); |
1042 | 0 | } |
1043 | 3 | } |
1044 | | |
1045 | | } // end namespace oox |
1046 | | |
1047 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |