1 /*
2
3 Copyright (C) 2015-2018 Night Dive Studios, LLC.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 */
19 //
20 // $Source: r:/prj/lib/src/3d/RCS/light.asm $
21 // $Revision: 1.2 $
22 // $Author: jaemz $
23 // $Date: 1994/10/13 20:51:49 $
24 //
25 // Light routines
26 //
27
28 #include "3d.h"
29 #include "GlobalV.h"
30 #include "lg.h"
31
32 // prototypes
33 void check_for_near(void);
34 void scale_light_vec(void);
35 void g3_light_obj(g3s_phandle norm, g3s_phandle pos);
36 fix light_diff_raw(g3s_phandle src, g3s_phandle dest);
37 fix light_spec_raw(g3s_phandle src, g3s_phandle dest);
38 fix light_dands_raw(g3s_phandle src, g3s_phandle dest);
39
40 // look ma, a zero vector
41 g3s_phandle tmp1;
42 g3s_phandle tmp2;
43 g3s_vector zero_vec = {0, 0, 0};
44
45 // sets a light vector in source space directly
46 // this light vector has to be in user space so we can dot it with
47 // other vector
48 // g3_set_light_src(g3s_vector *l)
49 // takes eax, trashes esi,edi
g3_set_light_src(g3s_vector * l)50 void g3_set_light_src(g3s_vector *l) { _g3d_light_src = *l; }
51
52 // This should be called after a frames' angles and stuff have been
53 // set, does not need to be done per object
54 // void g3_eval_vec_light(void)
55 // Means you should be not near
g3_eval_vec_light(void)56 void g3_eval_vec_light(void) {
57 // needs to be rotated through view matrix
58 g3_vec_rotate(&_g3d_light_vec, &_g3d_light_src, &_wtoo_matrix);
59 scale_light_vec();
60 }
61
62 // should be normalized already
63 // multiply by _g3d_diff_light
64 // to set light intensity
scale_light_vec(void)65 void scale_light_vec(void) {
66 _g3d_light_vec.gX = fix_mul(_g3d_light_vec.gX, _g3d_diff_light);
67 _g3d_light_vec.gY = fix_mul(_g3d_light_vec.gY, _g3d_diff_light);
68 _g3d_light_vec.gZ = fix_mul(_g3d_light_vec.gZ, _g3d_diff_light);
69 }
70
71 // transforms local light source into viewer coords in anticipation
72 // of calling g3_eval_loc_light. Saves a transformation don't you know
73 // ideally you'd want to transform the view vec and light vec into
74 // object coords. That way you wouldn't have to transform their normals
75 // at all. The new 3d should provide inverse transforms. That way things
76 // could be lit more cheaply
g3_trans_loc_light(void)77 void g3_trans_loc_light(void) {
78 g3s_phandle p;
79
80 p = g3_rotate_point(&_g3d_light_src);
81 _g3d_light_trans = *(g3s_vector *)p;
82 }
83
84 // evaluates light point relative to another point, src is in world coords
85 // and pos is already transformed into eye coords
86 // void g3_eval_loc_light(eax)//
g3_eval_loc_light(g3s_phandle pos)87 void g3_eval_loc_light(g3s_phandle pos) {
88 fix temp;
89
90 // transform light src point to eye coords
91
92 // take difference with pos, and unscale them
93 temp = -(pos->gX - _g3d_light_trans.gX);
94 _g3d_light_vec.gX = fix_div(temp, _matrix_scale.gX);
95
96 temp = -(pos->gY - _g3d_light_trans.gY);
97 _g3d_light_vec.gY = fix_div(temp, _matrix_scale.gY);
98
99 temp = -(pos->gZ - _g3d_light_trans.gZ);
100 _g3d_light_vec.gZ = fix_div(temp, _matrix_scale.gZ);
101
102 // normalize vector
103 g3_vec_normalize(&_g3d_light_vec);
104
105 // multiply by diff and divide by scale
106 // so dot product just works
107 scale_light_vec();
108 }
109
110 // evaluate the view vector relative to a point
111 // similar to above except src is always 0,0,0
112 // pos in eax has to be transformed into viewer coords
113 // eax points to the point in 3d space
g3_eval_view(g3s_phandle pos)114 void g3_eval_view(g3s_phandle pos) {
115 fix temp;
116
117 _g3d_view_vec.gX = pos->gX - _view_position.gX;
118 _g3d_view_vec.gY = pos->gY - _view_position.gY;
119 _g3d_view_vec.gZ = pos->gZ - _view_position.gZ;
120
121 // normalize
122 g3_vec_normalize(&_g3d_view_vec);
123
124 // multiply by spec and negate vector
125 // since it currently points at the
126 // point instead of the viewer
127 // you might think, why not do this
128 // afterwards, and you're right. But only
129 // only if this view vec only gets used once
130
131 temp = -_g3d_spec_light;
132 _g3d_view_vec.gX = fix_mul(_g3d_view_vec.gX, temp);
133 _g3d_view_vec.gY = fix_mul(_g3d_view_vec.gY, temp);
134 _g3d_view_vec.gZ = fix_mul(_g3d_view_vec.gZ, temp);
135 }
136
137 // takes the dot product of view and light, for specular light
138 // assumes both light_vec and view_vec have been evaluated already
139 // everything is normal and in object space
140 // void g3_eval_ldotv(void)
g3_eval_ldotv(void)141 void g3_eval_ldotv(void) {
142 // multiply and scale once to find true dotproduct.
143 // I hate this scaling stuff. Erg.
144 // transforming the light and view vectors into object
145 // space would avoid this entirely
146
147 _g3d_ldotv = g3_vec_dotprod(&_g3d_light_vec, &_g3d_view_vec);
148 }
149
150 // Evaluates and sets light vectors as necessary at the start of
151 // an object. Does view vec if SPEC is set. Transforms light
152 // vector or point depending how LOC_LIGHT is set. Use this if
153 // both light and view will be modelled as far. In fact, make
154 // sure both are set as far, or you will be sorry.
155 // Evaluates at the object center. If necessary, evaluates the
156 // ldotv for light and view
157 // void g3_eval_light_obj_cen(void)
158 // this could be optimized a bit more when both spec
159 // both spec and diff is true
160 // this should do eval vec as well, basically everything
g3_eval_light_obj_cen(void)161 void g3_eval_light_obj_cen(void) {
162 DEBUG("%s: Call Mark if you see this", __FUNCTION__);
163
164 // MLA - this routine is buggy as far as I can tell, it doesn't work at all
165 // edi is never set or just happens to be set right, or it always falls
166 // through the first test (non_local)
167
168 /* ; is local?
169 test _g3d_light_type,LT_LOC_LIGHT or LT_SPEC
170 jz non_local
171
172 ; find center of object, duh, center is at 0,0,0
173 ;lea esi,zero_vec
174 ;call g3_rotate_point; this would be point in viewer space
175 ; returns point in edi
176 ;mov tmp1,edi
177
178 ; evaluate local light
179 test _g3d_light_type,LT_LOC_LIGHT
180 jz no_loc
181 mov eax,edi
182 call g3_eval_loc_light
183 jmp test_spec
184 no_loc:
185 call g3_eval_vec_light
186
187 ; is spec set?
188 test_spec:
189 test _g3d_light_type,LT_SPEC
190 jz spec_done
191
192 ; evaluate view
193 ; view vector relative to zero is in _view_position
194 ; already
195 ;mov eax,tmp1
196 lea eax,zero_vec ;in reality should write a diff routine
197 call g3_eval_view
198
199 call g3_eval_ldotv
200
201 spec_done:
202 ;mov edi,tmp1
203 ;freepnt edi
204 ret
205
206 non_local:
207 ; assumes you've transformed
208 ; it already
209 jmp g3_eval_vec_light*/
210 }
211
212 // set your view to be straight ahead, use when using specular,
213 // and after the light has been evaluated. This is ultra cheap hack
214 // to get view vector for a whole scene, just points straight in
g3_eval_view_ahead(void)215 void g3_eval_view_ahead(void) {
216 // this is in view coords, need in
217 // object coords, this won't work
218 _g3d_view_vec.gX = 0;
219 _g3d_view_vec.gY = 0;
220 _g3d_view_vec.gZ = -0x01000;
221
222 // now eval ldotv, hm.
223 _g3d_ldotv = -_g3d_light_vec.gZ;
224 }
225
226 // check to see if local stuff has to get set and
227 // set it if necessary
228 // takes args in tmp1,tmp2
check_for_near(void)229 void check_for_near(void) {
230 if (!(_g3d_light_type & (LT_NEAR_VIEW | LT_NEAR_LIGHT)))
231 return;
232
233 // if light near, evaluate
234 if (_g3d_light_type & LT_NEAR_LIGHT)
235 g3_eval_loc_light(tmp2);
236
237 // if view near, eval
238 if (_g3d_light_type & LT_NEAR_VIEW)
239 g3_eval_view(tmp2);
240
241 // evaluate ldotv if either was local
242 // MLA - this is stupid, the code that tests for whether or not to call
243 // g3_eval_ldotv makes no sense, it does a JZ on an undetermined condition
244 // code setup. So I just call it all the time. Look in Light.ASM in the PC 3D
245 // code for the original stuff.
246 g3_eval_ldotv();
247 }
248
249 // void g3_light_diff(g3s_phandle norm,g3s_phandle pos)//
250 // takes normal vector transformed, dots with the light vec,
251 // puts light val in norm,
252 // takes args in [eax,edx]
g3_light_diff(g3s_phandle norm,g3s_phandle pos)253 void g3_light_diff(g3s_phandle norm, g3s_phandle pos) {
254 // push eax if not gouraud, or edx if, so we know
255 // whether to light the normal or the point
256 // maybe we could make this self modifying based
257 // on a light type setter, if this is slow
258
259 // MLA - whatever, I made it normal C code
260
261 tmp1 = norm;
262 tmp2 = pos;
263 check_for_near();
264
265 if ((_g3d_light_type & LT_GOUR) == 0)
266 light_diff_raw(tmp1, norm);
267 else
268 light_diff_raw(tmp1, pos);
269 }
270
271 // raw version
272 // dot product with normal
273 // esi and edi
274 // ret eax
light_diff_raw(g3s_phandle src,g3s_phandle dest)275 fix light_diff_raw(g3s_phandle src, g3s_phandle dest) {
276 fix temp;
277
278 temp = g3_vec_dotprod(&_g3d_light_vec, (g3s_vector *)src);
279
280 // set lighting value in norm
281 // test eax for negativity, zero if negative
282 if (temp < 0)
283 temp = 0;
284 temp += _g3d_amb_light; // add ambient light
285 temp >>= 4; // convert to sfix, consider row 16 normal
286 dest->i = temp;
287 return (temp);
288 }
289
290 // void g3_light_spec(g3s_phandle norm,g3s_phandle pos)//
291 // takes norm and point position, lights point
292 // could both be the same, of course [eax,edx]
g3_light_spec(g3s_phandle norm,g3s_phandle pos)293 void g3_light_spec(g3s_phandle norm, g3s_phandle pos) {
294 // push eax if not gouraud, or edx if, so we know
295 // whether to light the normal or the point
296 // maybe we could make this self modifying based
297 // on a light type setter, if this is slow
298
299 // MLA - whatever, I made it normal C code
300
301 // save norm and pos off so we don't push and
302 // pop them forever
303 tmp1 = norm;
304 tmp2 = pos;
305 check_for_near();
306
307 if ((_g3d_light_type & LT_GOUR) == 0)
308 light_spec_raw(tmp1, norm);
309 else
310 light_spec_raw(tmp1, pos);
311 }
312
313 // pure specular lighting is equal to
314 // 2(s.l)(s.v) - (l.v)
315 // take (s.l)
light_spec_raw(g3s_phandle src,g3s_phandle dest)316 fix light_spec_raw(g3s_phandle src, g3s_phandle dest) {
317 fix temp;
318
319 temp = g3_vec_dotprod(&_g3d_light_vec, (g3s_vector *)src);
320 if (temp < 0) {
321 dest->i = _g3d_amb_light >> 4;
322 return (dest->i);
323 }
324
325 _g3d_sdotl = temp;
326
327 // take (s.v), note that this is jnorm, if its been done
328 // we can eliminate this step intelligently somehow
329 _g3d_sdotv = temp = g3_vec_dotprod(&_g3d_view_vec, (g3s_vector *)tmp1);
330 temp <<= 1; // multiply (s.v) by 2
331 temp = fix_mul(temp, _g3d_sdotl); // mult by (s.l)
332 temp -= _g3d_ldotv; // subtract ldotv, done!
333
334 // test eax for flash point zero if under
335 // or better test eax for spec threshhold
336 if (temp < _g3d_flash)
337 temp = 0;
338
339 // add ambient light
340 temp += _g3d_amb_light;
341
342 // check to see if its greater than the max row
343 // and truncate if it is
344 if (temp >= (LT_TABSIZE << 12))
345 ;
346 temp = (LT_TABSIZE << 12) - 1; // if its over the max, set it to just under max
347
348 dest->i = temp >> 4; // convert to sfix, consider row 16 normal
349 return (temp);
350 }
351
352 // void g3_light_dands(g3s_phandle norm,g3s_phandle pos)//
353 // lights with both diff and spec
354 //[eax,edx]
g3_light_dands(g3s_phandle norm,g3s_phandle pos)355 void g3_light_dands(g3s_phandle norm, g3s_phandle pos) {
356 // MLA - same stuff as before, changed to C....
357 tmp1 = norm;
358 tmp2 = pos;
359 check_for_near();
360
361 if ((_g3d_light_type & LT_GOUR) == 0)
362 light_dands_raw(tmp1, norm);
363 else
364 light_dands_raw(tmp1, pos);
365 }
366
367 // raw version of dands without local checking
light_dands_raw(g3s_phandle src,g3s_phandle dest)368 fix light_dands_raw(g3s_phandle src, g3s_phandle dest) {
369 fix temp;
370
371 // pure specular lighting is equal to
372 // 2(s.l)(s.v) - (l.v)
373 // take (s.l) if neg, you know you're done, surface HAS to face the light
374
375 temp = g3_vec_dotprod(&_g3d_light_vec, (g3s_vector *)src);
376 if (temp < 0) {
377 dest->i = _g3d_amb_light >> 4;
378 return (dest->i);
379 }
380 _g3d_sdotl = temp;
381
382 // take (s.v), note that this is jnorm, if its been done
383 // we can eliminate this step intelligently somehow
384 _g3d_sdotv = temp = g3_vec_dotprod(&_g3d_view_vec, (g3s_vector *)tmp1);
385 temp = fix_mul(temp, _g3d_sdotl); // mult by (s.l)
386 temp <<= 1; // multiply (s.v)(s.l) by 2
387 temp -= _g3d_ldotv; // subtract ldotv, done!
388
389 // test eax for flash point zero if under
390 // or better test eax for spec threshhold
391 if (temp < _g3d_flash)
392 temp = 0;
393
394 // add diffuse component & ambient light
395 temp += _g3d_sdotl + _g3d_amb_light;
396
397 // check to see if its greater than the max row
398 // and truncate if it is
399 if (temp >= (LT_TABSIZE << 12))
400 temp = (LT_TABSIZE << 12) - 1; // if its over the max, set it to just under max
401
402 dest->i = temp >> 4; // convert to sfix, consider row 16 normal
403 return (temp);
404 }
405
406 // farms out a point based on flags
407 // void g3_light(g3s_phandle norm,g3s_phandle pos)//
408 //[eax,edx]
g3_light(g3s_phandle norm,g3s_phandle pos)409 fix g3_light(g3s_phandle norm, g3s_phandle pos) {
410 g3s_phandle temp;
411
412 if ((_g3d_light_type & LT_GOUR) == 0)
413 temp = norm;
414 else
415 temp = pos;
416
417 tmp1 = norm;
418 tmp2 = pos;
419
420 if ((_g3d_light_type & (LT_NEAR_VIEW | LT_NEAR_LIGHT)) != 0)
421 check_for_near();
422
423 // determine which routine to jump to based on flags
424 switch (_g3d_light_type) {
425 case LT_DIFF:
426 return (light_diff_raw(tmp1, temp));
427 case LT_SPEC:
428 return (light_spec_raw(tmp1, temp));
429 default:
430 return (light_dands_raw(tmp1, temp));
431 }
432 }
433
434 // farms out a point based on flags
435 // void g3_light_obj(g3s_vector *norm,g3s_vector *pos)//
436 //[eax,edx]
437 // norm is set with only 15 bits of fraction, pos is normal
438 // all vectors are in object space
439 // though we put these in points, they are in object space,
440 // not world space
g3_light_obj(g3s_phandle norm,g3s_phandle pos)441 void g3_light_obj(g3s_phandle norm, g3s_phandle pos) {
442 g3s_point *norm_point;
443 g3s_point *pos_point;
444 fix shade;
445
446 tmp1 = norm;
447 tmp2 = pos;
448 getpnt(norm_point);
449
450 norm_point->gX = norm->gX << 1;
451 norm_point->gY = norm->gY << 1;
452 norm_point->gZ = norm->gZ << 1;
453
454 // Copy position over to its own point
455 getpnt(pos_point);
456 *(g3s_vector *)pos_point = *(g3s_vector *)pos;
457
458 shade = g3_light(norm_point, pos_point);
459
460 // set the lighting when non gouraud
461 // set fill type to address of shading table
462 shade &= 0xffffff00;
463 shade += _g3d_light_tab;
464
465 gr_set_fill_parm(shade);
466
467 freepnt(norm_point);
468 freepnt(pos_point);
469 }
470
471 // generic list gronker, farms these points out
472 // call this inside an object or inside a frame
473 // at any rate, the points need to have been
474 // transformed
475 // g3_light_list(int n,g3s_phandle *norm,g3s_phandle *pos)
476 // [eax,edx,ebx]
g3_light_list(int n,g3s_phandle * norm,g3s_phandle * pos)477 void g3_light_list(int n, g3s_phandle *norm, g3s_phandle *pos) {}
478