Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | pmbaty | 1 | /* |
| 2 | * Portions of this file are copyright Rebirth contributors and licensed as |
||
| 3 | * described in COPYING.txt. |
||
| 4 | * Portions of this file are copyright Parallax Software and licensed |
||
| 5 | * according to the Parallax license below. |
||
| 6 | * See COPYING.txt for license details. |
||
| 7 | |||
| 8 | THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX |
||
| 9 | SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO |
||
| 10 | END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A |
||
| 11 | ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS |
||
| 12 | IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS |
||
| 13 | SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE |
||
| 14 | FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE |
||
| 15 | CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS |
||
| 16 | AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. |
||
| 17 | COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. |
||
| 18 | */ |
||
| 19 | |||
| 20 | /* |
||
| 21 | * |
||
| 22 | * Lighting functions. |
||
| 23 | * |
||
| 24 | */ |
||
| 25 | |||
| 26 | #include <algorithm> |
||
| 27 | #include <bitset> |
||
| 28 | #include <numeric> |
||
| 29 | #include <stdio.h> |
||
| 30 | #include <string.h> // for memset() |
||
| 31 | |||
| 32 | #include "render_state.h" |
||
| 33 | #include "maths.h" |
||
| 34 | #include "vecmat.h" |
||
| 35 | #include "gr.h" |
||
| 36 | #include "inferno.h" |
||
| 37 | #include "segment.h" |
||
| 38 | #include "dxxerror.h" |
||
| 39 | #include "render.h" |
||
| 40 | #include "game.h" |
||
| 41 | #include "vclip.h" |
||
| 42 | #include "lighting.h" |
||
| 43 | #include "3d.h" |
||
| 44 | #include "interp.h" |
||
| 45 | #include "gameseg.h" |
||
| 46 | #include "laser.h" |
||
| 47 | #include "timer.h" |
||
| 48 | #include "player.h" |
||
| 49 | #include "playsave.h" |
||
| 50 | #include "weapon.h" |
||
| 51 | #include "powerup.h" |
||
| 52 | #include "fvi.h" |
||
| 53 | #include "object.h" |
||
| 54 | #include "robot.h" |
||
| 55 | #include "multi.h" |
||
| 56 | #include "palette.h" |
||
| 57 | #include "bm.h" |
||
| 58 | #include "rle.h" |
||
| 59 | #include "wall.h" |
||
| 60 | |||
| 61 | #include "compiler-range_for.h" |
||
| 62 | #include "partial_range.h" |
||
| 63 | #include "d_range.h" |
||
| 64 | |||
| 65 | using std::min; |
||
| 66 | |||
| 67 | static int Do_dynamic_light=1; |
||
| 68 | static int use_fcd_lighting; |
||
| 69 | |||
| 70 | #define HEADLIGHT_CONE_DOT (F1_0*9/10) |
||
| 71 | #define HEADLIGHT_SCALE (F1_0*10) |
||
| 72 | |||
| 73 | namespace dcx { |
||
| 74 | |||
| 75 | static void add_light_div(g3s_lrgb &d, const g3s_lrgb &light, const fix &scale) |
||
| 76 | { |
||
| 77 | d.r += fixdiv(light.r, scale); |
||
| 78 | d.g += fixdiv(light.g, scale); |
||
| 79 | d.b += fixdiv(light.b, scale); |
||
| 80 | } |
||
| 81 | |||
| 82 | static void add_light_dot_square(g3s_lrgb &d, const g3s_lrgb &light, const fix &dot) |
||
| 83 | { |
||
| 84 | auto square = fixmul(dot, dot); |
||
| 85 | d.r += fixmul(square, light.r)/8; |
||
| 86 | d.g += fixmul(square, light.g)/8; |
||
| 87 | d.b += fixmul(square, light.b)/8; |
||
| 88 | } |
||
| 89 | |||
| 90 | static fix compute_player_light_emission_intensity(const object_base &objp) |
||
| 91 | { |
||
| 92 | auto &phys_info = objp.mtype.phys_info; |
||
| 93 | const auto drag = phys_info.drag; |
||
| 94 | const fix k = fixmuldiv(phys_info.mass, drag, (F1_0 - drag)); |
||
| 95 | // smooth thrust value like set_thrust_from_velocity() |
||
| 96 | const auto sthrust = vm_vec_copy_scale(phys_info.velocity, k); |
||
| 97 | return std::max(static_cast<fix>(vm_vec_mag_quick(sthrust) / 4), F2_0) + F0_5; |
||
| 98 | } |
||
| 99 | |||
| 100 | static fix compute_fireball_light_emission_intensity(const d_vclip_array &Vclip, const object_base &objp) |
||
| 101 | { |
||
| 102 | const auto oid = get_fireball_id(objp); |
||
| 103 | if (oid >= Vclip.size()) |
||
| 104 | return 0; |
||
| 105 | auto &v = Vclip[oid]; |
||
| 106 | const auto light_intensity = v.light_value; |
||
| 107 | if (objp.lifeleft < F1_0*4) |
||
| 108 | return fixmul(fixdiv(objp.lifeleft, v.play_time), light_intensity); |
||
| 109 | return light_intensity; |
||
| 110 | } |
||
| 111 | |||
| 112 | } |
||
| 113 | |||
| 114 | // ---------------------------------------------------------------------------------------------- |
||
| 115 | namespace dsx { |
||
| 116 | |||
| 117 | static void apply_light(fvmsegptridx &vmsegptridx, const g3s_lrgb obj_light_emission, const vcsegptridx_t obj_seg, const vms_vector &obj_pos, const unsigned n_render_vertices, std::array<unsigned, MAX_VERTICES> &render_vertices, const std::array<segnum_t, MAX_VERTICES> &vert_segnum_list, const icobjptridx_t objnum) |
||
| 118 | { |
||
| 119 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 120 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 121 | if (((obj_light_emission.r+obj_light_emission.g+obj_light_emission.b)/3) > 0) |
||
| 122 | { |
||
| 123 | fix obji_64 = ((obj_light_emission.r+obj_light_emission.g+obj_light_emission.b)/3)*64; |
||
| 124 | sbyte is_marker = 0; |
||
| 125 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 126 | if (objnum && objnum->type == OBJ_MARKER) |
||
| 127 | is_marker = 1; |
||
| 128 | #endif |
||
| 129 | |||
| 130 | auto &Dynamic_light = LevelUniqueLightState.Dynamic_light; |
||
| 131 | auto &vcvertptr = Vertices.vcptr; |
||
| 132 | // for pretty dim sources, only process vertices in object's own segment. |
||
| 133 | // 12/04/95, MK, markers only cast light in own segment. |
||
| 134 | if ((abs(obji_64) <= F1_0*8) || is_marker) { |
||
| 135 | auto &vp = obj_seg->verts; |
||
| 136 | |||
| 137 | range_for (const auto vertnum, vp) |
||
| 138 | { |
||
| 139 | fix dist; |
||
| 140 | auto &vertpos = *vcvertptr(vertnum); |
||
| 141 | dist = vm_vec_dist_quick(obj_pos, vertpos); |
||
| 142 | dist = fixmul(dist/4, dist/4); |
||
| 143 | if (dist < abs(obji_64)) { |
||
| 144 | if (dist < MIN_LIGHT_DIST) |
||
| 145 | dist = MIN_LIGHT_DIST; |
||
| 146 | |||
| 147 | add_light_div(Dynamic_light[vertnum], obj_light_emission, dist); |
||
| 148 | } |
||
| 149 | } |
||
| 150 | } else { |
||
| 151 | int headlight_shift = 0; |
||
| 152 | fix max_headlight_dist = F1_0*200; |
||
| 153 | |||
| 154 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 155 | if (objnum) |
||
| 156 | { |
||
| 157 | const object &obj = *objnum; |
||
| 158 | if (obj.type == OBJ_PLAYER) |
||
| 159 | if (obj.ctype.player_info.powerup_flags & PLAYER_FLAGS_HEADLIGHT_ON) { |
||
| 160 | headlight_shift = 3; |
||
| 161 | if (get_player_id(obj) != Player_num) |
||
| 162 | { |
||
| 163 | fvi_query fq; |
||
| 164 | fvi_info hit_data; |
||
| 165 | int fate; |
||
| 166 | |||
| 167 | const auto tvec = vm_vec_scale_add(obj.pos, obj.orient.fvec, F1_0*200); |
||
| 168 | |||
| 169 | fq.startseg = obj_seg; |
||
| 170 | fq.p0 = &obj.pos; |
||
| 171 | fq.p1 = &tvec; |
||
| 172 | fq.rad = 0; |
||
| 173 | fq.thisobjnum = objnum; |
||
| 174 | fq.ignore_obj_list.first = nullptr; |
||
| 175 | fq.flags = FQ_TRANSWALL; |
||
| 176 | |||
| 177 | fate = find_vector_intersection(fq, hit_data); |
||
| 178 | if (fate != HIT_NONE) |
||
| 179 | max_headlight_dist = vm_vec_mag_quick(vm_vec_sub(hit_data.hit_pnt, obj.pos)) + F1_0*4; |
||
| 180 | } |
||
| 181 | } |
||
| 182 | } |
||
| 183 | #endif |
||
| 184 | range_for (const unsigned vv, xrange(n_render_vertices)) |
||
| 185 | { |
||
| 186 | fix dist; |
||
| 187 | int apply_light = 0; |
||
| 188 | |||
| 189 | const auto vertnum = render_vertices[vv]; |
||
| 190 | auto vsegnum = vert_segnum_list[vv]; |
||
| 191 | auto &vertpos = *vcvertptr(vertnum); |
||
| 192 | |||
| 193 | if (use_fcd_lighting && abs(obji_64) > F1_0*32) |
||
| 194 | { |
||
| 195 | dist = find_connected_distance(obj_pos, obj_seg, vertpos, vmsegptridx(vsegnum), n_render_vertices, WID_RENDPAST_FLAG|WID_FLY_FLAG); |
||
| 196 | if (dist >= 0) |
||
| 197 | apply_light = 1; |
||
| 198 | } |
||
| 199 | else |
||
| 200 | { |
||
| 201 | dist = vm_vec_dist_quick(obj_pos, vertpos); |
||
| 202 | apply_light = 1; |
||
| 203 | } |
||
| 204 | |||
| 205 | if (apply_light && ((dist >> headlight_shift) < abs(obji_64))) { |
||
| 206 | |||
| 207 | if (dist < MIN_LIGHT_DIST) |
||
| 208 | dist = MIN_LIGHT_DIST; |
||
| 209 | |||
| 210 | if (headlight_shift && objnum) |
||
| 211 | { |
||
| 212 | fix dot; |
||
| 213 | // MK, Optimization note: You compute distance about 15 lines up, this is partially redundant |
||
| 214 | const auto vec_to_point = vm_vec_normalized_quick(vm_vec_sub(vertpos, obj_pos)); |
||
| 215 | dot = vm_vec_dot(vec_to_point, objnum->orient.fvec); |
||
| 216 | if (dot < F1_0/2) |
||
| 217 | { |
||
| 218 | // Do the normal thing, but darken around headlight. |
||
| 219 | add_light_div(Dynamic_light[vertnum], obj_light_emission, fixmul(HEADLIGHT_SCALE, dist)); |
||
| 220 | } |
||
| 221 | else |
||
| 222 | { |
||
| 223 | if (!(Game_mode & GM_MULTI) || dist < max_headlight_dist) |
||
| 224 | { |
||
| 225 | add_light_dot_square(Dynamic_light[vertnum], obj_light_emission, dot); |
||
| 226 | } |
||
| 227 | } |
||
| 228 | } |
||
| 229 | else |
||
| 230 | { |
||
| 231 | add_light_div(Dynamic_light[vertnum], obj_light_emission, dist); |
||
| 232 | } |
||
| 233 | } |
||
| 234 | } |
||
| 235 | } |
||
| 236 | } |
||
| 237 | } |
||
| 238 | } |
||
| 239 | |||
| 240 | #define FLASH_LEN_FIXED_SECONDS (F1_0/3) |
||
| 241 | #define FLASH_SCALE (3*F1_0/FLASH_LEN_FIXED_SECONDS) |
||
| 242 | |||
| 243 | // ---------------------------------------------------------------------------------------------- |
||
| 244 | static void cast_muzzle_flash_light(fvmsegptridx &vmsegptridx, int n_render_vertices, std::array<unsigned, MAX_VERTICES> &render_vertices, const std::array<segnum_t, MAX_VERTICES> &vert_segnum_list) |
||
| 245 | { |
||
| 246 | fix64 current_time; |
||
| 247 | short time_since_flash; |
||
| 248 | |||
| 249 | current_time = timer_query(); |
||
| 250 | |||
| 251 | range_for (auto &i, Muzzle_data) |
||
| 252 | { |
||
| 253 | if (i.create_time) |
||
| 254 | { |
||
| 255 | time_since_flash = current_time - i.create_time; |
||
| 256 | if (time_since_flash < FLASH_LEN_FIXED_SECONDS) |
||
| 257 | { |
||
| 258 | g3s_lrgb ml; |
||
| 259 | ml.r = ml.g = ml.b = ((FLASH_LEN_FIXED_SECONDS - time_since_flash) * FLASH_SCALE); |
||
| 260 | apply_light(vmsegptridx, ml, vmsegptridx(i.segnum), i.pos, n_render_vertices, render_vertices, vert_segnum_list, object_none); |
||
| 261 | } |
||
| 262 | else |
||
| 263 | { |
||
| 264 | i.create_time = 0; // turn off this muzzle flash |
||
| 265 | } |
||
| 266 | } |
||
| 267 | } |
||
| 268 | } |
||
| 269 | |||
| 270 | // Translation table to make flares flicker at different rates |
||
| 271 | const std::array<fix, 16> Obj_light_xlate{{0x1234, 0x3321, 0x2468, 0x1735, |
||
| 272 | 0x0123, 0x19af, 0x3f03, 0x232a, |
||
| 273 | 0x2123, 0x39af, 0x0f03, 0x132a, |
||
| 274 | 0x3123, 0x29af, 0x1f03, 0x032a |
||
| 275 | }}; |
||
| 276 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 277 | #define compute_player_light_emission_intensity(LevelUniqueHeadlightState, obj) compute_player_light_emission_intensity(obj) |
||
| 278 | #define compute_light_emission(LevelSharedRobotInfoState, LevelUniqueHeadlightState, Vclip, obj) compute_light_emission(Vclip, obj) |
||
| 279 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 280 | #undef compute_player_light_emission_intensity |
||
| 281 | #undef compute_light_emission |
||
| 282 | #endif |
||
| 283 | |||
| 284 | // --------------------------------------------------------- |
||
| 285 | namespace dsx { |
||
| 286 | |||
| 287 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 288 | static fix compute_player_light_emission_intensity(d_level_unique_headlight_state &LevelUniqueHeadlightState, const object &objp) |
||
| 289 | { |
||
| 290 | if (objp.ctype.player_info.powerup_flags & PLAYER_FLAGS_HEADLIGHT_ON) |
||
| 291 | { |
||
| 292 | auto &Headlights = LevelUniqueHeadlightState.Headlights; |
||
| 293 | auto &Num_headlights = LevelUniqueHeadlightState.Num_headlights; |
||
| 294 | if (Num_headlights < Headlights.size()) |
||
| 295 | Headlights[Num_headlights++] = &objp; |
||
| 296 | return HEADLIGHT_SCALE; |
||
| 297 | } |
||
| 298 | uint8_t hoard_orbs; |
||
| 299 | // If hoard game and player, add extra light based on how many orbs you have Pulse as well. |
||
| 300 | if (game_mode_hoard() && (hoard_orbs = objp.ctype.player_info.hoard.orbs)) |
||
| 301 | { |
||
| 302 | const fix hoardlight = 1 + (i2f(hoard_orbs) / 2); |
||
| 303 | const auto s = fix_sin(static_cast<fix>(GameTime64 >> 1) & 0xFFFF); // probably a bad way to do it |
||
| 304 | return fixmul((s + F1_0) >> 1, hoardlight); |
||
| 305 | } |
||
| 306 | return compute_player_light_emission_intensity(objp); |
||
| 307 | } |
||
| 308 | #endif |
||
| 309 | |||
| 310 | static g3s_lrgb compute_light_emission(const d_level_shared_robot_info_state &LevelSharedRobotInfoState, d_level_unique_headlight_state &LevelUniqueHeadlightState, const d_vclip_array &Vclip, const vcobjptridx_t obj) |
||
| 311 | { |
||
| 312 | int compute_color = 0; |
||
| 313 | fix light_intensity = 0; |
||
| 314 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 315 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
| 316 | #endif |
||
| 317 | const object &objp = obj; |
||
| 318 | switch (objp.type) |
||
| 319 | { |
||
| 320 | case OBJ_PLAYER: |
||
| 321 | light_intensity = compute_player_light_emission_intensity(LevelUniqueHeadlightState, objp); |
||
| 322 | break; |
||
| 323 | case OBJ_FIREBALL: |
||
| 324 | light_intensity = compute_fireball_light_emission_intensity(Vclip, objp); |
||
| 325 | break; |
||
| 326 | case OBJ_ROBOT: |
||
| 327 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 328 | light_intensity = F1_0/2; // F1_0*Robot_info[obj->id].lightcast; |
||
| 329 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 330 | light_intensity = F1_0*Robot_info[get_robot_id(objp)].lightcast; |
||
| 331 | #endif |
||
| 332 | break; |
||
| 333 | case OBJ_WEAPON: |
||
| 334 | { |
||
| 335 | const auto wid = get_weapon_id(objp); |
||
| 336 | const fix tval = Weapon_info[wid].light; |
||
| 337 | if (wid == weapon_id_type::FLARE_ID) |
||
| 338 | light_intensity = 2 * (min(tval, objp.lifeleft) + ((static_cast<fix>(GameTime64) ^ Obj_light_xlate[obj.get_unchecked_index() % Obj_light_xlate.size()]) & 0x3fff)); |
||
| 339 | else |
||
| 340 | light_intensity = tval; |
||
| 341 | break; |
||
| 342 | } |
||
| 343 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 344 | case OBJ_MARKER: |
||
| 345 | { |
||
| 346 | fix lightval = objp.lifeleft; |
||
| 347 | |||
| 348 | lightval &= 0xffff; |
||
| 349 | lightval = 8 * abs(F1_0/2 - lightval); |
||
| 350 | |||
| 351 | light_intensity = lightval; |
||
| 352 | break; |
||
| 353 | } |
||
| 354 | #endif |
||
| 355 | case OBJ_POWERUP: |
||
| 356 | light_intensity = Powerup_info[get_powerup_id(objp)].light; |
||
| 357 | break; |
||
| 358 | case OBJ_DEBRIS: |
||
| 359 | light_intensity = F1_0/4; |
||
| 360 | break; |
||
| 361 | case OBJ_LIGHT: |
||
| 362 | light_intensity = objp.ctype.light_info.intensity; |
||
| 363 | break; |
||
| 364 | default: |
||
| 365 | light_intensity = 0; |
||
| 366 | break; |
||
| 367 | } |
||
| 368 | |||
| 369 | const auto &&white_light = [light_intensity] { |
||
| 370 | return g3s_lrgb{light_intensity, light_intensity, light_intensity}; |
||
| 371 | }; |
||
| 372 | |||
| 373 | if (!PlayerCfg.DynLightColor) // colored lights not desired so use intensity only OR no intensity (== no light == no color) at all |
||
| 374 | return white_light(); |
||
| 375 | |||
| 376 | switch (objp.type) // find out if given object should cast colored light and compute if so |
||
| 377 | { |
||
| 378 | default: |
||
| 379 | break; |
||
| 380 | case OBJ_FIREBALL: |
||
| 381 | case OBJ_WEAPON: |
||
| 382 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 383 | case OBJ_MARKER: |
||
| 384 | #endif |
||
| 385 | compute_color = 1; |
||
| 386 | break; |
||
| 387 | case OBJ_POWERUP: |
||
| 388 | { |
||
| 389 | switch (get_powerup_id(objp)) |
||
| 390 | { |
||
| 391 | case POW_EXTRA_LIFE: |
||
| 392 | case POW_ENERGY: |
||
| 393 | case POW_SHIELD_BOOST: |
||
| 394 | case POW_KEY_BLUE: |
||
| 395 | case POW_KEY_RED: |
||
| 396 | case POW_KEY_GOLD: |
||
| 397 | case POW_CLOAK: |
||
| 398 | case POW_INVULNERABILITY: |
||
| 399 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 400 | case POW_HOARD_ORB: |
||
| 401 | #endif |
||
| 402 | compute_color = 1; |
||
| 403 | break; |
||
| 404 | default: |
||
| 405 | break; |
||
| 406 | } |
||
| 407 | break; |
||
| 408 | } |
||
| 409 | } |
||
| 410 | |||
| 411 | if (compute_color) |
||
| 412 | { |
||
| 413 | int t_idx_s = -1, t_idx_e = -1; |
||
| 414 | |||
| 415 | if (light_intensity < F1_0) // for every effect we want color, increase light_intensity so the effect becomes barely visible |
||
| 416 | light_intensity = F1_0; |
||
| 417 | |||
| 418 | g3s_lrgb obj_color = { 255, 255, 255 }; |
||
| 419 | |||
| 420 | switch (objp.render_type) |
||
| 421 | { |
||
| 422 | case RT_NONE: |
||
| 423 | break; // no object - no light |
||
| 424 | case RT_POLYOBJ: |
||
| 425 | { |
||
| 426 | auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; |
||
| 427 | const polymodel *const po = &Polygon_models[objp.rtype.pobj_info.model_num]; |
||
| 428 | if (po->n_textures <= 0) |
||
| 429 | { |
||
| 430 | int color = g3_poly_get_color(po->model_data.get()); |
||
| 431 | if (color) |
||
| 432 | { |
||
| 433 | obj_color.r = gr_current_pal[color].r; |
||
| 434 | obj_color.g = gr_current_pal[color].g; |
||
| 435 | obj_color.b = gr_current_pal[color].b; |
||
| 436 | } |
||
| 437 | } |
||
| 438 | else |
||
| 439 | { |
||
| 440 | t_idx_s = ObjBitmaps[ObjBitmapPtrs[po->first_texture]].index; |
||
| 441 | t_idx_e = t_idx_s + po->n_textures - 1; |
||
| 442 | } |
||
| 443 | break; |
||
| 444 | } |
||
| 445 | case RT_LASER: |
||
| 446 | { |
||
| 447 | t_idx_s = t_idx_e = Weapon_info[get_weapon_id(objp)].bitmap.index; |
||
| 448 | break; |
||
| 449 | } |
||
| 450 | case RT_POWERUP: |
||
| 451 | { |
||
| 452 | auto &v = Vclip[objp.rtype.vclip_info.vclip_num]; |
||
| 453 | auto &f = v.frames; |
||
| 454 | t_idx_s = f[0].index; |
||
| 455 | t_idx_e = f[v.num_frames - 1].index; |
||
| 456 | break; |
||
| 457 | } |
||
| 458 | case RT_WEAPON_VCLIP: |
||
| 459 | { |
||
| 460 | auto &v = Vclip[Weapon_info[get_weapon_id(objp)].weapon_vclip]; |
||
| 461 | auto &f = v.frames; |
||
| 462 | t_idx_s = f[0].index; |
||
| 463 | t_idx_e = f[v.num_frames - 1].index; |
||
| 464 | break; |
||
| 465 | } |
||
| 466 | default: |
||
| 467 | { |
||
| 468 | const auto &vc = Vclip[objp.id]; |
||
| 469 | t_idx_s = vc.frames[0].index; |
||
| 470 | t_idx_e = vc.frames[vc.num_frames-1].index; |
||
| 471 | break; |
||
| 472 | } |
||
| 473 | } |
||
| 474 | |||
| 475 | if (t_idx_s != -1 && t_idx_e != -1) |
||
| 476 | { |
||
| 477 | obj_color.r = obj_color.g = obj_color.b = 0; |
||
| 478 | range_for (const int i, xrange(t_idx_s, t_idx_e + 1)) |
||
| 479 | { |
||
| 480 | grs_bitmap *bm = &GameBitmaps[i]; |
||
| 481 | bitmap_index bi; |
||
| 482 | bi.index = i; |
||
| 483 | PIGGY_PAGE_IN(bi); |
||
| 484 | obj_color.r += bm->avg_color_rgb[0]; |
||
| 485 | obj_color.g += bm->avg_color_rgb[1]; |
||
| 486 | obj_color.b += bm->avg_color_rgb[2]; |
||
| 487 | } |
||
| 488 | } |
||
| 489 | |||
| 490 | const fix rgbsum = obj_color.r + obj_color.g + obj_color.b; |
||
| 491 | // obviously this object did not give us any usable color. so let's do our own but with blackjack and hookers! |
||
| 492 | if (rgbsum <= 0) |
||
| 493 | return white_light(); |
||
| 494 | // scale color to light intensity |
||
| 495 | const float cscale = static_cast<float>(light_intensity * 3) / rgbsum; |
||
| 496 | return g3s_lrgb{ |
||
| 497 | static_cast<fix>(obj_color.r * cscale), |
||
| 498 | static_cast<fix>(obj_color.g * cscale), |
||
| 499 | static_cast<fix>(obj_color.b * cscale) |
||
| 500 | }; |
||
| 501 | } |
||
| 502 | |||
| 503 | return white_light(); |
||
| 504 | } |
||
| 505 | |||
| 506 | // ---------------------------------------------------------------------------------------------- |
||
| 507 | void set_dynamic_light(render_state_t &rstate) |
||
| 508 | { |
||
| 509 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 510 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 511 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 512 | auto &vcobjptridx = Objects.vcptridx; |
||
| 513 | std::array<unsigned, MAX_VERTICES> render_vertices; |
||
| 514 | std::array<segnum_t, MAX_VERTICES> vert_segnum_list; |
||
| 515 | static fix light_time; |
||
| 516 | |||
| 517 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 518 | LevelUniqueLightState.Num_headlights = 0; |
||
| 519 | #endif |
||
| 520 | |||
| 521 | if (!Do_dynamic_light) |
||
| 522 | return; |
||
| 523 | |||
| 524 | light_time += FrameTime; |
||
| 525 | if (light_time < (F1_0/60)) // it's enough to stress the CPU 60 times per second |
||
| 526 | return; |
||
| 527 | light_time = light_time - (F1_0/60); |
||
| 528 | |||
| 529 | std::bitset<MAX_VERTICES> render_vertex_flags; |
||
| 530 | |||
| 531 | // Create list of vertices that need to be looked at for setting of ambient light. |
||
| 532 | auto &Dynamic_light = LevelUniqueLightState.Dynamic_light; |
||
| 533 | uint_fast32_t n_render_vertices = 0; |
||
| 534 | range_for (const auto segnum, partial_const_range(rstate.Render_list, rstate.N_render_segs)) |
||
| 535 | { |
||
| 536 | if (segnum != segment_none) { |
||
| 537 | auto &vp = Segments[segnum].verts; |
||
| 538 | range_for (const auto vnum, vp) |
||
| 539 | { |
||
| 540 | if (vnum > Vertices.get_count() - 1) |
||
| 541 | { |
||
| 542 | Int3(); //invalid vertex number |
||
| 543 | continue; //ignore it, and go on to next one |
||
| 544 | } |
||
| 545 | auto &&b = render_vertex_flags[vnum]; |
||
| 546 | if (!b) |
||
| 547 | { |
||
| 548 | b = true; |
||
| 549 | render_vertices[n_render_vertices] = vnum; |
||
| 550 | vert_segnum_list[n_render_vertices] = segnum; |
||
| 551 | n_render_vertices++; |
||
| 552 | Dynamic_light[vnum] = {}; |
||
| 553 | } |
||
| 554 | } |
||
| 555 | } |
||
| 556 | } |
||
| 557 | |||
| 558 | cast_muzzle_flash_light(vmsegptridx, n_render_vertices, render_vertices, vert_segnum_list); |
||
| 559 | |||
| 560 | range_for (const auto &&obj, vcobjptridx) |
||
| 561 | { |
||
| 562 | const object &objp = obj; |
||
| 563 | if (objp.type == OBJ_NONE) |
||
| 564 | continue; |
||
| 565 | const auto &&obj_light_emission = compute_light_emission(LevelSharedRobotInfoState, LevelUniqueLightState, Vclip, obj); |
||
| 566 | |||
| 567 | if (((obj_light_emission.r+obj_light_emission.g+obj_light_emission.b)/3) > 0) |
||
| 568 | apply_light(vmsegptridx, obj_light_emission, vcsegptridx(objp.segnum), objp.pos, n_render_vertices, render_vertices, vert_segnum_list, obj); |
||
| 569 | } |
||
| 570 | } |
||
| 571 | |||
| 572 | // --------------------------------------------------------- |
||
| 573 | |||
| 574 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 575 | |||
| 576 | void toggle_headlight_active(object &player) |
||
| 577 | { |
||
| 578 | auto &player_info = player.ctype.player_info; |
||
| 579 | if (player_info.powerup_flags & PLAYER_FLAGS_HEADLIGHT) { |
||
| 580 | player_info.powerup_flags ^= PLAYER_FLAGS_HEADLIGHT_ON; |
||
| 581 | if (Game_mode & GM_MULTI) |
||
| 582 | multi_send_flags(player.id); |
||
| 583 | } |
||
| 584 | } |
||
| 585 | |||
| 586 | static fix compute_headlight_light_on_object(const d_level_unique_headlight_state &LevelUniqueHeadlightState, const object_base &objp) |
||
| 587 | { |
||
| 588 | fix light; |
||
| 589 | |||
| 590 | // Let's just illuminate players and robots for speed reasons, ok? |
||
| 591 | if (objp.type != OBJ_ROBOT && objp.type != OBJ_PLAYER) |
||
| 592 | return 0; |
||
| 593 | |||
| 594 | light = 0; |
||
| 595 | |||
| 596 | range_for (const object_base *const light_objp, partial_const_range(LevelUniqueHeadlightState.Headlights, LevelUniqueHeadlightState.Num_headlights)) |
||
| 597 | { |
||
| 598 | auto vec_to_obj = vm_vec_sub(objp.pos, light_objp->pos); |
||
| 599 | const fix dist = vm_vec_normalize_quick(vec_to_obj); |
||
| 600 | if (dist > 0) { |
||
| 601 | const fix dot = vm_vec_dot(light_objp->orient.fvec, vec_to_obj); |
||
| 602 | |||
| 603 | if (dot < F1_0/2) |
||
| 604 | light += fixdiv(HEADLIGHT_SCALE, fixmul(HEADLIGHT_SCALE, dist)); // Do the normal thing, but darken around headlight. |
||
| 605 | else |
||
| 606 | light += fixmul(fixmul(dot, dot), HEADLIGHT_SCALE)/8; |
||
| 607 | } |
||
| 608 | } |
||
| 609 | return light; |
||
| 610 | } |
||
| 611 | #endif |
||
| 612 | |||
| 613 | } |
||
| 614 | |||
| 615 | //compute the average dynamic light in a segment. Takes the segment number |
||
| 616 | static g3s_lrgb compute_seg_dynamic_light(const std::array<g3s_lrgb, MAX_VERTICES> &Dynamic_light, const shared_segment &seg) |
||
| 617 | { |
||
| 618 | const auto &&op = [&Dynamic_light](g3s_lrgb r, const unsigned v) { |
||
| 619 | r.r += Dynamic_light[v].r; |
||
| 620 | r.g += Dynamic_light[v].g; |
||
| 621 | r.b += Dynamic_light[v].b; |
||
| 622 | return r; |
||
| 623 | }; |
||
| 624 | g3s_lrgb sum = std::accumulate(begin(seg.verts), end(seg.verts), g3s_lrgb{0, 0, 0}, op); |
||
| 625 | sum.r >>= 3; |
||
| 626 | sum.g >>= 3; |
||
| 627 | sum.b >>= 3; |
||
| 628 | return sum; |
||
| 629 | } |
||
| 630 | |||
| 631 | static std::array<g3s_lrgb, MAX_OBJECTS> object_light; |
||
| 632 | static std::array<object_signature_t, MAX_OBJECTS> object_sig; |
||
| 633 | const object *old_viewer; |
||
| 634 | static int reset_lighting_hack; |
||
| 635 | #define LIGHT_RATE i2f(4) //how fast the light ramps up |
||
| 636 | |||
| 637 | void start_lighting_frame(const object &viewer) |
||
| 638 | { |
||
| 639 | reset_lighting_hack = (&viewer != old_viewer); |
||
| 640 | old_viewer = &viewer; |
||
| 641 | } |
||
| 642 | |||
| 643 | namespace dsx { |
||
| 644 | |||
| 645 | //compute the lighting for an object. Takes a pointer to the object, |
||
| 646 | //and possibly a rotated 3d point. If the point isn't specified, the |
||
| 647 | //object's center point is rotated. |
||
| 648 | g3s_lrgb compute_object_light(const d_level_unique_light_state &LevelUniqueLightState, const vcobjptridx_t obj) |
||
| 649 | { |
||
| 650 | g3s_lrgb light; |
||
| 651 | const vcobjidx_t objnum = obj; |
||
| 652 | |||
| 653 | //First, get static (mono) light for this segment |
||
| 654 | const auto &&objsegp = vcsegptr(obj->segnum); |
||
| 655 | light.r = light.g = light.b = objsegp->static_light; |
||
| 656 | |||
| 657 | auto &os = object_sig[objnum]; |
||
| 658 | auto &ol = object_light[objnum]; |
||
| 659 | //Now, maybe return different value to smooth transitions |
||
| 660 | if (!reset_lighting_hack && os == obj->signature) |
||
| 661 | { |
||
| 662 | fix frame_delta; |
||
| 663 | g3s_lrgb delta_light; |
||
| 664 | |||
| 665 | delta_light.r = light.r - ol.r; |
||
| 666 | delta_light.g = light.g - ol.g; |
||
| 667 | delta_light.b = light.b - ol.b; |
||
| 668 | |||
| 669 | frame_delta = fixmul(LIGHT_RATE,FrameTime); |
||
| 670 | |||
| 671 | if (abs(((delta_light.r+delta_light.g+delta_light.b)/3)) <= frame_delta) |
||
| 672 | { |
||
| 673 | ol = light; //we've hit the goal |
||
| 674 | } |
||
| 675 | else |
||
| 676 | { |
||
| 677 | if (((delta_light.r+delta_light.g+delta_light.b)/3) < 0) |
||
| 678 | frame_delta = -frame_delta; |
||
| 679 | ol.r += frame_delta; |
||
| 680 | ol.g += frame_delta; |
||
| 681 | ol.b += frame_delta; |
||
| 682 | light = ol; |
||
| 683 | } |
||
| 684 | |||
| 685 | } |
||
| 686 | else //new object, initialize |
||
| 687 | { |
||
| 688 | os = obj->signature; |
||
| 689 | ol = light; |
||
| 690 | } |
||
| 691 | |||
| 692 | //Finally, add in dynamic light for this segment |
||
| 693 | auto &Dynamic_light = LevelUniqueLightState.Dynamic_light; |
||
| 694 | const auto &&seg_dl = compute_seg_dynamic_light(Dynamic_light, objsegp); |
||
| 695 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 696 | //Next, add in (NOTE: WHITE) headlight on this object |
||
| 697 | const fix mlight = compute_headlight_light_on_object(LevelUniqueLightState, obj); |
||
| 698 | light.r += mlight; |
||
| 699 | light.g += mlight; |
||
| 700 | light.b += mlight; |
||
| 701 | #endif |
||
| 702 | |||
| 703 | light.r += seg_dl.r; |
||
| 704 | light.g += seg_dl.g; |
||
| 705 | light.b += seg_dl.b; |
||
| 706 | |||
| 707 | return light; |
||
| 708 | } |
||
| 709 | |||
| 710 | } |