rev |
line source |
nuclear@54
|
1 #include <string.h>
|
nuclear@54
|
2 #include <assert.h>
|
nuclear@55
|
3 #include <limits.h>
|
nuclear@54
|
4 #include "rt.h"
|
nuclear@54
|
5 #include "ogl.h"
|
nuclear@54
|
6 #include "vector.h"
|
nuclear@54
|
7 #include "timer.h"
|
nuclear@54
|
8
|
nuclear@54
|
9 struct SurfPoint {
|
nuclear@54
|
10 float t;
|
nuclear@54
|
11 Vector3 pos, norm;
|
nuclear@54
|
12 const Face *face;
|
nuclear@54
|
13 };
|
nuclear@54
|
14
|
nuclear@54
|
15 static void trace_ray(float *pixel, const Ray &ray, int iter, float energy = 1.0f);
|
nuclear@54
|
16 static void shade(float *pixel, const Ray &ray, const SurfPoint &sp, int iter, float energy = 1.0f);
|
nuclear@54
|
17 static bool find_intersection(const Ray &ray, const Scene *scn, const KDNode *kd, SurfPoint *spret);
|
nuclear@54
|
18 static bool ray_aabb_test(const Ray &ray, const AABBox &aabb);
|
nuclear@54
|
19 static bool ray_triangle_test(const Ray &ray, const Face *face, SurfPoint *sp);
|
nuclear@54
|
20 static Vector3 calc_bary(const Vector3 &pt, const Face *face, const Vector3 &norm);
|
nuclear@54
|
21 static void transform(float *res, const float *v, const float *xform);
|
nuclear@54
|
22 static void transform_ray(Ray *ray, const float *xform, const float *invtrans_xform);
|
nuclear@54
|
23
|
nuclear@54
|
24 static int xsz, ysz;
|
nuclear@54
|
25 static float *fb;
|
nuclear@54
|
26 static unsigned int tex;
|
nuclear@54
|
27 static Scene *scn;
|
nuclear@54
|
28 static const Ray *prim_rays;
|
nuclear@55
|
29 static int max_iter;
|
nuclear@54
|
30
|
nuclear@55
|
31 static RenderStats *rstat;
|
nuclear@55
|
32 static int cur_ray_aabb_tests, cur_ray_triangle_tests;
|
nuclear@54
|
33
|
nuclear@54
|
34 bool init_dbg_renderer(int width, int height, Scene *scene, unsigned int texid)
|
nuclear@54
|
35 {
|
nuclear@54
|
36 try {
|
nuclear@54
|
37 fb = new float[3 * width * height];
|
nuclear@54
|
38 }
|
nuclear@54
|
39 catch(...) {
|
nuclear@54
|
40 return false;
|
nuclear@54
|
41 }
|
nuclear@54
|
42
|
nuclear@54
|
43 xsz = width;
|
nuclear@54
|
44 ysz = height;
|
nuclear@54
|
45 tex = texid;
|
nuclear@54
|
46 scn = scene;
|
nuclear@55
|
47
|
nuclear@55
|
48 rstat = (RenderStats*)get_render_stats();
|
nuclear@55
|
49
|
nuclear@54
|
50 return true;
|
nuclear@54
|
51 }
|
nuclear@54
|
52
|
nuclear@54
|
53 void destroy_dbg_renderer()
|
nuclear@54
|
54 {
|
nuclear@54
|
55 delete [] fb;
|
nuclear@54
|
56 delete [] prim_rays;
|
nuclear@54
|
57 }
|
nuclear@54
|
58
|
nuclear@54
|
59 void dbg_set_primary_rays(const Ray *rays)
|
nuclear@54
|
60 {
|
nuclear@54
|
61 prim_rays = rays;
|
nuclear@54
|
62 }
|
nuclear@54
|
63
|
nuclear@54
|
64 void dbg_render(const float *xform, const float *invtrans_xform, int num_threads)
|
nuclear@54
|
65 {
|
nuclear@54
|
66 unsigned long t0 = get_msec();
|
nuclear@54
|
67
|
nuclear@55
|
68 max_iter = get_render_option_int(ROPT_ITER);
|
nuclear@55
|
69
|
nuclear@55
|
70 // initialize render-stats
|
nuclear@55
|
71 memset(rstat, 0, sizeof *rstat);
|
nuclear@55
|
72 rstat->min_aabb_tests = rstat->min_triangle_tests = INT_MAX;
|
nuclear@55
|
73 rstat->max_aabb_tests = rstat->max_triangle_tests = 0;
|
nuclear@54
|
74
|
nuclear@54
|
75 int offs = 0;
|
nuclear@54
|
76 for(int i=0; i<ysz; i++) {
|
nuclear@54
|
77 for(int j=0; j<xsz; j++) {
|
nuclear@54
|
78 Ray ray = prim_rays[offs];
|
nuclear@54
|
79 transform_ray(&ray, xform, invtrans_xform);
|
nuclear@54
|
80
|
nuclear@55
|
81 cur_ray_aabb_tests = cur_ray_triangle_tests = 0;
|
nuclear@55
|
82
|
nuclear@55
|
83 trace_ray(fb + offs * 3, ray, max_iter, 1.0);
|
nuclear@54
|
84 offs++;
|
nuclear@55
|
85
|
nuclear@55
|
86 // update stats as needed
|
nuclear@55
|
87 if(cur_ray_aabb_tests < rstat->min_aabb_tests) {
|
nuclear@55
|
88 rstat->min_aabb_tests = cur_ray_aabb_tests;
|
nuclear@55
|
89 }
|
nuclear@55
|
90 if(cur_ray_aabb_tests > rstat->max_aabb_tests) {
|
nuclear@55
|
91 rstat->max_aabb_tests = cur_ray_aabb_tests;
|
nuclear@55
|
92 }
|
nuclear@55
|
93 if(cur_ray_triangle_tests < rstat->min_triangle_tests) {
|
nuclear@55
|
94 rstat->min_triangle_tests = cur_ray_triangle_tests;
|
nuclear@55
|
95 }
|
nuclear@55
|
96 if(cur_ray_triangle_tests > rstat->max_triangle_tests) {
|
nuclear@55
|
97 rstat->max_triangle_tests = cur_ray_triangle_tests;
|
nuclear@55
|
98 }
|
nuclear@55
|
99 rstat->prim_rays++;
|
nuclear@55
|
100 rstat->aabb_tests += cur_ray_aabb_tests;
|
nuclear@55
|
101 rstat->triangle_tests += cur_ray_triangle_tests;
|
nuclear@54
|
102 }
|
nuclear@54
|
103 }
|
nuclear@54
|
104
|
nuclear@55
|
105 unsigned long t1 = get_msec();
|
nuclear@55
|
106
|
nuclear@54
|
107 glPushAttrib(GL_TEXTURE_BIT);
|
nuclear@54
|
108 glBindTexture(GL_TEXTURE_2D, tex);
|
nuclear@54
|
109 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, xsz, ysz, GL_RGB, GL_FLOAT, fb);
|
nuclear@54
|
110 glPopAttrib();
|
nuclear@55
|
111 glFinish();
|
nuclear@54
|
112
|
nuclear@55
|
113 rstat->render_time = t1 - t0;
|
nuclear@55
|
114 rstat->tex_update_time = get_msec() - t1;
|
nuclear@55
|
115
|
nuclear@55
|
116 rstat->rays_cast = rstat->prim_rays + rstat->refl_rays + rstat->shadow_rays;
|
nuclear@55
|
117 rstat->rays_per_sec = 1000 * rstat->rays_cast / rstat->render_time;
|
nuclear@55
|
118 rstat->avg_aabb_tests = (float)rstat->aabb_tests / (float)rstat->rays_cast;
|
nuclear@55
|
119 rstat->avg_triangle_tests = (float)rstat->triangle_tests / (float)rstat->rays_cast;
|
nuclear@54
|
120 }
|
nuclear@54
|
121
|
nuclear@54
|
122 static void trace_ray(float *pixel, const Ray &ray, int iter, float energy)
|
nuclear@54
|
123 {
|
nuclear@54
|
124 SurfPoint sp;
|
nuclear@54
|
125
|
nuclear@54
|
126 if(find_intersection(ray, scn, scn->kdtree, &sp)) {
|
nuclear@54
|
127 shade(pixel, ray, sp, iter, energy);
|
nuclear@54
|
128 } else {
|
nuclear@54
|
129 pixel[0] = pixel[1] = pixel[2] = 0.05f;
|
nuclear@54
|
130 }
|
nuclear@54
|
131 }
|
nuclear@54
|
132
|
nuclear@54
|
133 #define MAX(a, b) ((a) > (b) ? (a) : (b))
|
nuclear@54
|
134
|
nuclear@54
|
135 static void shade(float *pixel, const Ray &ray, const SurfPoint &sp, int iter, float energy)
|
nuclear@54
|
136 {
|
nuclear@54
|
137 const Material *mat = scn->get_materials() + sp.face->matid;
|
nuclear@54
|
138
|
nuclear@54
|
139 bool cast_shadows = get_render_option_bool(ROPT_SHAD);
|
nuclear@54
|
140 Vector3 raydir(ray.dir);
|
nuclear@54
|
141 Vector3 norm = sp.norm;
|
nuclear@54
|
142
|
nuclear@54
|
143 if(dot(raydir, norm) >= 0.0) {
|
nuclear@54
|
144 norm = -norm;
|
nuclear@54
|
145 }
|
nuclear@54
|
146
|
nuclear@54
|
147 float dcol[3] = {0, 0, 0};
|
nuclear@54
|
148 float scol[3] = {0, 0, 0};
|
nuclear@54
|
149
|
nuclear@54
|
150 for(int i=0; i<scn->get_num_lights(); i++) {
|
nuclear@54
|
151 Vector3 lpos(scn->lights[i].pos);
|
nuclear@54
|
152 Vector3 ldir = lpos - sp.pos;
|
nuclear@54
|
153
|
nuclear@54
|
154 Ray shadowray;
|
nuclear@54
|
155 shadowray.origin[0] = sp.pos.x;
|
nuclear@54
|
156 shadowray.origin[1] = sp.pos.y;
|
nuclear@54
|
157 shadowray.origin[2] = sp.pos.z;
|
nuclear@54
|
158 shadowray.dir[0] = ldir.x;
|
nuclear@54
|
159 shadowray.dir[1] = ldir.y;
|
nuclear@54
|
160 shadowray.dir[2] = ldir.z;
|
nuclear@54
|
161
|
nuclear@54
|
162 if(!cast_shadows || !find_intersection(shadowray, scn, scn->kdtree, 0)) {
|
nuclear@55
|
163 rstat->brdf_evals++;
|
nuclear@54
|
164
|
nuclear@54
|
165 ldir.normalize();
|
nuclear@54
|
166
|
nuclear@54
|
167 Vector3 vdir = -raydir / RAY_MAG;
|
nuclear@54
|
168 Vector3 vref = reflect(vdir, norm);
|
nuclear@54
|
169
|
nuclear@54
|
170 float ndotl = dot(ldir, norm);
|
nuclear@54
|
171 float diff = MAX(ndotl, 0.0f);
|
nuclear@54
|
172
|
nuclear@54
|
173 dcol[0] += mat->kd[0] * diff;
|
nuclear@54
|
174 dcol[1] += mat->kd[1] * diff;
|
nuclear@54
|
175 dcol[2] += mat->kd[2] * diff;
|
nuclear@54
|
176
|
nuclear@54
|
177 float ldotvr = dot(ldir, vref);
|
nuclear@54
|
178 float spec = pow(MAX(ldotvr, 0.0f), mat->spow);
|
nuclear@54
|
179
|
nuclear@54
|
180 scol[0] += mat->ks[0] * spec;
|
nuclear@54
|
181 scol[1] += mat->ks[1] * spec;
|
nuclear@54
|
182 scol[2] += mat->ks[2] * spec;
|
nuclear@54
|
183 }
|
nuclear@55
|
184
|
nuclear@55
|
185 if(cast_shadows) {
|
nuclear@55
|
186 rstat->shadow_rays++;
|
nuclear@55
|
187 }
|
nuclear@54
|
188 }
|
nuclear@54
|
189
|
nuclear@54
|
190 float refl_color[3];
|
nuclear@54
|
191 refl_color[0] = mat->ks[0] * mat->kr;
|
nuclear@54
|
192 refl_color[1] = mat->ks[1] * mat->kr;
|
nuclear@54
|
193 refl_color[2] = mat->ks[2] * mat->kr;
|
nuclear@54
|
194
|
nuclear@54
|
195 energy *= (refl_color[0] + refl_color[1] + refl_color[2]) / 3.0;
|
nuclear@54
|
196 if(iter >= 0 && energy > MIN_ENERGY) {
|
nuclear@54
|
197 Vector3 rdir = reflect(-raydir, norm);
|
nuclear@54
|
198
|
nuclear@54
|
199 Ray refl;
|
nuclear@54
|
200 refl.origin[0] = sp.pos.x;
|
nuclear@54
|
201 refl.origin[1] = sp.pos.y;
|
nuclear@54
|
202 refl.origin[2] = sp.pos.z;
|
nuclear@54
|
203 refl.dir[0] = rdir.x;
|
nuclear@54
|
204 refl.dir[1] = rdir.y;
|
nuclear@54
|
205 refl.dir[2] = rdir.z;
|
nuclear@54
|
206
|
nuclear@54
|
207 float rcol[3];
|
nuclear@54
|
208 trace_ray(rcol, refl, iter - 1, energy);
|
nuclear@54
|
209 scol[0] += rcol[0] * mat->ks[0] * mat->kr;
|
nuclear@54
|
210 scol[1] += rcol[1] * mat->ks[1] * mat->kr;
|
nuclear@54
|
211 scol[2] += rcol[2] * mat->ks[2] * mat->kr;
|
nuclear@55
|
212
|
nuclear@55
|
213 rstat->refl_rays++;
|
nuclear@54
|
214 }
|
nuclear@54
|
215
|
nuclear@54
|
216 pixel[0] = dcol[0] + scol[0];
|
nuclear@54
|
217 pixel[1] = dcol[1] + scol[1];
|
nuclear@54
|
218 pixel[2] = dcol[2] + scol[2];
|
nuclear@54
|
219 }
|
nuclear@54
|
220
|
nuclear@54
|
221 static bool find_intersection(const Ray &ray, const Scene *scn, const KDNode *kd, SurfPoint *spret)
|
nuclear@54
|
222 {
|
nuclear@54
|
223 if(!ray_aabb_test(ray, kd->aabb)) {
|
nuclear@54
|
224 return false;
|
nuclear@54
|
225 }
|
nuclear@54
|
226
|
nuclear@54
|
227 SurfPoint sp, sptmp;
|
nuclear@54
|
228 if(!spret) {
|
nuclear@54
|
229 spret = &sptmp;
|
nuclear@54
|
230 }
|
nuclear@54
|
231
|
nuclear@54
|
232 spret->t = RAY_MAG;
|
nuclear@54
|
233 spret->face = 0;
|
nuclear@54
|
234
|
nuclear@54
|
235 if(kd->left) {
|
nuclear@54
|
236 assert(kd->right);
|
nuclear@54
|
237
|
nuclear@54
|
238 bool found = find_intersection(ray, scn, kd->left, spret);
|
nuclear@54
|
239 if(find_intersection(ray, scn, kd->right, &sp)) {
|
nuclear@54
|
240 if(!found || sp.t < spret->t) {
|
nuclear@54
|
241 *spret = sp;
|
nuclear@54
|
242 }
|
nuclear@54
|
243 found = true;
|
nuclear@54
|
244 }
|
nuclear@54
|
245 return found;
|
nuclear@54
|
246 }
|
nuclear@54
|
247
|
nuclear@54
|
248 const Face *faces = scn->get_face_buffer();
|
nuclear@54
|
249
|
nuclear@54
|
250 for(size_t i=0; i<kd->face_idx.size(); i++) {
|
nuclear@54
|
251 if(ray_triangle_test(ray, faces + kd->face_idx[i], &sp) && sp.t < spret->t) {
|
nuclear@54
|
252 *spret = sp;
|
nuclear@54
|
253 }
|
nuclear@54
|
254 }
|
nuclear@54
|
255 return spret->face != 0;
|
nuclear@54
|
256 }
|
nuclear@54
|
257
|
nuclear@54
|
258 static bool ray_aabb_test(const Ray &ray, const AABBox &aabb)
|
nuclear@54
|
259 {
|
nuclear@55
|
260 cur_ray_aabb_tests++;
|
nuclear@55
|
261
|
nuclear@54
|
262 if(ray.origin[0] >= aabb.min[0] && ray.origin[1] >= aabb.min[1] && ray.origin[2] >= aabb.min[2] &&
|
nuclear@54
|
263 ray.origin[0] < aabb.max[0] && ray.origin[1] < aabb.max[1] && ray.origin[2] < aabb.max[2]) {
|
nuclear@54
|
264 return true;
|
nuclear@54
|
265 }
|
nuclear@54
|
266
|
nuclear@54
|
267 float bbox[][3] = {
|
nuclear@54
|
268 {aabb.min[0], aabb.min[1], aabb.min[2]},
|
nuclear@54
|
269 {aabb.max[0], aabb.max[1], aabb.max[2]}
|
nuclear@54
|
270 };
|
nuclear@54
|
271
|
nuclear@54
|
272 int xsign = (int)(ray.dir[0] < 0.0);
|
nuclear@54
|
273 float invdirx = 1.0 / ray.dir[0];
|
nuclear@54
|
274 float tmin = (bbox[xsign][0] - ray.origin[0]) * invdirx;
|
nuclear@54
|
275 float tmax = (bbox[1 - xsign][0] - ray.origin[0]) * invdirx;
|
nuclear@54
|
276
|
nuclear@54
|
277 int ysign = (int)(ray.dir[1] < 0.0);
|
nuclear@54
|
278 float invdiry = 1.0 / ray.dir[1];
|
nuclear@54
|
279 float tymin = (bbox[ysign][1] - ray.origin[1]) * invdiry;
|
nuclear@54
|
280 float tymax = (bbox[1 - ysign][1] - ray.origin[1]) * invdiry;
|
nuclear@54
|
281
|
nuclear@54
|
282 if(tmin > tymax || tymin > tmax) {
|
nuclear@54
|
283 return false;
|
nuclear@54
|
284 }
|
nuclear@54
|
285
|
nuclear@54
|
286 if(tymin > tmin) tmin = tymin;
|
nuclear@54
|
287 if(tymax < tmax) tmax = tymax;
|
nuclear@54
|
288
|
nuclear@54
|
289 int zsign = (int)(ray.dir[2] < 0.0);
|
nuclear@54
|
290 float invdirz = 1.0 / ray.dir[2];
|
nuclear@54
|
291 float tzmin = (bbox[zsign][2] - ray.origin[2]) * invdirz;
|
nuclear@54
|
292 float tzmax = (bbox[1 - zsign][2] - ray.origin[2]) * invdirz;
|
nuclear@54
|
293
|
nuclear@54
|
294 if(tmin > tzmax || tzmin > tmax) {
|
nuclear@54
|
295 return false;
|
nuclear@54
|
296 }
|
nuclear@54
|
297
|
nuclear@54
|
298 return tmin < 1.0 && tmax > 0.0;
|
nuclear@54
|
299
|
nuclear@54
|
300 }
|
nuclear@54
|
301
|
nuclear@54
|
302 static bool ray_triangle_test(const Ray &ray, const Face *face, SurfPoint *sp)
|
nuclear@54
|
303 {
|
nuclear@55
|
304 cur_ray_triangle_tests++;
|
nuclear@55
|
305
|
nuclear@54
|
306 Vector3 origin = ray.origin;
|
nuclear@54
|
307 Vector3 dir = ray.dir;
|
nuclear@54
|
308 Vector3 norm = face->normal;
|
nuclear@54
|
309
|
nuclear@54
|
310 float ndotdir = dot(dir, norm);
|
nuclear@54
|
311
|
nuclear@54
|
312 if(fabs(ndotdir) <= EPSILON) {
|
nuclear@54
|
313 return false;
|
nuclear@54
|
314 }
|
nuclear@54
|
315
|
nuclear@54
|
316 Vector3 pt = face->v[0].pos;
|
nuclear@54
|
317 Vector3 vec = pt - origin;
|
nuclear@54
|
318
|
nuclear@54
|
319 float ndotvec = dot(norm, vec);
|
nuclear@54
|
320 float t = ndotvec / ndotdir;
|
nuclear@54
|
321
|
nuclear@54
|
322 if(t < EPSILON || t > 1.0) {
|
nuclear@54
|
323 return false;
|
nuclear@54
|
324 }
|
nuclear@54
|
325 pt = origin + dir * t;
|
nuclear@54
|
326
|
nuclear@54
|
327
|
nuclear@54
|
328 Vector3 bc = calc_bary(pt, face, norm);
|
nuclear@54
|
329 float bc_sum = bc.x + bc.y + bc.z;
|
nuclear@54
|
330
|
nuclear@54
|
331 if(bc_sum < 1.0 - EPSILON || bc_sum > 1.0 + EPSILON) {
|
nuclear@54
|
332 return false;
|
nuclear@54
|
333 }
|
nuclear@54
|
334
|
nuclear@54
|
335 Vector3 n0(face->v[0].normal);
|
nuclear@54
|
336 Vector3 n1(face->v[1].normal);
|
nuclear@54
|
337 Vector3 n2(face->v[2].normal);
|
nuclear@54
|
338
|
nuclear@54
|
339 sp->t = t;
|
nuclear@54
|
340 sp->pos = pt;
|
nuclear@54
|
341 sp->norm = n0 * bc.x + n1 * bc.y + n2 * bc.z;
|
nuclear@54
|
342 sp->norm.normalize();
|
nuclear@54
|
343 sp->face = face;
|
nuclear@54
|
344 return true;
|
nuclear@54
|
345 }
|
nuclear@54
|
346
|
nuclear@54
|
347 static Vector3 calc_bary(const Vector3 &pt, const Face *face, const Vector3 &norm)
|
nuclear@54
|
348 {
|
nuclear@54
|
349 Vector3 bc(0.0f, 0.0f, 0.0f);
|
nuclear@54
|
350
|
nuclear@54
|
351 Vector3 v1 = Vector3(face->v[1].pos) - Vector3(face->v[0].pos);
|
nuclear@54
|
352 Vector3 v2 = Vector3(face->v[2].pos) - Vector3(face->v[0].pos);
|
nuclear@54
|
353 Vector3 xv1v2 = cross(v1, v2);
|
nuclear@54
|
354
|
nuclear@54
|
355 float area = fabs(dot(xv1v2, norm)) * 0.5;
|
nuclear@54
|
356 if(area < EPSILON) {
|
nuclear@54
|
357 return bc;
|
nuclear@54
|
358 }
|
nuclear@54
|
359
|
nuclear@54
|
360 Vector3 pv0 = face->v[0].pos - pt;
|
nuclear@54
|
361 Vector3 pv1 = face->v[1].pos - pt;
|
nuclear@54
|
362 Vector3 pv2 = face->v[2].pos - pt;
|
nuclear@54
|
363
|
nuclear@54
|
364 // calculate the area of each sub-triangle
|
nuclear@54
|
365 Vector3 x12 = cross(pv1, pv2);
|
nuclear@54
|
366 Vector3 x20 = cross(pv2, pv0);
|
nuclear@54
|
367 Vector3 x01 = cross(pv0, pv1);
|
nuclear@54
|
368
|
nuclear@54
|
369 float a0 = fabs(dot(x12, norm)) * 0.5;
|
nuclear@54
|
370 float a1 = fabs(dot(x20, norm)) * 0.5;
|
nuclear@54
|
371 float a2 = fabs(dot(x01, norm)) * 0.5;
|
nuclear@54
|
372
|
nuclear@54
|
373 bc.x = a0 / area;
|
nuclear@54
|
374 bc.y = a1 / area;
|
nuclear@54
|
375 bc.z = a2 / area;
|
nuclear@54
|
376 return bc;
|
nuclear@54
|
377
|
nuclear@54
|
378 }
|
nuclear@54
|
379
|
nuclear@54
|
380 static void transform(float *res, const float *v, const float *xform)
|
nuclear@54
|
381 {
|
nuclear@54
|
382 float tmp[3];
|
nuclear@54
|
383 tmp[0] = v[0] * xform[0] + v[1] * xform[4] + v[2] * xform[8] + xform[12];
|
nuclear@54
|
384 tmp[1] = v[0] * xform[1] + v[1] * xform[5] + v[2] * xform[9] + xform[13];
|
nuclear@54
|
385 tmp[2] = v[0] * xform[2] + v[1] * xform[6] + v[2] * xform[10] + xform[14];
|
nuclear@54
|
386 memcpy(res, tmp, sizeof tmp);
|
nuclear@54
|
387 }
|
nuclear@54
|
388
|
nuclear@54
|
389 static void transform_ray(Ray *ray, const float *xform, const float *invtrans_xform)
|
nuclear@54
|
390 {
|
nuclear@54
|
391 transform(ray->origin, ray->origin, xform);
|
nuclear@54
|
392 transform(ray->dir, ray->dir, invtrans_xform);
|
nuclear@54
|
393 }
|