/src/libreoffice/basegfx/source/raster/rasterconvert3d.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
11 | | * Licensed to the Apache Software Foundation (ASF) under one or more |
12 | | * contributor license agreements. See the NOTICE file distributed |
13 | | * with this work for additional information regarding copyright |
14 | | * ownership. The ASF licenses this file to you under the Apache |
15 | | * License, Version 2.0 (the "License"); you may not use this file |
16 | | * except in compliance with the License. You may obtain a copy of |
17 | | * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
18 | | */ |
19 | | |
20 | | #include <basegfx/raster/rasterconvert3d.hxx> |
21 | | #include <basegfx/vector/b2dvector.hxx> |
22 | | #include <basegfx/polygon/b3dpolygon.hxx> |
23 | | #include <basegfx/polygon/b3dpolypolygon.hxx> |
24 | | #include <basegfx/point/b3dpoint.hxx> |
25 | | #include <osl/diagnose.h> |
26 | | |
27 | | // implementations of the 3D raster converter |
28 | | |
29 | | namespace basegfx |
30 | | { |
31 | | void RasterConverter3D::addArea(const B3DPolygon& rFill, const B3DHomMatrix* pViewToEye) |
32 | 0 | { |
33 | 0 | const sal_uInt32 nPointCount(rFill.count()); |
34 | |
|
35 | 0 | for(sal_uInt32 a(0); a < nPointCount; a++) |
36 | 0 | { |
37 | 0 | addEdge(rFill, a, (a + 1) % nPointCount, pViewToEye); |
38 | 0 | } |
39 | 0 | } |
40 | | |
41 | | void RasterConverter3D::addArea(const B3DPolyPolygon& rFill, const B3DHomMatrix* pViewToEye) |
42 | 0 | { |
43 | 0 | const sal_uInt32 nPolyCount(rFill.count()); |
44 | |
|
45 | 0 | for(sal_uInt32 a(0); a < nPolyCount; a++) |
46 | 0 | { |
47 | 0 | addArea(rFill.getB3DPolygon(a), pViewToEye); |
48 | 0 | } |
49 | 0 | } |
50 | | |
51 | | RasterConverter3D::RasterConverter3D() |
52 | 0 | {} |
53 | | |
54 | | RasterConverter3D::~RasterConverter3D() |
55 | 0 | {} |
56 | | |
57 | | void RasterConverter3D::rasterconvertB3DArea(sal_Int32 nStartLine, sal_Int32 nStopLine) |
58 | 0 | { |
59 | 0 | if(maLineEntries.empty()) |
60 | 0 | return; |
61 | | |
62 | 0 | OSL_ENSURE(nStopLine >= nStartLine, "nStopLine is bigger than nStartLine (!)"); |
63 | | |
64 | | // sort global entries by Y, X once. After this, the vector |
65 | | // is seen as frozen. Pointers to its entries will be used in the following code. |
66 | 0 | std::sort(maLineEntries.begin(), maLineEntries.end()); |
67 | | |
68 | | // local parameters |
69 | 0 | std::vector< RasterConversionLineEntry3D >::iterator aCurrentEntry(maLineEntries.begin()); |
70 | 0 | std::vector< RasterConversionLineEntry3D* > aCurrentLine; |
71 | 0 | std::vector< RasterConversionLineEntry3D* > aNextLine; |
72 | 0 | std::vector< RasterConversionLineEntry3D* >::iterator aRasterConversionLineEntry3D; |
73 | | |
74 | | // get scanlines first LineNumber as start |
75 | 0 | sal_Int32 nLineNumber(std::max(aCurrentEntry->getY(), nStartLine)); |
76 | |
|
77 | 0 | while((!aCurrentLine.empty() || aCurrentEntry != maLineEntries.end()) && (nLineNumber < nStopLine)) |
78 | 0 | { |
79 | | // add all entries which start at current line to current scanline |
80 | 0 | while(aCurrentEntry != maLineEntries.end()) |
81 | 0 | { |
82 | 0 | const sal_Int32 nCurrentLineNumber(aCurrentEntry->getY()); |
83 | |
|
84 | 0 | if(nCurrentLineNumber > nLineNumber) |
85 | 0 | { |
86 | | // line is below current one, done (since array is sorted) |
87 | 0 | break; |
88 | 0 | } |
89 | 0 | else |
90 | 0 | { |
91 | | // less or equal. Line is above or at current one. Advance it exactly to |
92 | | // current line |
93 | 0 | const sal_uInt32 nStep(nLineNumber - nCurrentLineNumber); |
94 | |
|
95 | 0 | if(!nStep || aCurrentEntry->decrementRasterConversionLineEntry3D(nStep)) |
96 | 0 | { |
97 | | // add when exactly on current line or when increment to it did not |
98 | | // completely consume it |
99 | 0 | if(nStep) |
100 | 0 | { |
101 | 0 | aCurrentEntry->incrementRasterConversionLineEntry3D(nStep, *this); |
102 | 0 | } |
103 | |
|
104 | 0 | aCurrentLine.push_back(&(*aCurrentEntry)); |
105 | 0 | } |
106 | 0 | } |
107 | | |
108 | 0 | ++aCurrentEntry; |
109 | 0 | } |
110 | | |
111 | | // sort current scanline using comparator. Only X is used there |
112 | | // since all entries are already in one processed line. This needs to be done |
113 | | // every time since not only new spans may have benn added or old removed, |
114 | | // but incrementing may also have changed the order |
115 | 0 | std::sort(aCurrentLine.begin(), aCurrentLine.end(), lineComparator()); |
116 | | |
117 | | // process current scanline |
118 | 0 | aRasterConversionLineEntry3D = aCurrentLine.begin(); |
119 | 0 | aNextLine.clear(); |
120 | 0 | sal_uInt32 nPairCount(0); |
121 | |
|
122 | 0 | while(aRasterConversionLineEntry3D != aCurrentLine.end()) |
123 | 0 | { |
124 | 0 | RasterConversionLineEntry3D& rPrevScanRasterConversionLineEntry3D(**aRasterConversionLineEntry3D++); |
125 | | |
126 | | // look for 2nd span |
127 | 0 | if(aRasterConversionLineEntry3D != aCurrentLine.end()) |
128 | 0 | { |
129 | | // work on span from rPrevScanRasterConversionLineEntry3D to aRasterConversionLineEntry3D, fLineNumber is valid |
130 | 0 | processLineSpan(rPrevScanRasterConversionLineEntry3D, **aRasterConversionLineEntry3D, nLineNumber, nPairCount++); |
131 | 0 | } |
132 | | |
133 | | // increment to next line |
134 | 0 | if(rPrevScanRasterConversionLineEntry3D.decrementRasterConversionLineEntry3D(1)) |
135 | 0 | { |
136 | 0 | rPrevScanRasterConversionLineEntry3D.incrementRasterConversionLineEntry3D(1, *this); |
137 | 0 | aNextLine.push_back(&rPrevScanRasterConversionLineEntry3D); |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | | // copy back next scanline if count has changed |
142 | 0 | if(aNextLine.size() != aCurrentLine.size()) |
143 | 0 | { |
144 | 0 | aCurrentLine = aNextLine; |
145 | 0 | } |
146 | | |
147 | | // increment fLineNumber |
148 | 0 | nLineNumber++; |
149 | 0 | } |
150 | 0 | } |
151 | | |
152 | | void RasterConverter3D::addEdge(const B3DPolygon& rFill, sal_uInt32 a, sal_uInt32 b, const B3DHomMatrix* pViewToEye) |
153 | 0 | { |
154 | 0 | B3DPoint aStart(rFill.getB3DPoint(a)); |
155 | 0 | B3DPoint aEnd(rFill.getB3DPoint(b)); |
156 | 0 | sal_Int32 nYStart(fround(aStart.getY())); |
157 | 0 | sal_Int32 nYEnd(fround(aEnd.getY())); |
158 | |
|
159 | 0 | if(nYStart == nYEnd) |
160 | 0 | return; |
161 | | |
162 | 0 | if(nYStart > nYEnd) |
163 | 0 | { |
164 | 0 | std::swap(aStart, aEnd); |
165 | 0 | std::swap(nYStart, nYEnd); |
166 | 0 | std::swap(a, b); |
167 | 0 | } |
168 | |
|
169 | 0 | const sal_uInt32 nYDelta(nYEnd - nYStart); |
170 | 0 | const double fInvYDelta(1.0 / nYDelta); |
171 | 0 | maLineEntries.emplace_back( |
172 | 0 | aStart.getX(), (aEnd.getX() - aStart.getX()) * fInvYDelta, |
173 | 0 | aStart.getZ(), (aEnd.getZ() - aStart.getZ()) * fInvYDelta, |
174 | 0 | nYStart, nYDelta); |
175 | | |
176 | | // if extra interpolation data is used, add it to the last created entry |
177 | 0 | RasterConversionLineEntry3D& rEntry = maLineEntries[maLineEntries.size() - 1]; |
178 | |
|
179 | 0 | if(rFill.areBColorsUsed()) |
180 | 0 | { |
181 | 0 | rEntry.setColorIndex(addColorInterpolator(rFill.getBColor(a), rFill.getBColor(b), fInvYDelta)); |
182 | 0 | } |
183 | |
|
184 | 0 | if(rFill.areNormalsUsed()) |
185 | 0 | { |
186 | 0 | rEntry.setNormalIndex(addNormalInterpolator(rFill.getNormal(a), rFill.getNormal(b), fInvYDelta)); |
187 | 0 | } |
188 | |
|
189 | 0 | if(!rFill.areTextureCoordinatesUsed()) |
190 | 0 | return; |
191 | | |
192 | 0 | if(pViewToEye) |
193 | 0 | { |
194 | 0 | const double fEyeA(((*pViewToEye) * aStart).getZ()); |
195 | 0 | const double fEyeB(((*pViewToEye) * aEnd).getZ()); |
196 | |
|
197 | 0 | rEntry.setInverseTextureIndex(addInverseTextureInterpolator( |
198 | 0 | rFill.getTextureCoordinate(a), |
199 | 0 | rFill.getTextureCoordinate(b), |
200 | 0 | fEyeA, fEyeB, fInvYDelta)); |
201 | 0 | } |
202 | 0 | else |
203 | 0 | { |
204 | 0 | rEntry.setTextureIndex(addTextureInterpolator( |
205 | 0 | rFill.getTextureCoordinate(a), |
206 | 0 | rFill.getTextureCoordinate(b), |
207 | 0 | fInvYDelta)); |
208 | 0 | } |
209 | 0 | } |
210 | | |
211 | | void RasterConverter3D::rasterconvertB3DEdge(const B3DPolygon& rLine, sal_uInt32 nA, sal_uInt32 nB, sal_Int32 nStartLine, sal_Int32 nStopLine, sal_uInt16 nLineWidth) |
212 | 0 | { |
213 | 0 | B3DPoint aStart(rLine.getB3DPoint(nA)); |
214 | 0 | B3DPoint aEnd(rLine.getB3DPoint(nB)); |
215 | 0 | const double fZBufferLineAdd(0x00ff); |
216 | |
|
217 | 0 | if(nLineWidth > 1) |
218 | 0 | { |
219 | | // this is not a hairline anymore, in most cases since it's an oversampled |
220 | | // hairline to get e.g. AA for Z-Buffering. Create fill geometry. |
221 | 0 | if(!aStart.equal(aEnd)) |
222 | 0 | { |
223 | 0 | reset(); |
224 | 0 | maLineEntries.clear(); |
225 | |
|
226 | 0 | B2DVector aVector(aEnd.getX() - aStart.getX(), aEnd.getY() - aStart.getY()); |
227 | 0 | aVector.normalize(); |
228 | 0 | const B2DVector aPerpend(getPerpendicular(aVector) * ((static_cast<double>(nLineWidth) + 0.5) * 0.5)); |
229 | 0 | const double fZStartWithAdd(aStart.getZ() + fZBufferLineAdd); |
230 | 0 | const double fZEndWithAdd(aEnd.getZ() + fZBufferLineAdd); |
231 | |
|
232 | 0 | B3DPolygon aPolygon; |
233 | 0 | aPolygon.append(B3DPoint(aStart.getX() + aPerpend.getX(), aStart.getY() + aPerpend.getY(), fZStartWithAdd)); |
234 | 0 | aPolygon.append(B3DPoint(aEnd.getX() + aPerpend.getX(), aEnd.getY() + aPerpend.getY(), fZEndWithAdd)); |
235 | 0 | aPolygon.append(B3DPoint(aEnd.getX() - aPerpend.getX(), aEnd.getY() - aPerpend.getY(), fZEndWithAdd)); |
236 | 0 | aPolygon.append(B3DPoint(aStart.getX() - aPerpend.getX(), aStart.getY() - aPerpend.getY(), fZStartWithAdd)); |
237 | 0 | aPolygon.setClosed(true); |
238 | |
|
239 | 0 | addArea(aPolygon, nullptr); |
240 | 0 | } |
241 | 0 | } |
242 | 0 | else |
243 | 0 | { |
244 | | // it's a hairline. Use direct RasterConversionLineEntry creation to |
245 | | // rasterconvert lines as similar to areas as possible to avoid Z-Fighting |
246 | 0 | sal_Int32 nYStart(fround(aStart.getY())); |
247 | 0 | sal_Int32 nYEnd(fround(aEnd.getY())); |
248 | |
|
249 | 0 | if(nYStart == nYEnd) |
250 | 0 | { |
251 | | // horizontal line, check X |
252 | 0 | const sal_Int32 nXStart(static_cast<sal_Int32>(aStart.getX())); |
253 | 0 | const sal_Int32 nXEnd(static_cast<sal_Int32>(aEnd.getX())); |
254 | |
|
255 | 0 | if(nXStart != nXEnd) |
256 | 0 | { |
257 | 0 | reset(); |
258 | 0 | maLineEntries.clear(); |
259 | | |
260 | | // horizontal line, create vertical entries. These will be sorted by |
261 | | // X anyways, so no need to distinguish the case here |
262 | 0 | maLineEntries.emplace_back( |
263 | 0 | aStart.getX(), 0.0, |
264 | 0 | aStart.getZ() + fZBufferLineAdd, 0.0, |
265 | 0 | nYStart, 1); |
266 | 0 | maLineEntries.emplace_back( |
267 | 0 | aEnd.getX(), 0.0, |
268 | 0 | aEnd.getZ() + fZBufferLineAdd, 0.0, |
269 | 0 | nYStart, 1); |
270 | 0 | } |
271 | 0 | } |
272 | 0 | else |
273 | 0 | { |
274 | 0 | reset(); |
275 | 0 | maLineEntries.clear(); |
276 | |
|
277 | 0 | if(nYStart > nYEnd) |
278 | 0 | { |
279 | 0 | std::swap(aStart, aEnd); |
280 | 0 | std::swap(nYStart, nYEnd); |
281 | 0 | } |
282 | |
|
283 | 0 | const sal_uInt32 nYDelta(static_cast<sal_uInt32>(nYEnd - nYStart)); |
284 | 0 | const double fInvYDelta(1.0 / nYDelta); |
285 | | |
286 | | // non-horizontal line, create two parallel entries. These will be sorted by |
287 | | // X anyways, so no need to distinguish the case here |
288 | 0 | maLineEntries.emplace_back( |
289 | 0 | aStart.getX(), (aEnd.getX() - aStart.getX()) * fInvYDelta, |
290 | 0 | aStart.getZ() + fZBufferLineAdd, (aEnd.getZ() - aStart.getZ()) * fInvYDelta, |
291 | 0 | nYStart, nYDelta); |
292 | |
|
293 | 0 | RasterConversionLineEntry3D& rEntry = maLineEntries[maLineEntries.size() - 1]; |
294 | | |
295 | | // need to choose a X-Distance for the 2nd edge which guarantees all pixels |
296 | | // of the line to be set. This is exactly the X-Increment for one Y-Step. |
297 | | // Same is true for Z, so in both cases, add one increment to them. To also |
298 | | // guarantee one pixel per line, add a minimum of one for X. |
299 | 0 | const double fDistanceX(fabs(rEntry.getX().getInc()) >= 1.0 ? rEntry.getX().getInc() : 1.0); |
300 | |
|
301 | 0 | maLineEntries.emplace_back( |
302 | 0 | rEntry.getX().getVal() + fDistanceX, rEntry.getX().getInc(), |
303 | 0 | rEntry.getZ().getVal() + rEntry.getZ().getInc(), rEntry.getZ().getInc(), |
304 | 0 | nYStart, nYDelta); |
305 | 0 | } |
306 | 0 | } |
307 | |
|
308 | 0 | if(!maLineEntries.empty()) |
309 | 0 | { |
310 | 0 | rasterconvertB3DArea(nStartLine, nStopLine); |
311 | 0 | } |
312 | 0 | } |
313 | | |
314 | | void RasterConverter3D::rasterconvertB3DPolyPolygon(const B3DPolyPolygon& rFill, const B3DHomMatrix* pViewToEye, sal_Int32 nStartLine, sal_Int32 nStopLine) |
315 | 0 | { |
316 | 0 | reset(); |
317 | 0 | maLineEntries.clear(); |
318 | 0 | addArea(rFill, pViewToEye); |
319 | 0 | rasterconvertB3DArea(nStartLine, nStopLine); |
320 | 0 | } |
321 | | |
322 | | void RasterConverter3D::rasterconvertB3DPolygon(const B3DPolygon& rLine, sal_Int32 nStartLine, sal_Int32 nStopLine, sal_uInt16 nLineWidth) |
323 | 0 | { |
324 | 0 | const sal_uInt32 nPointCount(rLine.count()); |
325 | |
|
326 | 0 | if(nPointCount) |
327 | 0 | { |
328 | 0 | const sal_uInt32 nEdgeCount(rLine.isClosed() ? nPointCount : nPointCount - 1); |
329 | |
|
330 | 0 | for(sal_uInt32 a(0); a < nEdgeCount; a++) |
331 | 0 | { |
332 | 0 | rasterconvertB3DEdge(rLine, a, (a + 1) % nPointCount, nStartLine, nStopLine, nLineWidth); |
333 | 0 | } |
334 | 0 | } |
335 | 0 | } |
336 | | } // end of namespace basegfx |
337 | | |
338 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |