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