/* Copyright (C) 2015-2018 Night Dive Studios, LLC. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // // $Source: r:/prj/lib/src/3d/RCS/light.asm $ // $Revision: 1.2 $ // $Author: jaemz $ // $Date: 1994/10/13 20:51:49 $ // // Light routines // #include "3d.h" #include "GlobalV.h" #include "lg.h" // prototypes void check_for_near(void); void scale_light_vec(void); void g3_light_obj(g3s_phandle norm, g3s_phandle pos); fix light_diff_raw(g3s_phandle src, g3s_phandle dest); fix light_spec_raw(g3s_phandle src, g3s_phandle dest); fix light_dands_raw(g3s_phandle src, g3s_phandle dest); // look ma, a zero vector g3s_phandle tmp1; g3s_phandle tmp2; g3s_vector zero_vec = {0, 0, 0}; // sets a light vector in source space directly // this light vector has to be in user space so we can dot it with // other vector // g3_set_light_src(g3s_vector *l) // takes eax, trashes esi,edi void g3_set_light_src(g3s_vector *l) { _g3d_light_src = *l; } // This should be called after a frames' angles and stuff have been // set, does not need to be done per object // void g3_eval_vec_light(void) // Means you should be not near void g3_eval_vec_light(void) { // needs to be rotated through view matrix g3_vec_rotate(&_g3d_light_vec, &_g3d_light_src, &_wtoo_matrix); scale_light_vec(); } // should be normalized already // multiply by _g3d_diff_light // to set light intensity void scale_light_vec(void) { _g3d_light_vec.gX = fix_mul(_g3d_light_vec.gX, _g3d_diff_light); _g3d_light_vec.gY = fix_mul(_g3d_light_vec.gY, _g3d_diff_light); _g3d_light_vec.gZ = fix_mul(_g3d_light_vec.gZ, _g3d_diff_light); } // transforms local light source into viewer coords in anticipation // of calling g3_eval_loc_light. Saves a transformation don't you know // ideally you'd want to transform the view vec and light vec into // object coords. That way you wouldn't have to transform their normals // at all. The new 3d should provide inverse transforms. That way things // could be lit more cheaply void g3_trans_loc_light(void) { g3s_phandle p; p = g3_rotate_point(&_g3d_light_src); _g3d_light_trans = *(g3s_vector *)p; } // evaluates light point relative to another point, src is in world coords // and pos is already transformed into eye coords // void g3_eval_loc_light(eax)// void g3_eval_loc_light(g3s_phandle pos) { fix temp; // transform light src point to eye coords // take difference with pos, and unscale them temp = -(pos->gX - _g3d_light_trans.gX); _g3d_light_vec.gX = fix_div(temp, _matrix_scale.gX); temp = -(pos->gY - _g3d_light_trans.gY); _g3d_light_vec.gY = fix_div(temp, _matrix_scale.gY); temp = -(pos->gZ - _g3d_light_trans.gZ); _g3d_light_vec.gZ = fix_div(temp, _matrix_scale.gZ); // normalize vector g3_vec_normalize(&_g3d_light_vec); // multiply by diff and divide by scale // so dot product just works scale_light_vec(); } // evaluate the view vector relative to a point // similar to above except src is always 0,0,0 // pos in eax has to be transformed into viewer coords // eax points to the point in 3d space void g3_eval_view(g3s_phandle pos) { fix temp; _g3d_view_vec.gX = pos->gX - _view_position.gX; _g3d_view_vec.gY = pos->gY - _view_position.gY; _g3d_view_vec.gZ = pos->gZ - _view_position.gZ; // normalize g3_vec_normalize(&_g3d_view_vec); // multiply by spec and negate vector // since it currently points at the // point instead of the viewer // you might think, why not do this // afterwards, and you're right. But only // only if this view vec only gets used once temp = -_g3d_spec_light; _g3d_view_vec.gX = fix_mul(_g3d_view_vec.gX, temp); _g3d_view_vec.gY = fix_mul(_g3d_view_vec.gY, temp); _g3d_view_vec.gZ = fix_mul(_g3d_view_vec.gZ, temp); } // takes the dot product of view and light, for specular light // assumes both light_vec and view_vec have been evaluated already // everything is normal and in object space // void g3_eval_ldotv(void) void g3_eval_ldotv(void) { // multiply and scale once to find true dotproduct. // I hate this scaling stuff. Erg. // transforming the light and view vectors into object // space would avoid this entirely _g3d_ldotv = g3_vec_dotprod(&_g3d_light_vec, &_g3d_view_vec); } // Evaluates and sets light vectors as necessary at the start of // an object. Does view vec if SPEC is set. Transforms light // vector or point depending how LOC_LIGHT is set. Use this if // both light and view will be modelled as far. In fact, make // sure both are set as far, or you will be sorry. // Evaluates at the object center. If necessary, evaluates the // ldotv for light and view // void g3_eval_light_obj_cen(void) // this could be optimized a bit more when both spec // both spec and diff is true // this should do eval vec as well, basically everything void g3_eval_light_obj_cen(void) { DEBUG("%s: Call Mark if you see this", __FUNCTION__); // MLA - this routine is buggy as far as I can tell, it doesn't work at all // edi is never set or just happens to be set right, or it always falls // through the first test (non_local) /* ; is local? test _g3d_light_type,LT_LOC_LIGHT or LT_SPEC jz non_local ; find center of object, duh, center is at 0,0,0 ;lea esi,zero_vec ;call g3_rotate_point; this would be point in viewer space ; returns point in edi ;mov tmp1,edi ; evaluate local light test _g3d_light_type,LT_LOC_LIGHT jz no_loc mov eax,edi call g3_eval_loc_light jmp test_spec no_loc: call g3_eval_vec_light ; is spec set? test_spec: test _g3d_light_type,LT_SPEC jz spec_done ; evaluate view ; view vector relative to zero is in _view_position ; already ;mov eax,tmp1 lea eax,zero_vec ;in reality should write a diff routine call g3_eval_view call g3_eval_ldotv spec_done: ;mov edi,tmp1 ;freepnt edi ret non_local: ; assumes you've transformed ; it already jmp g3_eval_vec_light*/ } // set your view to be straight ahead, use when using specular, // and after the light has been evaluated. This is ultra cheap hack // to get view vector for a whole scene, just points straight in void g3_eval_view_ahead(void) { // this is in view coords, need in // object coords, this won't work _g3d_view_vec.gX = 0; _g3d_view_vec.gY = 0; _g3d_view_vec.gZ = -0x01000; // now eval ldotv, hm. _g3d_ldotv = -_g3d_light_vec.gZ; } // check to see if local stuff has to get set and // set it if necessary // takes args in tmp1,tmp2 void check_for_near(void) { if (!(_g3d_light_type & (LT_NEAR_VIEW | LT_NEAR_LIGHT))) return; // if light near, evaluate if (_g3d_light_type & LT_NEAR_LIGHT) g3_eval_loc_light(tmp2); // if view near, eval if (_g3d_light_type & LT_NEAR_VIEW) g3_eval_view(tmp2); // evaluate ldotv if either was local // MLA - this is stupid, the code that tests for whether or not to call // g3_eval_ldotv makes no sense, it does a JZ on an undetermined condition // code setup. So I just call it all the time. Look in Light.ASM in the PC 3D // code for the original stuff. g3_eval_ldotv(); } // void g3_light_diff(g3s_phandle norm,g3s_phandle pos)// // takes normal vector transformed, dots with the light vec, // puts light val in norm, // takes args in [eax,edx] void g3_light_diff(g3s_phandle norm, g3s_phandle pos) { // push eax if not gouraud, or edx if, so we know // whether to light the normal or the point // maybe we could make this self modifying based // on a light type setter, if this is slow // MLA - whatever, I made it normal C code tmp1 = norm; tmp2 = pos; check_for_near(); if ((_g3d_light_type & LT_GOUR) == 0) light_diff_raw(tmp1, norm); else light_diff_raw(tmp1, pos); } // raw version // dot product with normal // esi and edi // ret eax fix light_diff_raw(g3s_phandle src, g3s_phandle dest) { fix temp; temp = g3_vec_dotprod(&_g3d_light_vec, (g3s_vector *)src); // set lighting value in norm // test eax for negativity, zero if negative if (temp < 0) temp = 0; temp += _g3d_amb_light; // add ambient light temp >>= 4; // convert to sfix, consider row 16 normal dest->i = temp; return (temp); } // void g3_light_spec(g3s_phandle norm,g3s_phandle pos)// // takes norm and point position, lights point // could both be the same, of course [eax,edx] void g3_light_spec(g3s_phandle norm, g3s_phandle pos) { // push eax if not gouraud, or edx if, so we know // whether to light the normal or the point // maybe we could make this self modifying based // on a light type setter, if this is slow // MLA - whatever, I made it normal C code // save norm and pos off so we don't push and // pop them forever tmp1 = norm; tmp2 = pos; check_for_near(); if ((_g3d_light_type & LT_GOUR) == 0) light_spec_raw(tmp1, norm); else light_spec_raw(tmp1, pos); } // pure specular lighting is equal to // 2(s.l)(s.v) - (l.v) // take (s.l) fix light_spec_raw(g3s_phandle src, g3s_phandle dest) { fix temp; temp = g3_vec_dotprod(&_g3d_light_vec, (g3s_vector *)src); if (temp < 0) { dest->i = _g3d_amb_light >> 4; return (dest->i); } _g3d_sdotl = temp; // take (s.v), note that this is jnorm, if its been done // we can eliminate this step intelligently somehow _g3d_sdotv = temp = g3_vec_dotprod(&_g3d_view_vec, (g3s_vector *)tmp1); temp <<= 1; // multiply (s.v) by 2 temp = fix_mul(temp, _g3d_sdotl); // mult by (s.l) temp -= _g3d_ldotv; // subtract ldotv, done! // test eax for flash point zero if under // or better test eax for spec threshhold if (temp < _g3d_flash) temp = 0; // add ambient light temp += _g3d_amb_light; // check to see if its greater than the max row // and truncate if it is if (temp >= (LT_TABSIZE << 12)) ; temp = (LT_TABSIZE << 12) - 1; // if its over the max, set it to just under max dest->i = temp >> 4; // convert to sfix, consider row 16 normal return (temp); } // void g3_light_dands(g3s_phandle norm,g3s_phandle pos)// // lights with both diff and spec //[eax,edx] void g3_light_dands(g3s_phandle norm, g3s_phandle pos) { // MLA - same stuff as before, changed to C.... tmp1 = norm; tmp2 = pos; check_for_near(); if ((_g3d_light_type & LT_GOUR) == 0) light_dands_raw(tmp1, norm); else light_dands_raw(tmp1, pos); } // raw version of dands without local checking fix light_dands_raw(g3s_phandle src, g3s_phandle dest) { fix temp; // pure specular lighting is equal to // 2(s.l)(s.v) - (l.v) // take (s.l) if neg, you know you're done, surface HAS to face the light temp = g3_vec_dotprod(&_g3d_light_vec, (g3s_vector *)src); if (temp < 0) { dest->i = _g3d_amb_light >> 4; return (dest->i); } _g3d_sdotl = temp; // take (s.v), note that this is jnorm, if its been done // we can eliminate this step intelligently somehow _g3d_sdotv = temp = g3_vec_dotprod(&_g3d_view_vec, (g3s_vector *)tmp1); temp = fix_mul(temp, _g3d_sdotl); // mult by (s.l) temp <<= 1; // multiply (s.v)(s.l) by 2 temp -= _g3d_ldotv; // subtract ldotv, done! // test eax for flash point zero if under // or better test eax for spec threshhold if (temp < _g3d_flash) temp = 0; // add diffuse component & ambient light temp += _g3d_sdotl + _g3d_amb_light; // check to see if its greater than the max row // and truncate if it is if (temp >= (LT_TABSIZE << 12)) temp = (LT_TABSIZE << 12) - 1; // if its over the max, set it to just under max dest->i = temp >> 4; // convert to sfix, consider row 16 normal return (temp); } // farms out a point based on flags // void g3_light(g3s_phandle norm,g3s_phandle pos)// //[eax,edx] fix g3_light(g3s_phandle norm, g3s_phandle pos) { g3s_phandle temp; if ((_g3d_light_type & LT_GOUR) == 0) temp = norm; else temp = pos; tmp1 = norm; tmp2 = pos; if ((_g3d_light_type & (LT_NEAR_VIEW | LT_NEAR_LIGHT)) != 0) check_for_near(); // determine which routine to jump to based on flags switch (_g3d_light_type) { case LT_DIFF: return (light_diff_raw(tmp1, temp)); case LT_SPEC: return (light_spec_raw(tmp1, temp)); default: return (light_dands_raw(tmp1, temp)); } } // farms out a point based on flags // void g3_light_obj(g3s_vector *norm,g3s_vector *pos)// //[eax,edx] // norm is set with only 15 bits of fraction, pos is normal // all vectors are in object space // though we put these in points, they are in object space, // not world space void g3_light_obj(g3s_phandle norm, g3s_phandle pos) { g3s_point *norm_point; g3s_point *pos_point; fix shade; tmp1 = norm; tmp2 = pos; getpnt(norm_point); norm_point->gX = norm->gX << 1; norm_point->gY = norm->gY << 1; norm_point->gZ = norm->gZ << 1; // Copy position over to its own point getpnt(pos_point); *(g3s_vector *)pos_point = *(g3s_vector *)pos; shade = g3_light(norm_point, pos_point); // set the lighting when non gouraud // set fill type to address of shading table shade &= 0xffffff00; shade += _g3d_light_tab; gr_set_fill_parm(shade); freepnt(norm_point); freepnt(pos_point); } // generic list gronker, farms these points out // call this inside an object or inside a frame // at any rate, the points need to have been // transformed // g3_light_list(int n,g3s_phandle *norm,g3s_phandle *pos) // [eax,edx,ebx] void g3_light_list(int n, g3s_phandle *norm, g3s_phandle *pos) {}