1 /* This program is free software; you can redistribute it and/or
2 * modify it under the terms of the GNU General Public License
3 * as published by the Free Software Foundation; either version 2
4 * of the License, or (at your option) any later version.
5 *
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
10 *
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software Foundation,
13 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14 *
15 * Copyright 2014, Blender Foundation.
16 */
17
18 #include "MEM_guardedalloc.h"
19
20 #include "COM_SunBeamsOperation.h"
21
SunBeamsOperation()22 SunBeamsOperation::SunBeamsOperation() : NodeOperation()
23 {
24 this->addInputSocket(COM_DT_COLOR);
25 this->addOutputSocket(COM_DT_COLOR);
26 this->setResolutionInputSocketIndex(0);
27
28 this->setComplex(true);
29 }
30
initExecution()31 void SunBeamsOperation::initExecution()
32 {
33 /* convert to pixels */
34 this->m_source_px[0] = this->m_data.source[0] * this->getWidth();
35 this->m_source_px[1] = this->m_data.source[1] * this->getHeight();
36 this->m_ray_length_px = this->m_data.ray_length * max(this->getWidth(), this->getHeight());
37 }
38
39 /**
40 * Defines a line accumulator for a specific sector,
41 * given by the four matrix entries that rotate from buffer space into the sector
42 *
43 * (x,y) is used to designate buffer space coordinates
44 * (u,v) is used to designate sector space coordinates
45 *
46 * For a target point (x,y) the sector should be chosen such that
47 * ``u >= v >= 0``
48 * This removes the need to handle all sorts of special cases.
49 *
50 * Template parameters:
51 * fxu : buffer increment in x for sector u+1
52 * fxv : buffer increment in x for sector v+1
53 * fyu : buffer increment in y for sector u+1
54 * fyv : buffer increment in y for sector v+1
55 */
56 template<int fxu, int fxv, int fyu, int fyv> struct BufferLineAccumulator {
57
58 /* utility functions implementing the matrix transform to/from sector space */
59
buffer_to_sectorBufferLineAccumulator60 static inline void buffer_to_sector(const float source[2], int x, int y, int &u, int &v)
61 {
62 int x0 = (int)source[0];
63 int y0 = (int)source[1];
64 x -= x0;
65 y -= y0;
66 u = x * fxu + y * fyu;
67 v = x * fxv + y * fyv;
68 }
69
buffer_to_sectorBufferLineAccumulator70 static inline void buffer_to_sector(const float source[2], float x, float y, float &u, float &v)
71 {
72 int x0 = (int)source[0];
73 int y0 = (int)source[1];
74 x -= (float)x0;
75 y -= (float)y0;
76 u = x * fxu + y * fyu;
77 v = x * fxv + y * fyv;
78 }
79
sector_to_bufferBufferLineAccumulator80 static inline void sector_to_buffer(const float source[2], int u, int v, int &x, int &y)
81 {
82 int x0 = (int)source[0];
83 int y0 = (int)source[1];
84 x = x0 + u * fxu + v * fxv;
85 y = y0 + u * fyu + v * fyv;
86 }
87
sector_to_bufferBufferLineAccumulator88 static inline void sector_to_buffer(const float source[2], float u, float v, float &x, float &y)
89 {
90 int x0 = (int)source[0];
91 int y0 = (int)source[1];
92 x = (float)x0 + u * fxu + v * fxv;
93 y = (float)y0 + u * fyu + v * fyv;
94 }
95
96 /**
97 * Set up the initial buffer pointer and calculate necessary variables for looping.
98 *
99 * Note that sector space is centered around the "source" point while the loop starts
100 * at dist_min from the target pt. This way the loop can be canceled as soon as it runs
101 * out of the buffer rect, because no pixels further along the line can contribute.
102 *
103 * \param x, y: Start location in the buffer
104 * \param num: Total steps in the loop
105 * \param v, dv: Vertical offset in sector space, for line offset perpendicular to the loop axis
106 */
init_buffer_iteratorBufferLineAccumulator107 static float *init_buffer_iterator(MemoryBuffer *input,
108 const float source[2],
109 const float co[2],
110 float dist_min,
111 float dist_max,
112 int &x,
113 int &y,
114 int &num,
115 float &v,
116 float &dv,
117 float &falloff_factor)
118 {
119 float pu, pv;
120 buffer_to_sector(source, co[0], co[1], pu, pv);
121
122 /* line angle */
123 float tan_phi = pv / pu;
124 float dr = sqrtf(tan_phi * tan_phi + 1.0f);
125 float cos_phi = 1.0f / dr;
126
127 /* clamp u range to avoid influence of pixels "behind" the source */
128 float umin = max_ff(pu - cos_phi * dist_min, 0.0f);
129 float umax = max_ff(pu - cos_phi * dist_max, 0.0f);
130 v = umin * tan_phi;
131 dv = tan_phi;
132
133 int start = (int)floorf(umax);
134 int end = (int)ceilf(umin);
135 num = end - start;
136
137 sector_to_buffer(source, end, (int)ceilf(v), x, y);
138
139 falloff_factor = dist_max > dist_min ? dr / (float)(dist_max - dist_min) : 0.0f;
140
141 float *iter = input->getBuffer() + COM_NUM_CHANNELS_COLOR * (x + input->getWidth() * y);
142 return iter;
143 }
144
145 /**
146 * Perform the actual accumulation along a ray segment from source to pt.
147 * Only pixels within dist_min..dist_max contribute.
148 *
149 * The loop runs backwards(!) over the primary sector space axis u, i.e. increasing distance to
150 * pt. After each step it decrements v by dv < 1, adding a buffer shift when necessary.
151 */
evalBufferLineAccumulator152 static void eval(MemoryBuffer *input,
153 float output[4],
154 const float co[2],
155 const float source[2],
156 float dist_min,
157 float dist_max)
158 {
159 rcti rect = *input->getRect();
160 int buffer_width = input->getWidth();
161 int x, y, num;
162 float v, dv;
163 float falloff_factor;
164 float border[4];
165
166 zero_v4(output);
167
168 if ((int)(co[0] - source[0]) == 0 && (int)(co[1] - source[1]) == 0) {
169 copy_v4_v4(output,
170 input->getBuffer() + COM_NUM_CHANNELS_COLOR *
171 ((int)source[0] + input->getWidth() * (int)source[1]));
172 return;
173 }
174
175 /* Initialize the iteration variables. */
176 float *buffer = init_buffer_iterator(
177 input, source, co, dist_min, dist_max, x, y, num, v, dv, falloff_factor);
178 zero_v3(border);
179 border[3] = 1.0f;
180
181 /* v_local keeps track of when to decrement v (see below) */
182 float v_local = v - floorf(v);
183
184 for (int i = 0; i < num; i++) {
185 float weight = 1.0f - (float)i * falloff_factor;
186 weight *= weight;
187
188 /* range check, use last valid color when running beyond the image border */
189 if (x >= rect.xmin && x < rect.xmax && y >= rect.ymin && y < rect.ymax) {
190 madd_v4_v4fl(output, buffer, buffer[3] * weight);
191 /* use as border color in case subsequent pixels are out of bounds */
192 copy_v4_v4(border, buffer);
193 }
194 else {
195 madd_v4_v4fl(output, border, border[3] * weight);
196 }
197
198 /* TODO implement proper filtering here, see
199 * https://en.wikipedia.org/wiki/Lanczos_resampling
200 * https://en.wikipedia.org/wiki/Sinc_function
201 *
202 * using lanczos with x = distance from the line segment,
203 * normalized to a == 0.5f, could give a good result
204 *
205 * for now just divide equally at the end ...
206 */
207
208 /* decrement u */
209 x -= fxu;
210 y -= fyu;
211 buffer -= (fxu + fyu * buffer_width) * COM_NUM_CHANNELS_COLOR;
212
213 /* decrement v (in steps of dv < 1) */
214 v_local -= dv;
215 if (v_local < 0.0f) {
216 v_local += 1.0f;
217
218 x -= fxv;
219 y -= fyv;
220 buffer -= (fxv + fyv * buffer_width) * COM_NUM_CHANNELS_COLOR;
221 }
222 }
223
224 /* normalize */
225 if (num > 0) {
226 mul_v4_fl(output, 1.0f / (float)num);
227 }
228 }
229 };
230
231 /**
232 * Dispatch function which selects an appropriate accumulator based on the sector of the target
233 * point, relative to the source.
234 *
235 * The BufferLineAccumulator defines the actual loop over the buffer, with an efficient inner loop
236 * due to using compile time constants instead of a local matrix variable defining the sector
237 * space.
238 */
accumulate_line(MemoryBuffer * input,float output[4],const float co[2],const float source[2],float dist_min,float dist_max)239 static void accumulate_line(MemoryBuffer *input,
240 float output[4],
241 const float co[2],
242 const float source[2],
243 float dist_min,
244 float dist_max)
245 {
246 /* coordinates relative to source */
247 float pt_ofs[2] = {co[0] - source[0], co[1] - source[1]};
248
249 /* The source sectors are defined like so:
250 *
251 * \ 3 | 2 /
252 * \ | /
253 * 4 \ | / 1
254 * \|/
255 * -----------
256 * /|\
257 * 5 / | \ 8
258 * / | \
259 * / 6 | 7 \
260 *
261 * The template arguments encode the transformation into "sector space",
262 * by means of rotation/mirroring matrix elements.
263 */
264
265 if (fabsf(pt_ofs[1]) > fabsf(pt_ofs[0])) {
266 if (pt_ofs[0] > 0.0f) {
267 if (pt_ofs[1] > 0.0f) {
268 /* 2 */
269 BufferLineAccumulator<0, 1, 1, 0>::eval(input, output, co, source, dist_min, dist_max);
270 }
271 else {
272 /* 7 */
273 BufferLineAccumulator<0, 1, -1, 0>::eval(input, output, co, source, dist_min, dist_max);
274 }
275 }
276 else {
277 if (pt_ofs[1] > 0.0f) {
278 /* 3 */
279 BufferLineAccumulator<0, -1, 1, 0>::eval(input, output, co, source, dist_min, dist_max);
280 }
281 else {
282 /* 6 */
283 BufferLineAccumulator<0, -1, -1, 0>::eval(input, output, co, source, dist_min, dist_max);
284 }
285 }
286 }
287 else {
288 if (pt_ofs[0] > 0.0f) {
289 if (pt_ofs[1] > 0.0f) {
290 /* 1 */
291 BufferLineAccumulator<1, 0, 0, 1>::eval(input, output, co, source, dist_min, dist_max);
292 }
293 else {
294 /* 8 */
295 BufferLineAccumulator<1, 0, 0, -1>::eval(input, output, co, source, dist_min, dist_max);
296 }
297 }
298 else {
299 if (pt_ofs[1] > 0.0f) {
300 /* 4 */
301 BufferLineAccumulator<-1, 0, 0, 1>::eval(input, output, co, source, dist_min, dist_max);
302 }
303 else {
304 /* 5 */
305 BufferLineAccumulator<-1, 0, 0, -1>::eval(input, output, co, source, dist_min, dist_max);
306 }
307 }
308 }
309 }
310
initializeTileData(rcti *)311 void *SunBeamsOperation::initializeTileData(rcti * /*rect*/)
312 {
313 void *buffer = getInputOperation(0)->initializeTileData(NULL);
314 return buffer;
315 }
316
executePixel(float output[4],int x,int y,void * data)317 void SunBeamsOperation::executePixel(float output[4], int x, int y, void *data)
318 {
319 const float co[2] = {(float)x, (float)y};
320
321 accumulate_line(
322 (MemoryBuffer *)data, output, co, this->m_source_px, 0.0f, this->m_ray_length_px);
323 }
324
calc_ray_shift(rcti * rect,float x,float y,const float source[2],float ray_length)325 static void calc_ray_shift(rcti *rect, float x, float y, const float source[2], float ray_length)
326 {
327 float co[2] = {(float)x, (float)y};
328 float dir[2], dist;
329
330 /* move (x,y) vector toward the source by ray_length distance */
331 sub_v2_v2v2(dir, co, source);
332 dist = normalize_v2(dir);
333 mul_v2_fl(dir, min_ff(dist, ray_length));
334 sub_v2_v2(co, dir);
335
336 int ico[2] = {(int)co[0], (int)co[1]};
337 BLI_rcti_do_minmax_v(rect, ico);
338 }
339
determineDependingAreaOfInterest(rcti * input,ReadBufferOperation * readOperation,rcti * output)340 bool SunBeamsOperation::determineDependingAreaOfInterest(rcti *input,
341 ReadBufferOperation *readOperation,
342 rcti *output)
343 {
344 /* Enlarges the rect by moving each corner toward the source.
345 * This is the maximum distance that pixels can influence each other
346 * and gives a rect that contains all possible accumulated pixels.
347 */
348 rcti rect = *input;
349 calc_ray_shift(&rect, input->xmin, input->ymin, this->m_source_px, this->m_ray_length_px);
350 calc_ray_shift(&rect, input->xmin, input->ymax, this->m_source_px, this->m_ray_length_px);
351 calc_ray_shift(&rect, input->xmax, input->ymin, this->m_source_px, this->m_ray_length_px);
352 calc_ray_shift(&rect, input->xmax, input->ymax, this->m_source_px, this->m_ray_length_px);
353
354 return NodeOperation::determineDependingAreaOfInterest(&rect, readOperation, output);
355 }
356