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 | * Functions moved from segment.c to make editor separable from game. |
||
| 23 | * |
||
| 24 | */ |
||
| 25 | |||
| 26 | #include <algorithm> |
||
| 27 | #include <cassert> |
||
| 28 | #include <stdlib.h> |
||
| 29 | #include <stdio.h> |
||
| 30 | #include <string.h> // for memset() |
||
| 31 | |||
| 32 | #include "u_mem.h" |
||
| 33 | #include "inferno.h" |
||
| 34 | #include "game.h" |
||
| 35 | #include "dxxerror.h" |
||
| 36 | #include "console.h" |
||
| 37 | #include "vecmat.h" |
||
| 38 | #include "gameseg.h" |
||
| 39 | #include "gameseq.h" |
||
| 40 | #include "wall.h" |
||
| 41 | #include "fuelcen.h" |
||
| 42 | #include "textures.h" |
||
| 43 | #include "fvi.h" |
||
| 44 | #include "object.h" |
||
| 45 | #include "byteutil.h" |
||
| 46 | #include "lighting.h" |
||
| 47 | #include "mission.h" |
||
| 48 | #if DXX_USE_EDITOR |
||
| 49 | #include "editor/editor.h" |
||
| 50 | #endif |
||
| 51 | |||
| 52 | #include "compiler-range_for.h" |
||
| 53 | #include "d_range.h" |
||
| 54 | #include "cast_range_result.h" |
||
| 55 | |||
| 56 | using std::min; |
||
| 57 | |||
| 58 | namespace { |
||
| 59 | |||
| 60 | /* The array can be of any type that can hold values in the range |
||
| 61 | * [0, AMBIENT_SEGMENT_DEPTH]. |
||
| 62 | */ |
||
| 63 | struct segment_lava_depth_array : std::array<uint8_t, MAX_SEGMENTS> {}; |
||
| 64 | struct segment_water_depth_array : std::array<uint8_t, MAX_SEGMENTS> {}; |
||
| 65 | |||
| 66 | class abs_vertex_lists_predicate |
||
| 67 | { |
||
| 68 | const std::array<unsigned, MAX_VERTICES_PER_SEGMENT> &m_vp; |
||
| 69 | const std::array<unsigned, 4> &m_sv; |
||
| 70 | public: |
||
| 71 | abs_vertex_lists_predicate(const shared_segment &seg, const uint_fast32_t sidenum) : |
||
| 72 | m_vp(seg.verts), m_sv(Side_to_verts_int[sidenum]) |
||
| 73 | { |
||
| 74 | } |
||
| 75 | unsigned operator()(const uint_fast32_t vv) const |
||
| 76 | { |
||
| 77 | return m_vp[m_sv[vv]]; |
||
| 78 | } |
||
| 79 | }; |
||
| 80 | |||
| 81 | class all_vertnum_lists_predicate : public abs_vertex_lists_predicate |
||
| 82 | { |
||
| 83 | public: |
||
| 84 | using abs_vertex_lists_predicate::abs_vertex_lists_predicate; |
||
| 85 | vertex_vertnum_pair operator()(const uint_fast32_t vv) const |
||
| 86 | { |
||
| 87 | return {this->abs_vertex_lists_predicate::operator()(vv), static_cast<unsigned>(vv)}; |
||
| 88 | } |
||
| 89 | }; |
||
| 90 | |||
| 91 | struct verts_for_normal |
||
| 92 | { |
||
| 93 | std::array<unsigned, 4> vsorted; |
||
| 94 | bool negate_flag; |
||
| 95 | }; |
||
| 96 | |||
| 97 | constexpr vm_distance fcd_abort_cache_value{F1_0 * 1000}; |
||
| 98 | constexpr vm_distance fcd_abort_return_value{-1}; |
||
| 99 | |||
| 100 | } |
||
| 101 | |||
| 102 | namespace dcx { |
||
| 103 | |||
| 104 | // How far a point can be from a plane, and still be "in" the plane |
||
| 105 | #define PLANE_DIST_TOLERANCE 250 |
||
| 106 | |||
| 107 | static uint_fast32_t find_connect_child(const vcsegidx_t base_seg, const std::array<segnum_t, MAX_SIDES_PER_SEGMENT> &children) |
||
| 108 | { |
||
| 109 | const auto &&b = begin(children); |
||
| 110 | return std::distance(b, std::find(b, end(children), base_seg)); |
||
| 111 | } |
||
| 112 | |||
| 113 | static void compute_center_point_on_side(fvcvertptr &vcvertptr, vms_vector &r, const std::array<unsigned, MAX_VERTICES_PER_SEGMENT> &verts, const unsigned side) |
||
| 114 | { |
||
| 115 | vms_vector vp; |
||
| 116 | vm_vec_zero(vp); |
||
| 117 | range_for (auto &v, Side_to_verts[side]) |
||
| 118 | vm_vec_add2(vp, vcvertptr(verts[v])); |
||
| 119 | vm_vec_copy_scale(r, vp, F1_0 / 4); |
||
| 120 | } |
||
| 121 | |||
| 122 | static void compute_segment_center(fvcvertptr &vcvertptr, vms_vector &r, const std::array<unsigned, MAX_VERTICES_PER_SEGMENT> &verts) |
||
| 123 | { |
||
| 124 | vms_vector vp; |
||
| 125 | vm_vec_zero(vp); |
||
| 126 | range_for (auto &v, verts) |
||
| 127 | vm_vec_add2(vp, vcvertptr(v)); |
||
| 128 | vm_vec_copy_scale(r, vp, F1_0 / 8); |
||
| 129 | } |
||
| 130 | |||
| 131 | // ------------------------------------------------------------------------------------------ |
||
| 132 | // Compute the center point of a side of a segment. |
||
| 133 | // The center point is defined to be the average of the 4 points defining the side. |
||
| 134 | void compute_center_point_on_side(fvcvertptr &vcvertptr, vms_vector &vp, const shared_segment &sp, const unsigned side) |
||
| 135 | { |
||
| 136 | compute_center_point_on_side(vcvertptr, vp, sp.verts, side); |
||
| 137 | } |
||
| 138 | |||
| 139 | // ------------------------------------------------------------------------------------------ |
||
| 140 | // Compute segment center. |
||
| 141 | // The center point is defined to be the average of the 8 points defining the segment. |
||
| 142 | void compute_segment_center(fvcvertptr &vcvertptr, vms_vector &vp, const shared_segment &sp) |
||
| 143 | { |
||
| 144 | compute_segment_center(vcvertptr, vp, sp.verts); |
||
| 145 | } |
||
| 146 | |||
| 147 | // ----------------------------------------------------------------------------- |
||
| 148 | // Given two segments, return the side index in the connecting segment which connects to the base segment |
||
| 149 | // Optimized by MK on 4/21/94 because it is a 2% load. |
||
| 150 | uint_fast32_t find_connect_side(const vcsegidx_t base_seg, const shared_segment &con_seg) |
||
| 151 | { |
||
| 152 | return find_connect_child(base_seg, con_seg.children); |
||
| 153 | } |
||
| 154 | |||
| 155 | // ----------------------------------------------------------------------------------- |
||
| 156 | // Given a side, return the number of faces |
||
| 157 | bool get_side_is_quad(const shared_side &sidep) |
||
| 158 | { |
||
| 159 | switch (sidep.get_type()) |
||
| 160 | { |
||
| 161 | case side_type::quad: |
||
| 162 | return true; |
||
| 163 | case side_type::tri_02: |
||
| 164 | case side_type::tri_13: |
||
| 165 | return false; |
||
| 166 | default: |
||
| 167 | throw shared_side::illegal_type(sidep); |
||
| 168 | } |
||
| 169 | } |
||
| 170 | |||
| 171 | // Fill in array with four absolute point numbers for a given side |
||
| 172 | static void get_side_verts(side_vertnum_list_t &vertlist, const std::array<unsigned, MAX_VERTICES_PER_SEGMENT> &vp, const unsigned sidenum) |
||
| 173 | { |
||
| 174 | auto &sv = Side_to_verts[sidenum]; |
||
| 175 | for (unsigned i = 4; i--;) |
||
| 176 | vertlist[i] = vp[sv[i]]; |
||
| 177 | } |
||
| 178 | |||
| 179 | void get_side_verts(side_vertnum_list_t &vertlist, const shared_segment &segp, const unsigned sidenum) |
||
| 180 | { |
||
| 181 | get_side_verts(vertlist, segp.verts, sidenum); |
||
| 182 | } |
||
| 183 | |||
| 184 | } |
||
| 185 | |||
| 186 | namespace dsx { |
||
| 187 | |||
| 188 | __attribute_cold |
||
| 189 | __noreturn |
||
| 190 | static void create_vertex_list_from_invalid_side(const shared_segment &segp, const shared_side &sidep) |
||
| 191 | { |
||
| 192 | throw shared_side::illegal_type(segp, sidep); |
||
| 193 | } |
||
| 194 | |||
| 195 | template <typename T, typename V> |
||
| 196 | static uint_fast32_t create_vertex_lists_from_values(T &va, const shared_segment &segp, const shared_side &sidep, const V &&f0, const V &&f1, const V &&f2, const V &&f3) |
||
| 197 | { |
||
| 198 | const auto type = sidep.get_type(); |
||
| 199 | if (type == side_type::tri_13) |
||
| 200 | { |
||
| 201 | va[0] = va[5] = f3; |
||
| 202 | va[1] = f0; |
||
| 203 | va[2] = va[3] = f1; |
||
| 204 | va[4] = f2; |
||
| 205 | return 2; |
||
| 206 | } |
||
| 207 | va[0] = f0; |
||
| 208 | va[1] = f1; |
||
| 209 | va[2] = f2; |
||
| 210 | switch (type) |
||
| 211 | { |
||
| 212 | case side_type::quad: |
||
| 213 | va[3] = f3; |
||
| 214 | /* Unused, but required to prevent bogus |
||
| 215 | * -Wmaybe-uninitialized in check_segment_connections |
||
| 216 | */ |
||
| 217 | va[4] = va[5] = {}; |
||
| 218 | DXX_MAKE_MEM_UNDEFINED(&va[4], 2 * sizeof(va[4])); |
||
| 219 | return 1; |
||
| 220 | case side_type::tri_02: |
||
| 221 | va[3] = f2; |
||
| 222 | va[4] = f3; |
||
| 223 | va[5] = f0; |
||
| 224 | |||
| 225 | //IMPORTANT: DON'T CHANGE THIS CODE WITHOUT CHANGING GET_SEG_MASKS() |
||
| 226 | //CREATE_ABS_VERTEX_LISTS(), CREATE_ALL_VERTEX_LISTS(), CREATE_ALL_VERTNUM_LISTS() |
||
| 227 | return 2; |
||
| 228 | default: |
||
| 229 | create_vertex_list_from_invalid_side(segp, sidep); |
||
| 230 | } |
||
| 231 | } |
||
| 232 | |||
| 233 | template <typename T, typename F> |
||
| 234 | static inline uint_fast32_t create_vertex_lists_by_predicate(T &va, const shared_segment &segp, const shared_side &sidep, const F &&f) |
||
| 235 | { |
||
| 236 | return create_vertex_lists_from_values(va, segp, sidep, f(0), f(1), f(2), f(3)); |
||
| 237 | } |
||
| 238 | |||
| 239 | #if DXX_USE_EDITOR |
||
| 240 | // ----------------------------------------------------------------------------------- |
||
| 241 | // Create all vertex lists (1 or 2) for faces on a side. |
||
| 242 | // Sets: |
||
| 243 | // num_faces number of lists |
||
| 244 | // vertices vertices in all (1 or 2) faces |
||
| 245 | // If there is one face, it has 4 vertices. |
||
| 246 | // If there are two faces, they both have three vertices, so face #0 is stored in vertices 0,1,2, |
||
| 247 | // face #1 is stored in vertices 3,4,5. |
||
| 248 | // Note: these are not absolute vertex numbers, but are relative to the segment |
||
| 249 | // Note: for triagulated sides, the middle vertex of each trianle is the one NOT |
||
| 250 | // adjacent on the diagonal edge |
||
| 251 | uint_fast32_t create_all_vertex_lists(vertex_array_list_t &vertices, const shared_segment &segp, const shared_side &sidep, const uint_fast32_t sidenum) |
||
| 252 | { |
||
| 253 | assert(sidenum < Side_to_verts_int.size()); |
||
| 254 | auto &sv = Side_to_verts_int[sidenum]; |
||
| 255 | return create_vertex_lists_by_predicate(vertices, segp, sidep, [&sv](const uint_fast32_t vv) { |
||
| 256 | return sv[vv]; |
||
| 257 | }); |
||
| 258 | } |
||
| 259 | #endif |
||
| 260 | |||
| 261 | // ----------------------------------------------------------------------------------- |
||
| 262 | // Like create all vertex lists, but returns the vertnums (relative to |
||
| 263 | // the side) for each of the faces that make up the side. |
||
| 264 | // If there is one face, it has 4 vertices. |
||
| 265 | // If there are two faces, they both have three vertices, so face #0 is stored in vertices 0,1,2, |
||
| 266 | // face #1 is stored in vertices 3,4,5. |
||
| 267 | void create_all_vertnum_lists(vertex_vertnum_array_list &vertnums, const shared_segment &segp, const shared_side &sidep, const uint_fast32_t sidenum) |
||
| 268 | { |
||
| 269 | create_vertex_lists_by_predicate(vertnums, segp, sidep, all_vertnum_lists_predicate(segp, sidenum)); |
||
| 270 | } |
||
| 271 | |||
| 272 | // ----- |
||
| 273 | // like create_all_vertex_lists(), but generate absolute point numbers |
||
| 274 | uint_fast32_t create_abs_vertex_lists(vertex_array_list_t &vertices, const shared_segment &segp, const shared_side &sidep, const uint_fast32_t sidenum) |
||
| 275 | { |
||
| 276 | return create_vertex_lists_by_predicate(vertices, segp, sidep, abs_vertex_lists_predicate(segp, sidenum)); |
||
| 277 | } |
||
| 278 | |||
| 279 | //returns 3 different bitmasks with info telling if this sphere is in |
||
| 280 | //this segment. See segmasks structure for info on fields |
||
| 281 | segmasks get_seg_masks(fvcvertptr &vcvertptr, const vms_vector &checkp, const shared_segment &seg, const fix rad) |
||
| 282 | { |
||
| 283 | int sn,facebit,sidebit; |
||
| 284 | segmasks masks{}; |
||
| 285 | |||
| 286 | //check point against each side of segment. return bitmask |
||
| 287 | |||
| 288 | for (sn=0,facebit=sidebit=1;sn<6;sn++,sidebit<<=1) { |
||
| 289 | auto &s = seg.sides[sn]; |
||
| 290 | |||
| 291 | // Get number of faces on this side, and at vertex_list, store vertices. |
||
| 292 | // If one face, then vertex_list indicates a quadrilateral. |
||
| 293 | // If two faces, then 0,1,2 define one triangle, 3,4,5 define the second. |
||
| 294 | const auto v = create_abs_vertex_lists(seg, s, sn); |
||
| 295 | const auto &num_faces = v.first; |
||
| 296 | const auto &vertex_list = v.second; |
||
| 297 | |||
| 298 | //ok...this is important. If a side has 2 faces, we need to know if |
||
| 299 | //those faces form a concave or convex side. If the side pokes out, |
||
| 300 | //then a point is on the back of the side if it is behind BOTH faces, |
||
| 301 | //but if the side pokes in, a point is on the back if behind EITHER face. |
||
| 302 | |||
| 303 | if (num_faces==2) { |
||
| 304 | int side_count,center_count; |
||
| 305 | |||
| 306 | const auto vertnum = min(vertex_list[0],vertex_list[2]); |
||
| 307 | const auto &&mvert = vcvertptr(vertnum); |
||
| 308 | |||
| 309 | auto a = vertex_list[4] < vertex_list[1] |
||
| 310 | ? std::make_pair(vertex_list[4], &s.normals[0]) |
||
| 311 | : std::make_pair(vertex_list[1], &s.normals[1]); |
||
| 312 | const auto mdist = vm_dist_to_plane(vcvertptr(a.first), *a.second, mvert); |
||
| 313 | |||
| 314 | side_count = center_count = 0; |
||
| 315 | |||
| 316 | for (int fn=0;fn<2;fn++,facebit<<=1) { |
||
| 317 | |||
| 318 | const auto dist = vm_dist_to_plane(checkp, s.normals[fn], mvert); |
||
| 319 | |||
| 320 | if (dist-rad < -PLANE_DIST_TOLERANCE) { |
||
| 321 | if (dist < -PLANE_DIST_TOLERANCE) //in front of face |
||
| 322 | center_count++; |
||
| 323 | |||
| 324 | masks.facemask |= facebit; |
||
| 325 | side_count++; |
||
| 326 | } |
||
| 327 | } |
||
| 328 | |||
| 329 | if (!(mdist > PLANE_DIST_TOLERANCE)) { //must be behind both faces |
||
| 330 | |||
| 331 | if (side_count==2) |
||
| 332 | masks.sidemask |= sidebit; |
||
| 333 | |||
| 334 | if (center_count==2) |
||
| 335 | masks.centermask |= sidebit; |
||
| 336 | |||
| 337 | } |
||
| 338 | else { //must be behind at least one face |
||
| 339 | |||
| 340 | if (side_count) |
||
| 341 | masks.sidemask |= sidebit; |
||
| 342 | |||
| 343 | if (center_count) |
||
| 344 | masks.centermask |= sidebit; |
||
| 345 | |||
| 346 | } |
||
| 347 | |||
| 348 | |||
| 349 | } |
||
| 350 | else { //only one face on this side |
||
| 351 | //use lowest point number |
||
| 352 | auto b = begin(vertex_list); |
||
| 353 | const auto vertnum = *std::min_element(b, std::next(b, 4)); |
||
| 354 | const auto &&mvert = vcvertptr(vertnum); |
||
| 355 | |||
| 356 | const auto dist = vm_dist_to_plane(checkp, s.normals[0], mvert); |
||
| 357 | if (dist-rad < -PLANE_DIST_TOLERANCE) { |
||
| 358 | if (dist < -PLANE_DIST_TOLERANCE) |
||
| 359 | masks.centermask |= sidebit; |
||
| 360 | |||
| 361 | masks.facemask |= facebit; |
||
| 362 | masks.sidemask |= sidebit; |
||
| 363 | } |
||
| 364 | |||
| 365 | facebit <<= 2; |
||
| 366 | } |
||
| 367 | |||
| 368 | } |
||
| 369 | return masks; |
||
| 370 | } |
||
| 371 | |||
| 372 | //this was converted from get_seg_masks()...it fills in an array of 6 |
||
| 373 | //elements for the distace behind each side, or zero if not behind |
||
| 374 | //only gets centermask, and assumes zero rad |
||
| 375 | static uint8_t get_side_dists(fvcvertptr &vcvertptr, const vms_vector &checkp, const shared_segment &segnum, std::array<fix, 6> &side_dists) |
||
| 376 | { |
||
| 377 | int sn,facebit,sidebit; |
||
| 378 | ubyte mask; |
||
| 379 | auto &sides = segnum.sides; |
||
| 380 | |||
| 381 | //check point against each side of segment. return bitmask |
||
| 382 | |||
| 383 | mask = 0; |
||
| 384 | |||
| 385 | side_dists = {}; |
||
| 386 | for (sn=0,facebit=sidebit=1;sn<6;sn++,sidebit<<=1) { |
||
| 387 | auto &s = sides[sn]; |
||
| 388 | |||
| 389 | // Get number of faces on this side, and at vertex_list, store vertices. |
||
| 390 | // If one face, then vertex_list indicates a quadrilateral. |
||
| 391 | // If two faces, then 0,1,2 define one triangle, 3,4,5 define the second. |
||
| 392 | const auto v = create_abs_vertex_lists(segnum, s, sn); |
||
| 393 | const auto &num_faces = v.first; |
||
| 394 | const auto &vertex_list = v.second; |
||
| 395 | |||
| 396 | //ok...this is important. If a side has 2 faces, we need to know if |
||
| 397 | //those faces form a concave or convex side. If the side pokes out, |
||
| 398 | //then a point is on the back of the side if it is behind BOTH faces, |
||
| 399 | //but if the side pokes in, a point is on the back if behind EITHER face. |
||
| 400 | |||
| 401 | if (num_faces==2) { |
||
| 402 | int center_count; |
||
| 403 | |||
| 404 | const auto vertnum = min(vertex_list[0],vertex_list[2]); |
||
| 405 | auto &mvert = *vcvertptr(vertnum); |
||
| 406 | |||
| 407 | auto a = vertex_list[4] < vertex_list[1] |
||
| 408 | ? std::make_pair(vertex_list[4], &s.normals[0]) |
||
| 409 | : std::make_pair(vertex_list[1], &s.normals[1]); |
||
| 410 | const auto mdist = vm_dist_to_plane(vcvertptr(a.first), *a.second, mvert); |
||
| 411 | |||
| 412 | center_count = 0; |
||
| 413 | |||
| 414 | for (int fn=0;fn<2;fn++,facebit<<=1) { |
||
| 415 | |||
| 416 | const auto dist = vm_dist_to_plane(checkp, s.normals[fn], mvert); |
||
| 417 | |||
| 418 | if (dist < -PLANE_DIST_TOLERANCE) { //in front of face |
||
| 419 | center_count++; |
||
| 420 | side_dists[sn] += dist; |
||
| 421 | } |
||
| 422 | |||
| 423 | } |
||
| 424 | |||
| 425 | if (!(mdist > PLANE_DIST_TOLERANCE)) { //must be behind both faces |
||
| 426 | |||
| 427 | if (center_count==2) { |
||
| 428 | mask |= sidebit; |
||
| 429 | side_dists[sn] /= 2; //get average |
||
| 430 | } |
||
| 431 | |||
| 432 | |||
| 433 | } |
||
| 434 | else { //must be behind at least one face |
||
| 435 | |||
| 436 | if (center_count) { |
||
| 437 | mask |= sidebit; |
||
| 438 | if (center_count==2) |
||
| 439 | side_dists[sn] /= 2; //get average |
||
| 440 | |||
| 441 | } |
||
| 442 | } |
||
| 443 | |||
| 444 | |||
| 445 | } |
||
| 446 | else { //only one face on this side |
||
| 447 | //use lowest point number |
||
| 448 | |||
| 449 | auto b = begin(vertex_list); |
||
| 450 | auto vertnum = *std::min_element(b, std::next(b, 4)); |
||
| 451 | |||
| 452 | const auto dist = vm_dist_to_plane(checkp, s.normals[0], vcvertptr(vertnum)); |
||
| 453 | |||
| 454 | if (dist < -PLANE_DIST_TOLERANCE) { |
||
| 455 | mask |= sidebit; |
||
| 456 | side_dists[sn] = dist; |
||
| 457 | } |
||
| 458 | |||
| 459 | facebit <<= 2; |
||
| 460 | } |
||
| 461 | |||
| 462 | } |
||
| 463 | |||
| 464 | return mask; |
||
| 465 | |||
| 466 | } |
||
| 467 | |||
| 468 | #ifndef NDEBUG |
||
| 469 | //returns true if errors detected |
||
| 470 | static int check_norms(const shared_segment &segp, const unsigned sidenum, const unsigned facenum, const shared_segment &csegp, const unsigned csidenum, const unsigned cfacenum) |
||
| 471 | { |
||
| 472 | const auto &n0 = segp.sides[sidenum].normals[facenum]; |
||
| 473 | const auto &n1 = csegp.sides[csidenum].normals[cfacenum]; |
||
| 474 | if (n0.x != -n1.x || n0.y != -n1.y || n0.z != -n1.z) |
||
| 475 | return 1; |
||
| 476 | else |
||
| 477 | return 0; |
||
| 478 | } |
||
| 479 | |||
| 480 | static void invert_shared_side_triangle_type(shared_side &s) |
||
| 481 | { |
||
| 482 | const auto t = s.get_type(); |
||
| 483 | side_type nt; |
||
| 484 | switch (t) |
||
| 485 | { |
||
| 486 | case side_type::tri_02: |
||
| 487 | nt = side_type::tri_13; |
||
| 488 | break; |
||
| 489 | case side_type::tri_13: |
||
| 490 | nt = side_type::tri_02; |
||
| 491 | break; |
||
| 492 | default: |
||
| 493 | return; |
||
| 494 | } |
||
| 495 | s.set_type(nt); |
||
| 496 | } |
||
| 497 | #endif |
||
| 498 | |||
| 499 | //heavy-duty error checking |
||
| 500 | int check_segment_connections(void) |
||
| 501 | { |
||
| 502 | int errors=0; |
||
| 503 | |||
| 504 | range_for (const auto &&seg, vmsegptridx) |
||
| 505 | { |
||
| 506 | range_for (const int sidenum, xrange(6u)) { |
||
| 507 | #ifndef NDEBUG |
||
| 508 | const auto v = create_abs_vertex_lists(seg, sidenum); |
||
| 509 | const auto &num_faces = v.first; |
||
| 510 | const auto &vertex_list = v.second; |
||
| 511 | #endif |
||
| 512 | auto csegnum = seg->children[sidenum]; |
||
| 513 | if (IS_CHILD(csegnum)) { |
||
| 514 | auto cseg = vcsegptr(csegnum); |
||
| 515 | auto csidenum = find_connect_side(seg,cseg); |
||
| 516 | |||
| 517 | if (csidenum == side_none) |
||
| 518 | { |
||
| 519 | shared_segment &rseg = *seg; |
||
| 520 | const shared_segment &rcseg = *cseg; |
||
| 521 | const unsigned segi = seg.get_unchecked_index(); |
||
| 522 | LevelError("Segment #%u side %u has asymmetric link to segment %u. Coercing to segment_none; Segments[%u].children={%hu, %hu, %hu, %hu, %hu, %hu}, Segments[%u].children={%hu, %hu, %hu, %hu, %hu, %hu}.", segi, sidenum, csegnum, segi, rseg.children[0], rseg.children[1], rseg.children[2], rseg.children[3], rseg.children[4], rseg.children[5], csegnum, rcseg.children[0], rcseg.children[1], rcseg.children[2], rcseg.children[3], rcseg.children[4], rcseg.children[5]); |
||
| 523 | rseg.children[sidenum] = segment_none; |
||
| 524 | errors = 1; |
||
| 525 | continue; |
||
| 526 | } |
||
| 527 | |||
| 528 | #ifndef NDEBUG |
||
| 529 | const auto cv = create_abs_vertex_lists(cseg, csidenum); |
||
| 530 | const auto &con_num_faces = cv.first; |
||
| 531 | const auto &con_vertex_list = cv.second; |
||
| 532 | |||
| 533 | if (con_num_faces != num_faces) { |
||
| 534 | LevelError("Segment #%u side %u: wrong faces: con_num_faces=%" PRIuFAST32 " num_faces=%" PRIuFAST32 ".", seg.get_unchecked_index(), sidenum, con_num_faces, num_faces); |
||
| 535 | errors = 1; |
||
| 536 | } |
||
| 537 | else |
||
| 538 | if (num_faces == 1) { |
||
| 539 | int t; |
||
| 540 | |||
| 541 | for (t=0;t<4 && con_vertex_list[t]!=vertex_list[0];t++); |
||
| 542 | |||
| 543 | if (t==4 || |
||
| 544 | vertex_list[0] != con_vertex_list[t] || |
||
| 545 | vertex_list[1] != con_vertex_list[(t+3)%4] || |
||
| 546 | vertex_list[2] != con_vertex_list[(t+2)%4] || |
||
| 547 | vertex_list[3] != con_vertex_list[(t+1)%4]) { |
||
| 548 | LevelError("Segment #%u side %u: bad vertices.", seg.get_unchecked_index(), sidenum); |
||
| 549 | errors = 1; |
||
| 550 | } |
||
| 551 | else |
||
| 552 | errors |= check_norms(seg,sidenum,0,cseg,csidenum,0); |
||
| 553 | |||
| 554 | } |
||
| 555 | else { |
||
| 556 | |||
| 557 | if (vertex_list[1] == con_vertex_list[1]) { |
||
| 558 | |||
| 559 | if (vertex_list[4] != con_vertex_list[4] || |
||
| 560 | vertex_list[0] != con_vertex_list[2] || |
||
| 561 | vertex_list[2] != con_vertex_list[0] || |
||
| 562 | vertex_list[3] != con_vertex_list[5] || |
||
| 563 | vertex_list[5] != con_vertex_list[3]) { |
||
| 564 | auto &cside = vmsegptr(csegnum)->shared_segment::sides[csidenum]; |
||
| 565 | invert_shared_side_triangle_type(cside); |
||
| 566 | } else { |
||
| 567 | errors |= check_norms(seg,sidenum,0,cseg,csidenum,0); |
||
| 568 | errors |= check_norms(seg,sidenum,1,cseg,csidenum,1); |
||
| 569 | } |
||
| 570 | |||
| 571 | } else { |
||
| 572 | |||
| 573 | if (vertex_list[1] != con_vertex_list[4] || |
||
| 574 | vertex_list[4] != con_vertex_list[1] || |
||
| 575 | vertex_list[0] != con_vertex_list[5] || |
||
| 576 | vertex_list[5] != con_vertex_list[0] || |
||
| 577 | vertex_list[2] != con_vertex_list[3] || |
||
| 578 | vertex_list[3] != con_vertex_list[2]) { |
||
| 579 | auto &cside = vmsegptr(csegnum)->shared_segment::sides[csidenum]; |
||
| 580 | invert_shared_side_triangle_type(cside); |
||
| 581 | } else { |
||
| 582 | errors |= check_norms(seg,sidenum,0,cseg,csidenum,1); |
||
| 583 | errors |= check_norms(seg,sidenum,1,cseg,csidenum,0); |
||
| 584 | } |
||
| 585 | } |
||
| 586 | } |
||
| 587 | #endif |
||
| 588 | } |
||
| 589 | } |
||
| 590 | } |
||
| 591 | return errors; |
||
| 592 | } |
||
| 593 | |||
| 594 | // Used to become a constant based on editor, but I wanted to be able to set |
||
| 595 | // this for omega blob find_point_seg calls. |
||
| 596 | // Would be better to pass a paremeter to the routine...--MK, 01/17/96 |
||
| 597 | #if defined(DXX_BUILD_DESCENT_II) || DXX_USE_EDITOR |
||
| 598 | int Doing_lighting_hack_flag=0; |
||
| 599 | #else |
||
| 600 | #define Doing_lighting_hack_flag 0 |
||
| 601 | #endif |
||
| 602 | |||
| 603 | // figure out what seg the given point is in, tracing through segments |
||
| 604 | // returns segment number, or -1 if can't find segment |
||
| 605 | static icsegptridx_t trace_segs(const d_level_shared_segment_state &LevelSharedSegmentState, const vms_vector &p0, const vcsegptridx_t oldsegnum, const unsigned recursion_count, visited_segment_bitarray_t &visited) |
||
| 606 | { |
||
| 607 | int centermask; |
||
| 608 | std::array<fix, 6> side_dists; |
||
| 609 | fix biggest_val; |
||
| 610 | int sidenum, bit, biggest_side; |
||
| 611 | if (recursion_count >= LevelSharedSegmentState.Num_segments) { |
||
| 612 | con_puts(CON_DEBUG, "trace_segs: Segment not found"); |
||
| 613 | return segment_none; |
||
| 614 | } |
||
| 615 | if (auto &&vs = visited[oldsegnum]) |
||
| 616 | return segment_none; |
||
| 617 | else |
||
| 618 | vs = true; |
||
| 619 | |||
| 620 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 621 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 622 | centermask = get_side_dists(Vertices.vcptr, p0, oldsegnum, side_dists); //check old segment |
||
| 623 | if (centermask == 0) // we are in the old segment |
||
| 624 | return oldsegnum; //..say so |
||
| 625 | |||
| 626 | for (;;) { |
||
| 627 | auto seg = oldsegnum; |
||
| 628 | biggest_side = -1; |
||
| 629 | biggest_val = 0; |
||
| 630 | for (sidenum = 0, bit = 1; sidenum < 6; sidenum++, bit <<= 1) |
||
| 631 | { |
||
| 632 | if ((centermask & bit) && IS_CHILD(seg->children[sidenum]) |
||
| 633 | && side_dists[sidenum] < biggest_val) { |
||
| 634 | biggest_val = side_dists[sidenum]; |
||
| 635 | biggest_side = sidenum; |
||
| 636 | } |
||
| 637 | } |
||
| 638 | |||
| 639 | if (biggest_side == -1) |
||
| 640 | break; |
||
| 641 | |||
| 642 | side_dists[biggest_side] = 0; |
||
| 643 | // trace into adjacent segment: |
||
| 644 | const auto &&check = trace_segs(LevelSharedSegmentState, p0, oldsegnum.absolute_sibling(seg->children[biggest_side]), recursion_count + 1, visited); |
||
| 645 | if (check != segment_none) //we've found a segment |
||
| 646 | return check; |
||
| 647 | } |
||
| 648 | return segment_none; //we haven't found a segment |
||
| 649 | } |
||
| 650 | |||
| 651 | imsegptridx_t find_point_seg(const d_level_shared_segment_state &LevelSharedSegmentState, d_level_unique_segment_state &, const vms_vector &p, const imsegptridx_t segnum) |
||
| 652 | { |
||
| 653 | return segnum.rebind_policy(find_point_seg(LevelSharedSegmentState, p, segnum)); |
||
| 654 | } |
||
| 655 | |||
| 656 | //Tries to find a segment for a point, in the following way: |
||
| 657 | // 1. Check the given segment |
||
| 658 | // 2. Recursively trace through attached segments |
||
| 659 | // 3. Check all the segments |
||
| 660 | //Returns segnum if found, or -1 |
||
| 661 | icsegptridx_t find_point_seg(const d_level_shared_segment_state &LevelSharedSegmentState, const vms_vector &p, const icsegptridx_t segnum) |
||
| 662 | { |
||
| 663 | //allow segnum==-1, meaning we have no idea what segment point is in |
||
| 664 | if (segnum != segment_none) { |
||
| 665 | visited_segment_bitarray_t visited; |
||
| 666 | const auto &&newseg = trace_segs(LevelSharedSegmentState, p, segnum, 0, visited); |
||
| 667 | if (newseg != segment_none) //we found a segment! |
||
| 668 | return newseg; |
||
| 669 | } |
||
| 670 | |||
| 671 | //couldn't find via attached segs, so search all segs |
||
| 672 | |||
| 673 | // MK: 10/15/94 |
||
| 674 | // This Doing_lighting_hack_flag thing added by mk because the hundreds of scrolling messages were |
||
| 675 | // slowing down lighting, and in about 98% of cases, it would just return -1 anyway. |
||
| 676 | // Matt: This really should be fixed, though. We're probably screwing up our lighting in a few places. |
||
| 677 | if (!Doing_lighting_hack_flag) { |
||
| 678 | auto &Segments = LevelSharedSegmentState.get_segments(); |
||
| 679 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 680 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 681 | range_for (const auto &&segp, Segments.vmptridx) |
||
| 682 | { |
||
| 683 | if (get_seg_masks(Vertices.vcptr, p, segp, 0).centermask == 0) |
||
| 684 | return segp; |
||
| 685 | } |
||
| 686 | } |
||
| 687 | return segment_none; |
||
| 688 | } |
||
| 689 | |||
| 690 | |||
| 691 | //--repair-- // ------------------------------------------------------------------------------ |
||
| 692 | //--repair-- void clsd_repair_center(int segnum) |
||
| 693 | //--repair-- { |
||
| 694 | //--repair-- int sidenum; |
||
| 695 | //--repair-- |
||
| 696 | //--repair-- // --- Set repair center bit for all repair center segments. |
||
| 697 | //--repair-- if (Segments[segnum].special == SEGMENT_IS_REPAIRCEN) { |
||
| 698 | //--repair-- Lsegments[segnum].special_type |= SS_REPAIR_CENTER; |
||
| 699 | //--repair-- Lsegments[segnum].special_segment = segnum; |
||
| 700 | //--repair-- } |
||
| 701 | //--repair-- |
||
| 702 | //--repair-- // --- Set repair center bit for all segments adjacent to a repair center. |
||
| 703 | //--repair-- for (sidenum=0; sidenum < MAX_SIDES_PER_SEGMENT; sidenum++) { |
||
| 704 | //--repair-- int s = Segments[segnum].children[sidenum]; |
||
| 705 | //--repair-- |
||
| 706 | //--repair-- if ( (s != -1) && (Segments[s].special==SEGMENT_IS_REPAIRCEN) ) { |
||
| 707 | //--repair-- Lsegments[segnum].special_type |= SS_REPAIR_CENTER; |
||
| 708 | //--repair-- Lsegments[segnum].special_segment = s; |
||
| 709 | //--repair-- } |
||
| 710 | //--repair-- } |
||
| 711 | //--repair-- } |
||
| 712 | |||
| 713 | //--repair-- // ------------------------------------------------------------------------------ |
||
| 714 | //--repair-- // --- Set destination points for all Materialization centers. |
||
| 715 | //--repair-- void clsd_materialization_center(int segnum) |
||
| 716 | //--repair-- { |
||
| 717 | //--repair-- if (Segments[segnum].special == SEGMENT_IS_ROBOTMAKER) { |
||
| 718 | //--repair-- |
||
| 719 | //--repair-- } |
||
| 720 | //--repair-- } |
||
| 721 | //--repair-- |
||
| 722 | //--repair-- int Lsegment_highest_segment_index, Lsegment_highest_vertex_index; |
||
| 723 | //--repair-- |
||
| 724 | //--repair-- // ------------------------------------------------------------------------------ |
||
| 725 | //--repair-- // Create data specific to mine which doesn't get written to disk. |
||
| 726 | //--repair-- // Highest_segment_index and Highest_object_index must be valid. |
||
| 727 | //--repair-- // 07/21: set repair center bit |
||
| 728 | //--repair-- void create_local_segment_data(void) |
||
| 729 | //--repair-- { |
||
| 730 | //--repair-- int segnum; |
||
| 731 | //--repair-- |
||
| 732 | //--repair-- // --- Initialize all Lsegments. |
||
| 733 | //--repair-- for (segnum=0; segnum <= Highest_segment_index; segnum++) { |
||
| 734 | //--repair-- Lsegments[segnum].special_type = 0; |
||
| 735 | //--repair-- Lsegments[segnum].special_segment = -1; |
||
| 736 | //--repair-- } |
||
| 737 | //--repair-- |
||
| 738 | //--repair-- for (segnum=0; segnum <= Highest_segment_index; segnum++) { |
||
| 739 | //--repair-- |
||
| 740 | //--repair-- clsd_repair_center(segnum); |
||
| 741 | //--repair-- clsd_materialization_center(segnum); |
||
| 742 | //--repair-- |
||
| 743 | //--repair-- } |
||
| 744 | //--repair-- |
||
| 745 | //--repair-- // Set check variables. |
||
| 746 | //--repair-- // In main game loop, make sure these are valid, else Lsegments is not valid. |
||
| 747 | //--repair-- Lsegment_highest_segment_index = Highest_segment_index; |
||
| 748 | //--repair-- Lsegment_highest_vertex_index = Highest_vertex_index; |
||
| 749 | //--repair-- } |
||
| 750 | //--repair-- |
||
| 751 | //--repair-- // ------------------------------------------------------------------------------------------ |
||
| 752 | //--repair-- // Sort of makes sure create_local_segment_data has been called for the currently executing mine. |
||
| 753 | //--repair-- // It is not failsafe, as you will see if you look at the code. |
||
| 754 | //--repair-- // Returns 1 if Lsegments appears valid, 0 if not. |
||
| 755 | //--repair-- int check_lsegments_validity(void) |
||
| 756 | //--repair-- { |
||
| 757 | //--repair-- return ((Lsegment_highest_segment_index == Highest_segment_index) && (Lsegment_highest_vertex_index == Highest_vertex_index)); |
||
| 758 | //--repair-- } |
||
| 759 | |||
| 760 | } |
||
| 761 | |||
| 762 | #define MAX_LOC_POINT_SEGS 64 |
||
| 763 | |||
| 764 | namespace dsx { |
||
| 765 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 766 | static inline void add_to_fcd_cache(int seg0, int seg1, int depth, vm_distance dist) |
||
| 767 | { |
||
| 768 | (void)(seg0||seg1||depth||dist); |
||
| 769 | } |
||
| 770 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 771 | #define MIN_CACHE_FCD_DIST (F1_0*80) // Must be this far apart for cache lookup to succeed. Recognizes small changes in distance matter at small distances. |
||
| 772 | #define MAX_FCD_CACHE 8 |
||
| 773 | |||
| 774 | namespace { |
||
| 775 | |||
| 776 | struct fcd_data { |
||
| 777 | segnum_t seg0, seg1; |
||
| 778 | int csd; |
||
| 779 | vm_distance dist; |
||
| 780 | }; |
||
| 781 | |||
| 782 | } |
||
| 783 | |||
| 784 | int Fcd_index = 0; |
||
| 785 | static std::array<fcd_data, MAX_FCD_CACHE> Fcd_cache; |
||
| 786 | fix64 Last_fcd_flush_time; |
||
| 787 | |||
| 788 | // ---------------------------------------------------------------------------------------------------------- |
||
| 789 | void flush_fcd_cache(void) |
||
| 790 | { |
||
| 791 | Fcd_index = 0; |
||
| 792 | |||
| 793 | range_for (auto &i, Fcd_cache) |
||
| 794 | i.seg0 = segment_none; |
||
| 795 | } |
||
| 796 | |||
| 797 | // ---------------------------------------------------------------------------------------------------------- |
||
| 798 | static void add_to_fcd_cache(int seg0, int seg1, int depth, vm_distance dist) |
||
| 799 | { |
||
| 800 | if (dist > MIN_CACHE_FCD_DIST) { |
||
| 801 | Fcd_cache[Fcd_index].seg0 = seg0; |
||
| 802 | Fcd_cache[Fcd_index].seg1 = seg1; |
||
| 803 | Fcd_cache[Fcd_index].csd = depth; |
||
| 804 | Fcd_cache[Fcd_index].dist = dist; |
||
| 805 | |||
| 806 | Fcd_index++; |
||
| 807 | |||
| 808 | if (Fcd_index >= MAX_FCD_CACHE) |
||
| 809 | Fcd_index = 0; |
||
| 810 | } else { |
||
| 811 | // If it's in the cache, remove it. |
||
| 812 | range_for (auto &i, Fcd_cache) |
||
| 813 | if (i.seg0 == seg0) |
||
| 814 | if (i.seg1 == seg1) { |
||
| 815 | Fcd_cache[Fcd_index].seg0 = segment_none; |
||
| 816 | break; |
||
| 817 | } |
||
| 818 | } |
||
| 819 | |||
| 820 | } |
||
| 821 | #endif |
||
| 822 | |||
| 823 | // ---------------------------------------------------------------------------------------------------------- |
||
| 824 | // Determine whether seg0 and seg1 are reachable in a way that allows sound to pass. |
||
| 825 | // Search up to a maximum depth of max_depth. |
||
| 826 | // Return the distance. |
||
| 827 | vm_distance find_connected_distance(const vms_vector &p0, const vcsegptridx_t seg0, const vms_vector &p1, const vcsegptridx_t seg1, int max_depth, WALL_IS_DOORWAY_mask_t wid_flag) |
||
| 828 | { |
||
| 829 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 830 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 831 | segnum_t cur_seg; |
||
| 832 | int qtail = 0, qhead = 0; |
||
| 833 | seg_seg seg_queue[MAX_SEGMENTS]; |
||
| 834 | short depth[MAX_SEGMENTS]; |
||
| 835 | int cur_depth; |
||
| 836 | int num_points; |
||
| 837 | point_seg point_segs[MAX_LOC_POINT_SEGS]; |
||
| 838 | |||
| 839 | // If > this, will overrun point_segs buffer |
||
| 840 | #ifdef WINDOWS |
||
| 841 | if (max_depth == -1) max_depth = 200; |
||
| 842 | #endif |
||
| 843 | |||
| 844 | if (max_depth > MAX_LOC_POINT_SEGS-2) { |
||
| 845 | max_depth = MAX_LOC_POINT_SEGS-2; |
||
| 846 | } |
||
| 847 | |||
| 848 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 849 | auto &vcwallptr = Walls.vcptr; |
||
| 850 | if (seg0 == seg1) { |
||
| 851 | return vm_vec_dist_quick(p0, p1); |
||
| 852 | } else { |
||
| 853 | auto conn_side = find_connect_side(seg0, seg1); |
||
| 854 | if (conn_side != side_none) |
||
| 855 | { |
||
| 856 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 857 | if (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, seg1, conn_side) & wid_flag) |
||
| 858 | #endif |
||
| 859 | { |
||
| 860 | return vm_vec_dist_quick(p0, p1); |
||
| 861 | } |
||
| 862 | } |
||
| 863 | } |
||
| 864 | |||
| 865 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 866 | // Periodically flush cache. |
||
| 867 | if ((GameTime64 - Last_fcd_flush_time > F1_0*2) || (GameTime64 < Last_fcd_flush_time)) { |
||
| 868 | flush_fcd_cache(); |
||
| 869 | Last_fcd_flush_time = GameTime64; |
||
| 870 | } |
||
| 871 | |||
| 872 | else |
||
| 873 | // Can't quickly get distance, so see if in Fcd_cache. |
||
| 874 | range_for (auto &i, Fcd_cache) |
||
| 875 | if (i.seg0 == seg0 && i.seg1 == seg1) |
||
| 876 | { |
||
| 877 | return i.dist; |
||
| 878 | } |
||
| 879 | #endif |
||
| 880 | |||
| 881 | num_points = 0; |
||
| 882 | |||
| 883 | visited_segment_bitarray_t visited; |
||
| 884 | memset(depth, 0, sizeof(depth[0]) * (Highest_segment_index+1)); |
||
| 885 | |||
| 886 | cur_seg = seg0; |
||
| 887 | visited[cur_seg] = true; |
||
| 888 | cur_depth = 0; |
||
| 889 | |||
| 890 | while (cur_seg != seg1) { |
||
| 891 | const cscusegment segp = *vmsegptr(cur_seg); |
||
| 892 | for (int sidenum = 0; sidenum < MAX_SIDES_PER_SEGMENT; sidenum++) { |
||
| 893 | |||
| 894 | int snum = sidenum; |
||
| 895 | |||
| 896 | const auto this_seg = segp.s.children[snum]; |
||
| 897 | if (!IS_CHILD(this_seg)) |
||
| 898 | continue; |
||
| 899 | if (!wid_flag.value || (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, snum) & wid_flag)) |
||
| 900 | { |
||
| 901 | if (!visited[this_seg]) { |
||
| 902 | seg_queue[qtail].start = cur_seg; |
||
| 903 | seg_queue[qtail].end = this_seg; |
||
| 904 | visited[this_seg] = true; |
||
| 905 | depth[qtail++] = cur_depth+1; |
||
| 906 | if (max_depth != -1) { |
||
| 907 | if (depth[qtail-1] == max_depth) { |
||
| 908 | constexpr auto Connected_segment_distance = 1000; |
||
| 909 | add_to_fcd_cache(seg0, seg1, Connected_segment_distance, fcd_abort_cache_value); |
||
| 910 | return fcd_abort_return_value; |
||
| 911 | } |
||
| 912 | } else if (this_seg == seg1) { |
||
| 913 | goto fcd_done1; |
||
| 914 | } |
||
| 915 | } |
||
| 916 | |||
| 917 | } |
||
| 918 | } // for (sidenum... |
||
| 919 | |||
| 920 | if (qhead >= qtail) { |
||
| 921 | constexpr auto Connected_segment_distance = 1000; |
||
| 922 | add_to_fcd_cache(seg0, seg1, Connected_segment_distance, fcd_abort_cache_value); |
||
| 923 | return fcd_abort_return_value; |
||
| 924 | } |
||
| 925 | |||
| 926 | cur_seg = seg_queue[qhead].end; |
||
| 927 | cur_depth = depth[qhead]; |
||
| 928 | qhead++; |
||
| 929 | |||
| 930 | fcd_done1: ; |
||
| 931 | } // while (cur_seg ... |
||
| 932 | |||
| 933 | // Set qtail to the segment which ends at the goal. |
||
| 934 | while (seg_queue[--qtail].end != seg1) |
||
| 935 | if (qtail < 0) { |
||
| 936 | constexpr auto Connected_segment_distance = 1000; |
||
| 937 | add_to_fcd_cache(seg0, seg1, Connected_segment_distance, fcd_abort_cache_value); |
||
| 938 | return fcd_abort_return_value; |
||
| 939 | } |
||
| 940 | |||
| 941 | auto &vcvertptr = Vertices.vcptr; |
||
| 942 | while (qtail >= 0) { |
||
| 943 | segnum_t parent_seg, this_seg; |
||
| 944 | |||
| 945 | this_seg = seg_queue[qtail].end; |
||
| 946 | parent_seg = seg_queue[qtail].start; |
||
| 947 | point_segs[num_points].segnum = this_seg; |
||
| 948 | compute_segment_center(vcvertptr, point_segs[num_points].point, vcsegptr(this_seg)); |
||
| 949 | num_points++; |
||
| 950 | |||
| 951 | if (parent_seg == seg0) |
||
| 952 | break; |
||
| 953 | |||
| 954 | while (seg_queue[--qtail].end != parent_seg) |
||
| 955 | Assert(qtail >= 0); |
||
| 956 | } |
||
| 957 | |||
| 958 | point_segs[num_points].segnum = seg0; |
||
| 959 | compute_segment_center(vcvertptr, point_segs[num_points].point,seg0); |
||
| 960 | num_points++; |
||
| 961 | |||
| 962 | if (num_points == 1) { |
||
| 963 | return vm_vec_dist_quick(p0, p1); |
||
| 964 | } |
||
| 965 | auto dist = vm_vec_dist_quick(p1, point_segs[1].point); |
||
| 966 | dist += vm_vec_dist_quick(p0, point_segs[num_points-2].point); |
||
| 967 | |||
| 968 | for (int i=1; i<num_points-2; i++) { |
||
| 969 | dist += vm_vec_dist_quick(point_segs[i].point, point_segs[i+1].point); |
||
| 970 | } |
||
| 971 | |||
| 972 | add_to_fcd_cache(seg0, seg1, num_points, dist); |
||
| 973 | |||
| 974 | return dist; |
||
| 975 | |||
| 976 | } |
||
| 977 | |||
| 978 | } |
||
| 979 | |||
| 980 | namespace dcx { |
||
| 981 | |||
| 982 | static sbyte convert_to_byte(fix f) |
||
| 983 | { |
||
| 984 | const uint8_t MATRIX_MAX = 0x7f; // This is based on MATRIX_PRECISION, 9 => 0x7f |
||
| 985 | if (f >= 0x00010000) |
||
| 986 | return MATRIX_MAX; |
||
| 987 | else if (f <= -0x00010000) |
||
| 988 | return -MATRIX_MAX; |
||
| 989 | else |
||
| 990 | return f >> MATRIX_PRECISION; |
||
| 991 | } |
||
| 992 | |||
| 993 | } |
||
| 994 | |||
| 995 | #define VEL_PRECISION 12 |
||
| 996 | |||
| 997 | namespace dsx { |
||
| 998 | |||
| 999 | // Create a shortpos struct from an object. |
||
| 1000 | // Extract the matrix into byte values. |
||
| 1001 | // Create a position relative to vertex 0 with 1/256 normal "fix" precision. |
||
| 1002 | // Stuff segment in a short. |
||
| 1003 | void create_shortpos_native(const d_level_shared_segment_state &LevelSharedSegmentState, shortpos &spp, const object_base &objp) |
||
| 1004 | { |
||
| 1005 | auto &vcsegptr = LevelSharedSegmentState.get_segments().vcptr; |
||
| 1006 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 1007 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 1008 | auto &vcvertptr = Vertices.vcptr; |
||
| 1009 | spp.bytemat[0] = convert_to_byte(objp.orient.rvec.x); |
||
| 1010 | spp.bytemat[1] = convert_to_byte(objp.orient.uvec.x); |
||
| 1011 | spp.bytemat[2] = convert_to_byte(objp.orient.fvec.x); |
||
| 1012 | spp.bytemat[3] = convert_to_byte(objp.orient.rvec.y); |
||
| 1013 | spp.bytemat[4] = convert_to_byte(objp.orient.uvec.y); |
||
| 1014 | spp.bytemat[5] = convert_to_byte(objp.orient.fvec.y); |
||
| 1015 | spp.bytemat[6] = convert_to_byte(objp.orient.rvec.z); |
||
| 1016 | spp.bytemat[7] = convert_to_byte(objp.orient.uvec.z); |
||
| 1017 | spp.bytemat[8] = convert_to_byte(objp.orient.fvec.z); |
||
| 1018 | |||
| 1019 | spp.segment = objp.segnum; |
||
| 1020 | const shared_segment &segp = *vcsegptr(objp.segnum); |
||
| 1021 | auto &vert = *vcvertptr(segp.verts[0]); |
||
| 1022 | spp.xo = (objp.pos.x - vert.x) >> RELPOS_PRECISION; |
||
| 1023 | spp.yo = (objp.pos.y - vert.y) >> RELPOS_PRECISION; |
||
| 1024 | spp.zo = (objp.pos.z - vert.z) >> RELPOS_PRECISION; |
||
| 1025 | |||
| 1026 | spp.velx = (objp.mtype.phys_info.velocity.x) >> VEL_PRECISION; |
||
| 1027 | spp.vely = (objp.mtype.phys_info.velocity.y) >> VEL_PRECISION; |
||
| 1028 | spp.velz = (objp.mtype.phys_info.velocity.z) >> VEL_PRECISION; |
||
| 1029 | } |
||
| 1030 | |||
| 1031 | void create_shortpos_little(const d_level_shared_segment_state &LevelSharedSegmentState, shortpos &spp, const object_base &objp) |
||
| 1032 | { |
||
| 1033 | create_shortpos_native(LevelSharedSegmentState, spp, objp); |
||
| 1034 | // swap the short values for the big-endian machines. |
||
| 1035 | |||
| 1036 | if constexpr (words_bigendian) |
||
| 1037 | { |
||
| 1038 | spp.xo = INTEL_SHORT(spp.xo); |
||
| 1039 | spp.yo = INTEL_SHORT(spp.yo); |
||
| 1040 | spp.zo = INTEL_SHORT(spp.zo); |
||
| 1041 | spp.segment = INTEL_SHORT(spp.segment); |
||
| 1042 | spp.velx = INTEL_SHORT(spp.velx); |
||
| 1043 | spp.vely = INTEL_SHORT(spp.vely); |
||
| 1044 | spp.velz = INTEL_SHORT(spp.velz); |
||
| 1045 | } |
||
| 1046 | } |
||
| 1047 | |||
| 1048 | void extract_shortpos_little(const vmobjptridx_t objp, const shortpos *spp) |
||
| 1049 | { |
||
| 1050 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 1051 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 1052 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 1053 | auto &vmobjptr = Objects.vmptr; |
||
| 1054 | auto sp = spp->bytemat.data(); |
||
| 1055 | |||
| 1056 | objp->orient.rvec.x = *sp++ << MATRIX_PRECISION; |
||
| 1057 | objp->orient.uvec.x = *sp++ << MATRIX_PRECISION; |
||
| 1058 | objp->orient.fvec.x = *sp++ << MATRIX_PRECISION; |
||
| 1059 | objp->orient.rvec.y = *sp++ << MATRIX_PRECISION; |
||
| 1060 | objp->orient.uvec.y = *sp++ << MATRIX_PRECISION; |
||
| 1061 | objp->orient.fvec.y = *sp++ << MATRIX_PRECISION; |
||
| 1062 | objp->orient.rvec.z = *sp++ << MATRIX_PRECISION; |
||
| 1063 | objp->orient.uvec.z = *sp++ << MATRIX_PRECISION; |
||
| 1064 | objp->orient.fvec.z = *sp++ << MATRIX_PRECISION; |
||
| 1065 | |||
| 1066 | const segnum_t segnum = INTEL_SHORT(spp->segment); |
||
| 1067 | |||
| 1068 | Assert(segnum <= Highest_segment_index); |
||
| 1069 | |||
| 1070 | const auto &&segp = vmsegptridx(segnum); |
||
| 1071 | auto &vcvertptr = Vertices.vcptr; |
||
| 1072 | auto &vp = *vcvertptr(segp->verts[0]); |
||
| 1073 | objp->pos.x = (INTEL_SHORT(spp->xo) << RELPOS_PRECISION) + vp.x; |
||
| 1074 | objp->pos.y = (INTEL_SHORT(spp->yo) << RELPOS_PRECISION) + vp.y; |
||
| 1075 | objp->pos.z = (INTEL_SHORT(spp->zo) << RELPOS_PRECISION) + vp.z; |
||
| 1076 | |||
| 1077 | objp->mtype.phys_info.velocity.x = (INTEL_SHORT(spp->velx) << VEL_PRECISION); |
||
| 1078 | objp->mtype.phys_info.velocity.y = (INTEL_SHORT(spp->vely) << VEL_PRECISION); |
||
| 1079 | objp->mtype.phys_info.velocity.z = (INTEL_SHORT(spp->velz) << VEL_PRECISION); |
||
| 1080 | |||
| 1081 | obj_relink(vmobjptr, vmsegptr, objp, segp); |
||
| 1082 | } |
||
| 1083 | |||
| 1084 | // create and extract quaternion structure from object data which greatly saves bytes by using quaternion instead or orientation matrix |
||
| 1085 | void create_quaternionpos(quaternionpos &qpp, const object_base &objp) |
||
| 1086 | { |
||
| 1087 | vms_quaternion_from_matrix(qpp.orient, objp.orient); |
||
| 1088 | |||
| 1089 | qpp.pos = objp.pos; |
||
| 1090 | qpp.segment = objp.segnum; |
||
| 1091 | qpp.vel = objp.mtype.phys_info.velocity; |
||
| 1092 | qpp.rotvel = objp.mtype.phys_info.rotvel; |
||
| 1093 | } |
||
| 1094 | |||
| 1095 | void extract_quaternionpos(const vmobjptridx_t objp, quaternionpos &qpp) |
||
| 1096 | { |
||
| 1097 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 1098 | auto &vmobjptr = Objects.vmptr; |
||
| 1099 | vms_matrix_from_quaternion(objp->orient, qpp.orient); |
||
| 1100 | |||
| 1101 | objp->pos = qpp.pos; |
||
| 1102 | objp->mtype.phys_info.velocity = qpp.vel; |
||
| 1103 | objp->mtype.phys_info.rotvel = qpp.rotvel; |
||
| 1104 | |||
| 1105 | const auto segnum = qpp.segment; |
||
| 1106 | Assert(segnum <= Highest_segment_index); |
||
| 1107 | obj_relink(vmobjptr, vmsegptr, objp, vmsegptridx(segnum)); |
||
| 1108 | } |
||
| 1109 | |||
| 1110 | |||
| 1111 | // ----------------------------------------------------------------------------- |
||
| 1112 | // Segment validation functions. |
||
| 1113 | // Moved from editor to game so we can compute surface normals at load time. |
||
| 1114 | // ------------------------------------------------------------------------------- |
||
| 1115 | |||
| 1116 | // ------------------------------------------------------------------------------------------ |
||
| 1117 | // Extract a vector from a segment. The vector goes from the start face to the end face. |
||
| 1118 | // The point on each face is the average of the four points forming the face. |
||
| 1119 | static void extract_vector_from_segment(fvcvertptr &vcvertptr, const shared_segment &sp, vms_vector &vp, const uint_fast32_t istart, const uint_fast32_t iend) |
||
| 1120 | { |
||
| 1121 | vp = {}; |
||
| 1122 | auto &start = Side_to_verts[istart]; |
||
| 1123 | auto &end = Side_to_verts[iend]; |
||
| 1124 | auto &verts = sp.verts; |
||
| 1125 | range_for (const uint_fast32_t i, xrange(4u)) |
||
| 1126 | { |
||
| 1127 | vm_vec_sub2(vp, vcvertptr(verts[start[i]])); |
||
| 1128 | vm_vec_add2(vp, vcvertptr(verts[end[i]])); |
||
| 1129 | } |
||
| 1130 | vm_vec_scale(vp,F1_0/4); |
||
| 1131 | } |
||
| 1132 | |||
| 1133 | //create a matrix that describes the orientation of the given segment |
||
| 1134 | void extract_orient_from_segment(fvcvertptr &vcvertptr, vms_matrix &m, const shared_segment &seg) |
||
| 1135 | { |
||
| 1136 | vms_vector fvec,uvec; |
||
| 1137 | |||
| 1138 | extract_vector_from_segment(vcvertptr, seg, fvec, WFRONT, WBACK); |
||
| 1139 | extract_vector_from_segment(vcvertptr, seg, uvec, WBOTTOM, WTOP); |
||
| 1140 | |||
| 1141 | //vector to matrix does normalizations and orthogonalizations |
||
| 1142 | vm_vector_2_matrix(m, fvec, &uvec, nullptr); |
||
| 1143 | } |
||
| 1144 | |||
| 1145 | #if !DXX_USE_EDITOR |
||
| 1146 | namespace { |
||
| 1147 | #endif |
||
| 1148 | |||
| 1149 | // ------------------------------------------------------------------------------------------ |
||
| 1150 | // Extract the forward vector from segment *sp, return in *vp. |
||
| 1151 | // The forward vector is defined to be the vector from the the center of the front face of the segment |
||
| 1152 | // to the center of the back face of the segment. |
||
| 1153 | void extract_forward_vector_from_segment(fvcvertptr &vcvertptr, const shared_segment &sp, vms_vector &vp) |
||
| 1154 | { |
||
| 1155 | extract_vector_from_segment(vcvertptr, sp, vp, WFRONT, WBACK); |
||
| 1156 | } |
||
| 1157 | |||
| 1158 | // ------------------------------------------------------------------------------------------ |
||
| 1159 | // Extract the right vector from segment *sp, return in *vp. |
||
| 1160 | // The forward vector is defined to be the vector from the the center of the left face of the segment |
||
| 1161 | // to the center of the right face of the segment. |
||
| 1162 | void extract_right_vector_from_segment(fvcvertptr &vcvertptr, const shared_segment &sp, vms_vector &vp) |
||
| 1163 | { |
||
| 1164 | extract_vector_from_segment(vcvertptr, sp, vp, WLEFT, WRIGHT); |
||
| 1165 | } |
||
| 1166 | |||
| 1167 | // ------------------------------------------------------------------------------------------ |
||
| 1168 | // Extract the up vector from segment *sp, return in *vp. |
||
| 1169 | // The forward vector is defined to be the vector from the the center of the bottom face of the segment |
||
| 1170 | // to the center of the top face of the segment. |
||
| 1171 | void extract_up_vector_from_segment(fvcvertptr &vcvertptr, const shared_segment &sp, vms_vector &vp) |
||
| 1172 | { |
||
| 1173 | extract_vector_from_segment(vcvertptr, sp, vp, WBOTTOM, WTOP); |
||
| 1174 | } |
||
| 1175 | |||
| 1176 | #if !DXX_USE_EDITOR |
||
| 1177 | } |
||
| 1178 | #endif |
||
| 1179 | |||
| 1180 | // ---- |
||
| 1181 | // A side is determined to be degenerate if the cross products of 3 consecutive points does not point outward. |
||
| 1182 | __attribute_warn_unused_result |
||
| 1183 | static unsigned check_for_degenerate_side(fvcvertptr &vcvertptr, const shared_segment &sp, const unsigned sidenum) |
||
| 1184 | { |
||
| 1185 | auto &vp = Side_to_verts[sidenum]; |
||
| 1186 | vms_vector vec1, vec2; |
||
| 1187 | fix dot; |
||
| 1188 | int degeneracy_flag = 0; |
||
| 1189 | |||
| 1190 | const auto segc = compute_segment_center(vcvertptr, sp); |
||
| 1191 | const auto &&sidec = compute_center_point_on_side(vcvertptr, sp, sidenum); |
||
| 1192 | const auto vec_to_center = vm_vec_sub(segc, sidec); |
||
| 1193 | |||
| 1194 | //vm_vec_sub(&vec1, &Vertices[sp->verts[vp[1]]], &Vertices[sp->verts[vp[0]]]); |
||
| 1195 | //vm_vec_sub(&vec2, &Vertices[sp->verts[vp[2]]], &Vertices[sp->verts[vp[1]]]); |
||
| 1196 | //vm_vec_normalize(&vec1); |
||
| 1197 | //vm_vec_normalize(&vec2); |
||
| 1198 | const auto vp1 = vp[1]; |
||
| 1199 | const auto vp2 = vp[2]; |
||
| 1200 | auto &vert1 = *vcvertptr(sp.verts[vp1]); |
||
| 1201 | auto &vert2 = *vcvertptr(sp.verts[vp2]); |
||
| 1202 | vm_vec_normalized_dir(vec1, vert1, vcvertptr(sp.verts[vp[0]])); |
||
| 1203 | vm_vec_normalized_dir(vec2, vert2, vert1); |
||
| 1204 | const auto cross0 = vm_vec_cross(vec1, vec2); |
||
| 1205 | |||
| 1206 | dot = vm_vec_dot(vec_to_center, cross0); |
||
| 1207 | if (dot <= 0) |
||
| 1208 | degeneracy_flag |= 1; |
||
| 1209 | |||
| 1210 | //vm_vec_sub(&vec1, &Vertices[sp->verts[vp[2]]], &Vertices[sp->verts[vp[1]]]); |
||
| 1211 | //vm_vec_sub(&vec2, &Vertices[sp->verts[vp[3]]], &Vertices[sp->verts[vp[2]]]); |
||
| 1212 | //vm_vec_normalize(&vec1); |
||
| 1213 | //vm_vec_normalize(&vec2); |
||
| 1214 | vm_vec_normalized_dir(vec1, vert2, vert1); |
||
| 1215 | vm_vec_normalized_dir(vec2, vcvertptr(sp.verts[vp[3]]), vert2); |
||
| 1216 | const auto cross1 = vm_vec_cross(vec1, vec2); |
||
| 1217 | |||
| 1218 | dot = vm_vec_dot(vec_to_center, cross1); |
||
| 1219 | if (dot <= 0) |
||
| 1220 | degeneracy_flag |= 1; |
||
| 1221 | |||
| 1222 | return degeneracy_flag; |
||
| 1223 | } |
||
| 1224 | |||
| 1225 | // ---- |
||
| 1226 | // See if a segment has gotten turned inside out, or something. |
||
| 1227 | // If so, set global Degenerate_segment_found and return 1, else return 0. |
||
| 1228 | static unsigned check_for_degenerate_segment(fvcvertptr &vcvertptr, const shared_segment &sp) |
||
| 1229 | { |
||
| 1230 | vms_vector fvec, rvec, uvec; |
||
| 1231 | fix dot; |
||
| 1232 | int degeneracy_flag = 0; // degeneracy flag for current segment |
||
| 1233 | |||
| 1234 | extract_forward_vector_from_segment(vcvertptr, sp, fvec); |
||
| 1235 | extract_right_vector_from_segment(vcvertptr, sp, rvec); |
||
| 1236 | extract_up_vector_from_segment(vcvertptr, sp, uvec); |
||
| 1237 | |||
| 1238 | vm_vec_normalize(fvec); |
||
| 1239 | vm_vec_normalize(rvec); |
||
| 1240 | vm_vec_normalize(uvec); |
||
| 1241 | |||
| 1242 | const auto cross = vm_vec_cross(fvec, rvec); |
||
| 1243 | dot = vm_vec_dot(cross, uvec); |
||
| 1244 | |||
| 1245 | if (dot > 0) |
||
| 1246 | degeneracy_flag = 0; |
||
| 1247 | else { |
||
| 1248 | degeneracy_flag = 1; |
||
| 1249 | } |
||
| 1250 | |||
| 1251 | // Now, see if degenerate because of any side. |
||
| 1252 | range_for (const uint_fast32_t i, xrange(MAX_SIDES_PER_SEGMENT)) |
||
| 1253 | degeneracy_flag |= check_for_degenerate_side(vcvertptr, sp, i); |
||
| 1254 | |||
| 1255 | #if DXX_USE_EDITOR |
||
| 1256 | Degenerate_segment_found |= degeneracy_flag; |
||
| 1257 | #endif |
||
| 1258 | |||
| 1259 | return degeneracy_flag; |
||
| 1260 | |||
| 1261 | } |
||
| 1262 | |||
| 1263 | static void add_side_as_quad(shared_side &sidep, const vms_vector &normal) |
||
| 1264 | { |
||
| 1265 | sidep.set_type(side_type::quad); |
||
| 1266 | sidep.normals[0] = normal; |
||
| 1267 | sidep.normals[1] = normal; |
||
| 1268 | // If there is a connection here, we only formed the faces for the purpose of determining segment boundaries, |
||
| 1269 | // so don't generate polys, else they will get rendered. |
||
| 1270 | // if (sp->children[sidenum] != -1) |
||
| 1271 | // sidep->render_flag = 0; |
||
| 1272 | // else |
||
| 1273 | // sidep->render_flag = 1; |
||
| 1274 | } |
||
| 1275 | |||
| 1276 | } |
||
| 1277 | |||
| 1278 | namespace dcx { |
||
| 1279 | |||
| 1280 | // ------------------------------------------------------------------------------- |
||
| 1281 | // Return v0, v1, v2 = 3 vertices with smallest numbers. If *negate_flag set, then negate normal after computation. |
||
| 1282 | // Note, you cannot just compute the normal by treating the points in the opposite direction as this introduces |
||
| 1283 | // small differences between normals which should merely be opposites of each other. |
||
| 1284 | static void get_verts_for_normal(verts_for_normal &r, const unsigned va, const unsigned vb, const unsigned vc, const unsigned vd) |
||
| 1285 | { |
||
| 1286 | auto &v = r.vsorted; |
||
| 1287 | std::array<unsigned, 4> w; |
||
| 1288 | |||
| 1289 | // w is a list that shows how things got scrambled so we know if our normal is pointing backwards |
||
| 1290 | range_for (const unsigned i, xrange(4u)) |
||
| 1291 | w[i] = i; |
||
| 1292 | |||
| 1293 | v[0] = va; |
||
| 1294 | v[1] = vb; |
||
| 1295 | v[2] = vc; |
||
| 1296 | v[3] = vd; |
||
| 1297 | |||
| 1298 | range_for (const unsigned i, xrange(1u, 4u)) |
||
| 1299 | range_for (const unsigned j, xrange(i)) |
||
| 1300 | if (v[j] > v[i]) { |
||
| 1301 | using std::swap; |
||
| 1302 | swap(v[j], v[i]); |
||
| 1303 | swap(w[j], w[i]); |
||
| 1304 | } |
||
| 1305 | |||
| 1306 | if (!((v[0] < v[1]) && (v[1] < v[2]) && (v[2] < v[3]))) |
||
| 1307 | LevelError("Level contains malformed geometry."); |
||
| 1308 | |||
| 1309 | // Now, if for any w[i] & w[i+1]: w[i+1] = (w[i]+3)%4, then must swap |
||
| 1310 | r.negate_flag = ((w[0] + 3) % 4) == w[1] || ((w[1] + 3) % 4) == w[2]; |
||
| 1311 | } |
||
| 1312 | |||
| 1313 | static void assign_side_normal(fvcvertptr &vcvertptr, vms_vector &n, const unsigned v0, const unsigned v1, const unsigned v2) |
||
| 1314 | { |
||
| 1315 | verts_for_normal vfn; |
||
| 1316 | get_verts_for_normal(vfn, v0, v1, v2, UINT32_MAX); |
||
| 1317 | const auto &vsorted = vfn.vsorted; |
||
| 1318 | const auto &negate_flag = vfn.negate_flag; |
||
| 1319 | vm_vec_normal(n, vcvertptr(vsorted[0]), vcvertptr(vsorted[1]), vcvertptr(vsorted[2])); |
||
| 1320 | if (negate_flag) |
||
| 1321 | vm_vec_negate(n); |
||
| 1322 | } |
||
| 1323 | |||
| 1324 | } |
||
| 1325 | |||
| 1326 | namespace dsx { |
||
| 1327 | |||
| 1328 | // ------------------------------------------------------------------------------- |
||
| 1329 | static void add_side_as_2_triangles(fvcvertptr &vcvertptr, shared_segment &sp, const unsigned sidenum) |
||
| 1330 | { |
||
| 1331 | auto &vs = Side_to_verts[sidenum]; |
||
| 1332 | fix dot; |
||
| 1333 | |||
| 1334 | const auto sidep = &sp.sides[sidenum]; |
||
| 1335 | |||
| 1336 | // Choose how to triangulate. |
||
| 1337 | // If a wall, then |
||
| 1338 | // Always triangulate so segment is convex. |
||
| 1339 | // Use Matt's formula: Na . AD > 0, where ABCD are vertices on side, a is face formed by A,B,C, Na is normal from face a. |
||
| 1340 | // If not a wall, then triangulate so whatever is on the other side is triangulated the same (ie, between the same absoluate vertices) |
||
| 1341 | if (!IS_CHILD(sp.children[sidenum])) |
||
| 1342 | { |
||
| 1343 | auto &verts = sp.verts; |
||
| 1344 | auto &vvs0 = *vcvertptr(verts[vs[0]]); |
||
| 1345 | auto &vvs1 = *vcvertptr(verts[vs[1]]); |
||
| 1346 | auto &vvs2 = *vcvertptr(verts[vs[2]]); |
||
| 1347 | auto &vvs3 = *vcvertptr(verts[vs[3]]); |
||
| 1348 | const auto &&norm = vm_vec_normal(vvs0, vvs1, vvs2); |
||
| 1349 | const auto &&vec_13 = vm_vec_sub(vvs3, vvs1); // vector from vertex 1 to vertex 3 |
||
| 1350 | dot = vm_vec_dot(norm, vec_13); |
||
| 1351 | |||
| 1352 | const vertex *n0v3, *n1v1; |
||
| 1353 | // Now, signifiy whether to triangulate from 0:2 or 1:3 |
||
| 1354 | sidep->set_type(dot >= 0 ? (n0v3 = &vvs2, n1v1 = &vvs0, side_type::tri_02) : (n0v3 = &vvs3, n1v1 = &vvs1, side_type::tri_13)); |
||
| 1355 | |||
| 1356 | // Now, based on triangulation type, set the normals. |
||
| 1357 | vm_vec_normal(sidep->normals[0], vvs0, vvs1, *n0v3); |
||
| 1358 | vm_vec_normal(sidep->normals[1], *n1v1, vvs2, vvs3); |
||
| 1359 | } else { |
||
| 1360 | std::array<unsigned, 4> v; |
||
| 1361 | |||
| 1362 | range_for (const unsigned i, xrange(4u)) |
||
| 1363 | v[i] = sp.verts[vs[i]]; |
||
| 1364 | |||
| 1365 | verts_for_normal vfn; |
||
| 1366 | get_verts_for_normal(vfn, v[0], v[1], v[2], v[3]); |
||
| 1367 | auto &vsorted = vfn.vsorted; |
||
| 1368 | |||
| 1369 | unsigned s0v2, s1v0; |
||
| 1370 | if ((vsorted[0] == v[0]) || (vsorted[0] == v[2])) { |
||
| 1371 | sidep->set_type(side_type::tri_02); |
||
| 1372 | // Now, get vertices for normal for each triangle based on triangulation type. |
||
| 1373 | s0v2 = v[2]; |
||
| 1374 | s1v0 = v[0]; |
||
| 1375 | } else { |
||
| 1376 | sidep->set_type(side_type::tri_13); |
||
| 1377 | // Now, get vertices for normal for each triangle based on triangulation type. |
||
| 1378 | s0v2 = v[3]; |
||
| 1379 | s1v0 = v[1]; |
||
| 1380 | } |
||
| 1381 | assign_side_normal(vcvertptr, sidep->normals[0], v[0], v[1], s0v2); |
||
| 1382 | assign_side_normal(vcvertptr, sidep->normals[1], s1v0, v[2], v[3]); |
||
| 1383 | } |
||
| 1384 | } |
||
| 1385 | |||
| 1386 | } |
||
| 1387 | |||
| 1388 | namespace dcx { |
||
| 1389 | |||
| 1390 | static int sign(fix v) |
||
| 1391 | { |
||
| 1392 | |||
| 1393 | if (v > PLANE_DIST_TOLERANCE) |
||
| 1394 | return 1; |
||
| 1395 | else if (v < -(PLANE_DIST_TOLERANCE+1)) //neg & pos round differently |
||
| 1396 | return -1; |
||
| 1397 | else |
||
| 1398 | return 0; |
||
| 1399 | } |
||
| 1400 | |||
| 1401 | } |
||
| 1402 | |||
| 1403 | namespace dsx { |
||
| 1404 | |||
| 1405 | #if !DXX_USE_EDITOR |
||
| 1406 | namespace { |
||
| 1407 | #endif |
||
| 1408 | |||
| 1409 | // ------------------------------------------------------------------------------- |
||
| 1410 | void create_walls_on_side(fvcvertptr &vcvertptr, shared_segment &sp, const unsigned sidenum) |
||
| 1411 | { |
||
| 1412 | auto &vs = Side_to_verts[sidenum]; |
||
| 1413 | const auto v0 = sp.verts[vs[0]]; |
||
| 1414 | const auto v1 = sp.verts[vs[1]]; |
||
| 1415 | const auto v2 = sp.verts[vs[2]]; |
||
| 1416 | const auto v3 = sp.verts[vs[3]]; |
||
| 1417 | |||
| 1418 | verts_for_normal vfn; |
||
| 1419 | get_verts_for_normal(vfn, v0, v1, v2, v3); |
||
| 1420 | auto &vm1 = vfn.vsorted[1]; |
||
| 1421 | auto &vm2 = vfn.vsorted[2]; |
||
| 1422 | auto &vm3 = vfn.vsorted[3]; |
||
| 1423 | auto &negate_flag = vfn.negate_flag; |
||
| 1424 | |||
| 1425 | auto &vvm0 = *vcvertptr(vfn.vsorted[0]); |
||
| 1426 | auto &&vn = vm_vec_normal(vvm0, vcvertptr(vm1), vcvertptr(vm2)); |
||
| 1427 | const fix dist_to_plane = abs(vm_dist_to_plane(vcvertptr(vm3), vn, vvm0)); |
||
| 1428 | |||
| 1429 | if (negate_flag) |
||
| 1430 | vm_vec_negate(vn); |
||
| 1431 | |||
| 1432 | auto &s = sp.sides[sidenum]; |
||
| 1433 | if (dist_to_plane > PLANE_DIST_TOLERANCE) |
||
| 1434 | { |
||
| 1435 | add_side_as_2_triangles(vcvertptr, sp, sidenum); |
||
| 1436 | |||
| 1437 | //this code checks to see if we really should be triangulated, and |
||
| 1438 | //de-triangulates if we shouldn't be. |
||
| 1439 | |||
| 1440 | int s0,s1; |
||
| 1441 | |||
| 1442 | const auto v = create_abs_vertex_lists(sp, s, sidenum); |
||
| 1443 | const auto &vertex_list = v.second; |
||
| 1444 | |||
| 1445 | Assert(v.first == 2); |
||
| 1446 | |||
| 1447 | auto &vvn = *vcvertptr(min(vertex_list[0],vertex_list[2])); |
||
| 1448 | |||
| 1449 | const fix dist0 = vm_dist_to_plane(vcvertptr(vertex_list[1]), s.normals[1], vvn); |
||
| 1450 | const fix dist1 = vm_dist_to_plane(vcvertptr(vertex_list[4]), s.normals[0], vvn); |
||
| 1451 | |||
| 1452 | s0 = sign(dist0); |
||
| 1453 | s1 = sign(dist1); |
||
| 1454 | |||
| 1455 | if (!(s0 == 0 || s1 == 0 || s0 != s1)) |
||
| 1456 | return; |
||
| 1457 | //detriangulate! |
||
| 1458 | } |
||
| 1459 | add_side_as_quad(s, vn); |
||
| 1460 | } |
||
| 1461 | |||
| 1462 | // ------------------------------------------------------------------------------- |
||
| 1463 | // Make a just-modified segment side valid. |
||
| 1464 | void validate_segment_side(fvcvertptr &vcvertptr, const vmsegptridx_t sp, const unsigned sidenum) |
||
| 1465 | { |
||
| 1466 | auto &sside = sp->shared_segment::sides[sidenum]; |
||
| 1467 | auto &uside = sp->unique_segment::sides[sidenum]; |
||
| 1468 | create_walls_on_side(vcvertptr, sp, sidenum); |
||
| 1469 | /* |
||
| 1470 | * If the texture was wrong, then fix it and log a diagnostic. For |
||
| 1471 | * builtin missions, log the diagnostic at level CON_VERBOSE, since |
||
| 1472 | * retail levels trigger this during normal play. For external |
||
| 1473 | * missions, log the diagnostic at level CON_URGENT. External |
||
| 1474 | * levels might be fixable by contacting the author, but the retail |
||
| 1475 | * levels can only be fixed by using a Rebirth level patch file (not |
||
| 1476 | * supported yet). When fixing the texture, change it to 0 for |
||
| 1477 | * walls and 1 for non-walls. This should make walls transparent |
||
| 1478 | * for their primary texture; transparent non-walls usually generate |
||
| 1479 | * ugly visual artifacts, so choose a non-zero texture for them. |
||
| 1480 | * |
||
| 1481 | * Known affected retail levels (incomplete list): |
||
| 1482 | |||
| 1483 | Descent 2: Counterstrike |
||
| 1484 | sha256: f1abf516512739c97b43e2e93611a2398fc9f8bc7a014095ebc2b6b2fd21b703 descent2.hog |
||
| 1485 | Levels 1-3: clean |
||
| 1486 | |||
| 1487 | Level #4 |
||
| 1488 | segment #170 side #4 has invalid tmap 910 (NumTextures=910) |
||
| 1489 | segment #171 side #5 has invalid tmap 910 (NumTextures=910) |
||
| 1490 | segment #184 side #2 has invalid tmap 910 (NumTextures=910) |
||
| 1491 | segment #188 side #5 has invalid tmap 910 (NumTextures=910) |
||
| 1492 | |||
| 1493 | Level #5 |
||
| 1494 | segment #141 side #4 has invalid tmap 910 (NumTextures=910) |
||
| 1495 | |||
| 1496 | Level #6 |
||
| 1497 | segment #128 side #4 has invalid tmap 910 (NumTextures=910) |
||
| 1498 | |||
| 1499 | Level #7 |
||
| 1500 | segment #26 side #5 has invalid tmap 910 (NumTextures=910) |
||
| 1501 | segment #28 side #5 has invalid tmap 910 (NumTextures=910) |
||
| 1502 | segment #60 side #5 has invalid tmap 910 (NumTextures=910) |
||
| 1503 | segment #63 side #5 has invalid tmap 910 (NumTextures=910) |
||
| 1504 | segment #161 side #4 has invalid tmap 910 (NumTextures=910) |
||
| 1505 | segment #305 side #4 has invalid tmap 910 (NumTextures=910) |
||
| 1506 | segment #427 side #4 has invalid tmap 910 (NumTextures=910) |
||
| 1507 | segment #533 side #5 has invalid tmap 910 (NumTextures=910) |
||
| 1508 | segment #536 side #4 has invalid tmap 910 (NumTextures=910) |
||
| 1509 | segment #647 side #4 has invalid tmap 910 (NumTextures=910) |
||
| 1510 | segment #648 side #5 has invalid tmap 910 (NumTextures=910) |
||
| 1511 | |||
| 1512 | Level #8 |
||
| 1513 | segment #0 side #4 has invalid tmap 910 (NumTextures=910) |
||
| 1514 | segment #92 side #0 has invalid tmap 910 (NumTextures=910) |
||
| 1515 | segment #92 side #5 has invalid tmap 910 (NumTextures=910) |
||
| 1516 | segment #94 side #1 has invalid tmap 910 (NumTextures=910) |
||
| 1517 | segment #94 side #2 has invalid tmap 910 (NumTextures=910) |
||
| 1518 | segment #95 side #0 has invalid tmap 910 (NumTextures=910) |
||
| 1519 | segment #95 side #1 has invalid tmap 910 (NumTextures=910) |
||
| 1520 | segment #97 side #5 has invalid tmap 910 (NumTextures=910) |
||
| 1521 | segment #98 side #3 has invalid tmap 910 (NumTextures=910) |
||
| 1522 | segment #100 side #1 has invalid tmap 910 (NumTextures=910) |
||
| 1523 | segment #102 side #1 has invalid tmap 910 (NumTextures=910) |
||
| 1524 | segment #104 side #3 has invalid tmap 910 (NumTextures=910) |
||
| 1525 | |||
| 1526 | Levels 9-end: unchecked |
||
| 1527 | |||
| 1528 | */ |
||
| 1529 | const auto old_tmap_num = uside.tmap_num; |
||
| 1530 | if (old_tmap_num >= NumTextures) |
||
| 1531 | uside.tmap_num = ( |
||
| 1532 | LevelErrorV(PLAYING_BUILTIN_MISSION ? CON_VERBOSE : CON_URGENT, "segment #%hu side #%i has invalid tmap %u (NumTextures=%u).", static_cast<segnum_t>(sp), sidenum, old_tmap_num, NumTextures), |
||
| 1533 | (sside.wall_num == wall_none) |
||
| 1534 | ); |
||
| 1535 | |||
| 1536 | // Set render_flag. |
||
| 1537 | // If side doesn't have a child, then render wall. If it does have a child, but there is a temporary |
||
| 1538 | // wall there, then do render wall. |
||
| 1539 | // if (sp->children[sidenum] == -1) |
||
| 1540 | // sp->sides[sidenum].render_flag = 1; |
||
| 1541 | // else if (sp->sides[sidenum].wall_num != -1) |
||
| 1542 | // sp->sides[sidenum].render_flag = 1; |
||
| 1543 | // else |
||
| 1544 | // sp->sides[sidenum].render_flag = 0; |
||
| 1545 | } |
||
| 1546 | |||
| 1547 | // ------------------------------------------------------------------------------- |
||
| 1548 | // Make a just-modified segment valid. |
||
| 1549 | // check all sides to see how many faces they each should have (0,1,2) |
||
| 1550 | // create new vector normals |
||
| 1551 | void validate_segment(fvcvertptr &vcvertptr, const vmsegptridx_t sp) |
||
| 1552 | { |
||
| 1553 | check_for_degenerate_segment(vcvertptr, sp); |
||
| 1554 | |||
| 1555 | for (int side = 0; side < MAX_SIDES_PER_SEGMENT; side++) |
||
| 1556 | validate_segment_side(vcvertptr, sp, side); |
||
| 1557 | } |
||
| 1558 | |||
| 1559 | #if !DXX_USE_EDITOR |
||
| 1560 | } |
||
| 1561 | #endif |
||
| 1562 | |||
| 1563 | // ------------------------------------------------------------------------------- |
||
| 1564 | // Validate all segments. |
||
| 1565 | // Highest_segment_index must be set. |
||
| 1566 | // For all used segments (number <= Highest_segment_index), segnum field must be != -1. |
||
| 1567 | void validate_segment_all(d_level_shared_segment_state &LevelSharedSegmentState) |
||
| 1568 | { |
||
| 1569 | auto &Segments = LevelSharedSegmentState.get_segments(); |
||
| 1570 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 1571 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 1572 | range_for (const auto &&segp, Segments.vmptridx) |
||
| 1573 | { |
||
| 1574 | #if DXX_USE_EDITOR |
||
| 1575 | if (segp->segnum != segment_none) |
||
| 1576 | #endif |
||
| 1577 | validate_segment(Vertices.vcptr, segp); |
||
| 1578 | } |
||
| 1579 | |||
| 1580 | #if DXX_USE_EDITOR |
||
| 1581 | range_for (shared_segment &s, partial_range(Segments, Highest_segment_index + 1, Segments.size())) |
||
| 1582 | s.segnum = segment_none; |
||
| 1583 | #endif |
||
| 1584 | } |
||
| 1585 | |||
| 1586 | |||
| 1587 | // ------------------------------------------------------------------------------------------------------ |
||
| 1588 | // Picks a random point in a segment like so: |
||
| 1589 | // From center, go up to 50% of way towards any of the 8 vertices. |
||
| 1590 | void pick_random_point_in_seg(fvcvertptr &vcvertptr, vms_vector &new_pos, const shared_segment &sp) |
||
| 1591 | { |
||
| 1592 | compute_segment_center(vcvertptr, new_pos, sp); |
||
| 1593 | const unsigned vnum = (d_rand() * MAX_VERTICES_PER_SEGMENT) >> 15; |
||
| 1594 | auto &&vec2 = vm_vec_sub(vcvertptr(sp.verts[vnum]), new_pos); |
||
| 1595 | vm_vec_scale(vec2, d_rand()); // d_rand() always in 0..1/2 |
||
| 1596 | vm_vec_add2(new_pos, vec2); |
||
| 1597 | } |
||
| 1598 | |||
| 1599 | |||
| 1600 | // ---------------------------------------------------------------------------------------------------------- |
||
| 1601 | // Set the segment depth of all segments from start_seg in *segbuf. |
||
| 1602 | // Returns maximum depth value. |
||
| 1603 | unsigned set_segment_depths(vcsegidx_t start_seg, const std::array<uint8_t, MAX_SEGMENTS> *const limit, segment_depth_array_t &depth) |
||
| 1604 | { |
||
| 1605 | std::array<segnum_t, MAX_SEGMENTS> queue; |
||
| 1606 | int head, tail; |
||
| 1607 | |||
| 1608 | head = 0; |
||
| 1609 | tail = 0; |
||
| 1610 | |||
| 1611 | visited_segment_bitarray_t visited; |
||
| 1612 | |||
| 1613 | queue[tail++] = start_seg; |
||
| 1614 | visited[start_seg] = true; |
||
| 1615 | depth[start_seg] = 1; |
||
| 1616 | |||
| 1617 | unsigned parent_depth; |
||
| 1618 | do { |
||
| 1619 | const auto curseg = queue[head++]; |
||
| 1620 | parent_depth = depth[curseg]; |
||
| 1621 | |||
| 1622 | range_for (const auto childnum, vcsegptr(curseg)->children) |
||
| 1623 | { |
||
| 1624 | if (childnum != segment_none && childnum != segment_exit) |
||
| 1625 | if (!limit || (*limit)[childnum]) |
||
| 1626 | { |
||
| 1627 | auto &&v = visited[childnum]; |
||
| 1628 | if (!v) |
||
| 1629 | { |
||
| 1630 | v = true; |
||
| 1631 | depth[childnum] = min(static_cast<unsigned>(std::numeric_limits<segment_depth_array_t::value_type>::max()), parent_depth + 1); |
||
| 1632 | queue[tail++] = childnum; |
||
| 1633 | } |
||
| 1634 | } |
||
| 1635 | } |
||
| 1636 | } while (head < tail); |
||
| 1637 | |||
| 1638 | return parent_depth+1; |
||
| 1639 | } |
||
| 1640 | |||
| 1641 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1642 | //these constants should match the ones in seguvs |
||
| 1643 | #define LIGHT_DISTANCE_THRESHOLD (F1_0*80) |
||
| 1644 | #define Magical_light_constant (F1_0*16) |
||
| 1645 | |||
| 1646 | // ------------------------------------------------------------------------------------------ |
||
| 1647 | //cast static light from a segment to nearby segments |
||
| 1648 | static void apply_light_to_segment(visited_segment_bitarray_t &visited, const vmsegptridx_t segp, const vms_vector &segment_center, const fix light_intensity, const unsigned recursion_depth) |
||
| 1649 | { |
||
| 1650 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 1651 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 1652 | fix dist_to_rseg; |
||
| 1653 | if (auto &&visited_ref = visited[segp]) |
||
| 1654 | { |
||
| 1655 | } |
||
| 1656 | else |
||
| 1657 | { |
||
| 1658 | visited_ref = true; |
||
| 1659 | auto &vcvertptr = Vertices.vcptr; |
||
| 1660 | const auto r_segment_center = compute_segment_center(vcvertptr, segp); |
||
| 1661 | dist_to_rseg = vm_vec_dist_quick(r_segment_center, segment_center); |
||
| 1662 | |||
| 1663 | if (dist_to_rseg <= LIGHT_DISTANCE_THRESHOLD) { |
||
| 1664 | fix light_at_point; |
||
| 1665 | if (dist_to_rseg > F1_0) |
||
| 1666 | light_at_point = fixdiv(Magical_light_constant, dist_to_rseg); |
||
| 1667 | else |
||
| 1668 | light_at_point = Magical_light_constant; |
||
| 1669 | |||
| 1670 | if (light_at_point >= 0) { |
||
| 1671 | light_at_point = fixmul(light_at_point, light_intensity); |
||
| 1672 | #if 0 // don't see the point, static_light can be greater than F1_0 |
||
| 1673 | if (light_at_point >= F1_0) |
||
| 1674 | light_at_point = F1_0-1; |
||
| 1675 | if (light_at_point <= -F1_0) |
||
| 1676 | light_at_point = -(F1_0-1); |
||
| 1677 | #endif |
||
| 1678 | segp->static_light += light_at_point; |
||
| 1679 | if (segp->static_light < 0) // if it went negative, saturate |
||
| 1680 | segp->static_light = 0; |
||
| 1681 | } // end if (light_at_point... |
||
| 1682 | } // end if (dist_to_rseg... |
||
| 1683 | } |
||
| 1684 | |||
| 1685 | if (recursion_depth < 2) |
||
| 1686 | { |
||
| 1687 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 1688 | auto &vcwallptr = Walls.vcptr; |
||
| 1689 | range_for (const int sidenum, xrange(6u)) { |
||
| 1690 | if (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, sidenum) & WID_RENDPAST_FLAG) |
||
| 1691 | apply_light_to_segment(visited, segp.absolute_sibling(segp->children[sidenum]), segment_center, light_intensity, recursion_depth+1); |
||
| 1692 | } |
||
| 1693 | } |
||
| 1694 | } |
||
| 1695 | |||
| 1696 | |||
| 1697 | //update the static_light field in a segment, which is used for object lighting |
||
| 1698 | //this code is copied from the editor routine calim_process_all_lights() |
||
| 1699 | static void change_segment_light(const vmsegptridx_t segp, const unsigned sidenum, const unsigned dir) |
||
| 1700 | { |
||
| 1701 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 1702 | auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; |
||
| 1703 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 1704 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 1705 | auto &vcwallptr = Walls.vcptr; |
||
| 1706 | if (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, sidenum) & WID_RENDER_FLAG) |
||
| 1707 | { |
||
| 1708 | auto &sidep = segp->unique_segment::sides[sidenum]; |
||
| 1709 | fix light_intensity; |
||
| 1710 | |||
| 1711 | light_intensity = TmapInfo[sidep.tmap_num].lighting + TmapInfo[sidep.tmap_num2 & 0x3fff].lighting; |
||
| 1712 | if (light_intensity) { |
||
| 1713 | auto &vcvertptr = Vertices.vcptr; |
||
| 1714 | const auto segment_center = compute_segment_center(vcvertptr, segp); |
||
| 1715 | visited_segment_bitarray_t visited; |
||
| 1716 | apply_light_to_segment(visited, segp, segment_center, light_intensity * dir, 0); |
||
| 1717 | } |
||
| 1718 | } |
||
| 1719 | |||
| 1720 | //this is a horrible hack to get around the horrible hack used to |
||
| 1721 | //smooth lighting values when an object moves between segments |
||
| 1722 | old_viewer = NULL; |
||
| 1723 | |||
| 1724 | } |
||
| 1725 | |||
| 1726 | // ------------------------------------------------------------------------------------------ |
||
| 1727 | // dir = +1 -> add light |
||
| 1728 | // dir = -1 -> subtract light |
||
| 1729 | // dir = 17 -> add 17x light |
||
| 1730 | // dir = 0 -> you are dumb |
||
| 1731 | static void change_light(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, const vmsegptridx_t segnum, const uint8_t sidenum, const int dir) |
||
| 1732 | { |
||
| 1733 | const fix ds = dir * DL_SCALE; |
||
| 1734 | auto &Dl_indices = LevelSharedDestructibleLightState.Dl_indices; |
||
| 1735 | const auto &&pr = cast_range_result<const dl_index &>(Dl_indices.vcptr); |
||
| 1736 | const auto &&er = std::equal_range(pr.begin(), pr.end(), dl_index{segnum, sidenum, 0, 0}); |
||
| 1737 | auto &Delta_lights = LevelSharedDestructibleLightState.Delta_lights; |
||
| 1738 | range_for (auto &i, partial_range_t<const dl_index *>(er.first.base().base(), er.second.base().base())) |
||
| 1739 | { |
||
| 1740 | const uint_fast32_t idx = i.index; |
||
| 1741 | range_for (auto &j, partial_const_range(Delta_lights, idx, idx + i.count)) |
||
| 1742 | { |
||
| 1743 | assert(j.sidenum < MAX_SIDES_PER_SEGMENT); |
||
| 1744 | const auto &&segp = vmsegptr(j.segnum); |
||
| 1745 | auto &uvls = segp->unique_segment::sides[j.sidenum].uvls; |
||
| 1746 | range_for (const int k, xrange(4u)) { |
||
| 1747 | auto &l = uvls[k].l; |
||
| 1748 | const fix dl = ds * j.vert_light[k]; |
||
| 1749 | if ((l += dl) < 0) |
||
| 1750 | l = 0; |
||
| 1751 | } |
||
| 1752 | } |
||
| 1753 | } |
||
| 1754 | |||
| 1755 | //recompute static light for segment |
||
| 1756 | change_segment_light(segnum,sidenum,dir); |
||
| 1757 | } |
||
| 1758 | |||
| 1759 | // Subtract light cast by a light source from all surfaces to which it applies light. |
||
| 1760 | // This is precomputed data, stored at static light application time in the editor (the slow lighting function). |
||
| 1761 | // returns 1 if lights actually subtracted, else 0 |
||
| 1762 | int subtract_light(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, const vmsegptridx_t segnum, const sidenum_fast_t sidenum) |
||
| 1763 | { |
||
| 1764 | if (segnum->light_subtracted & (1 << sidenum)) { |
||
| 1765 | return 0; |
||
| 1766 | } |
||
| 1767 | |||
| 1768 | segnum->light_subtracted |= (1 << sidenum); |
||
| 1769 | change_light(LevelSharedDestructibleLightState, segnum, sidenum, -1); |
||
| 1770 | return 1; |
||
| 1771 | } |
||
| 1772 | |||
| 1773 | // Add light cast by a light source from all surfaces to which it applies light. |
||
| 1774 | // This is precomputed data, stored at static light application time in the editor (the slow lighting function). |
||
| 1775 | // You probably only want to call this after light has been subtracted. |
||
| 1776 | // returns 1 if lights actually added, else 0 |
||
| 1777 | int add_light(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, const vmsegptridx_t segnum, sidenum_fast_t sidenum) |
||
| 1778 | { |
||
| 1779 | if (!(segnum->light_subtracted & (1 << sidenum))) { |
||
| 1780 | return 0; |
||
| 1781 | } |
||
| 1782 | |||
| 1783 | segnum->light_subtracted &= ~(1 << sidenum); |
||
| 1784 | change_light(LevelSharedDestructibleLightState, segnum, sidenum, 1); |
||
| 1785 | return 1; |
||
| 1786 | } |
||
| 1787 | |||
| 1788 | // Parse the Light_subtracted array, turning on or off all lights. |
||
| 1789 | void apply_all_changed_light(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, fvmsegptridx &vmsegptridx) |
||
| 1790 | { |
||
| 1791 | range_for (const auto &&segp, vmsegptridx) |
||
| 1792 | { |
||
| 1793 | for (int j=0; j<MAX_SIDES_PER_SEGMENT; j++) |
||
| 1794 | if (segp->light_subtracted & (1 << j)) |
||
| 1795 | change_light(LevelSharedDestructibleLightState, segp, j, -1); |
||
| 1796 | } |
||
| 1797 | } |
||
| 1798 | |||
| 1799 | // Should call this whenever a new mine gets loaded. |
||
| 1800 | // More specifically, should call this whenever something global happens |
||
| 1801 | // to change the status of static light in the mine. |
||
| 1802 | void clear_light_subtracted(void) |
||
| 1803 | { |
||
| 1804 | range_for (const auto &&segp, vmsegptr) |
||
| 1805 | { |
||
| 1806 | segp->light_subtracted = 0; |
||
| 1807 | } |
||
| 1808 | } |
||
| 1809 | |||
| 1810 | #define AMBIENT_SEGMENT_DEPTH 5 |
||
| 1811 | |||
| 1812 | static void ambient_mark_bfs(const vmsegptridx_t segp, segment_lava_depth_array *segdepth_lava, segment_water_depth_array *segdepth_water, const unsigned depth, const uint_fast8_t s2f_bit) |
||
| 1813 | { |
||
| 1814 | segp->s2_flags |= s2f_bit; |
||
| 1815 | if (segdepth_lava) |
||
| 1816 | { |
||
| 1817 | auto &d = (*segdepth_lava)[segp]; |
||
| 1818 | if (d < depth) |
||
| 1819 | d = depth; |
||
| 1820 | else |
||
| 1821 | segdepth_lava = nullptr; |
||
| 1822 | } |
||
| 1823 | if (segdepth_water) |
||
| 1824 | { |
||
| 1825 | auto &d = (*segdepth_water)[segp]; |
||
| 1826 | if (d < depth) |
||
| 1827 | d = depth; |
||
| 1828 | else |
||
| 1829 | segdepth_water = nullptr; |
||
| 1830 | } |
||
| 1831 | if (!segdepth_lava && !segdepth_water) |
||
| 1832 | return; |
||
| 1833 | |||
| 1834 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 1835 | auto &vcwallptr = Walls.vcptr; |
||
| 1836 | for (unsigned i = 0; i < MAX_SIDES_PER_SEGMENT; ++i) |
||
| 1837 | { |
||
| 1838 | const auto child = segp->children[i]; |
||
| 1839 | |||
| 1840 | /* |
||
| 1841 | * No explicit check for IS_CHILD. If !IS_CHILD, then |
||
| 1842 | * WALL_IS_DOORWAY never sets WID_RENDPAST_FLAG. |
||
| 1843 | */ |
||
| 1844 | if (!(WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, i) & WID_RENDPAST_FLAG)) |
||
| 1845 | continue; |
||
| 1846 | ambient_mark_bfs(segp.absolute_sibling(child), segdepth_lava, segdepth_water, depth - 1, s2f_bit); |
||
| 1847 | } |
||
| 1848 | } |
||
| 1849 | |||
| 1850 | // ----------------------------------------------------------------------------- |
||
| 1851 | // Indicate all segments which are within audible range of falling water or lava, |
||
| 1852 | // and so should hear ambient gurgles. |
||
| 1853 | void set_ambient_sound_flags() |
||
| 1854 | { |
||
| 1855 | auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; |
||
| 1856 | range_for (const auto &&segp, vmsegptr) |
||
| 1857 | segp->s2_flags = 0; |
||
| 1858 | // Now, all segments containing ambient lava or water sound makers are flagged. |
||
| 1859 | // Additionally flag all segments which are within range of them. |
||
| 1860 | // Mark all segments which are sources of the sound. |
||
| 1861 | segment_lava_depth_array segdepth_lava{}; |
||
| 1862 | segment_water_depth_array segdepth_water{}; |
||
| 1863 | |||
| 1864 | range_for (const auto &&segp, vmsegptridx) |
||
| 1865 | { |
||
| 1866 | for (unsigned j = 0; j < MAX_SIDES_PER_SEGMENT; ++j) |
||
| 1867 | { |
||
| 1868 | const auto &sside = segp->shared_segment::sides[j]; |
||
| 1869 | const auto &uside = segp->unique_segment::sides[j]; |
||
| 1870 | if (IS_CHILD(segp->children[j]) && sside.wall_num == wall_none) |
||
| 1871 | /* If this side is open and there is no wall defined, |
||
| 1872 | * then the texture is never visible to the player. |
||
| 1873 | * This happens normally in some level editors if the |
||
| 1874 | * texture is not cleared when the child segment is |
||
| 1875 | * added. Skip this side. |
||
| 1876 | */ |
||
| 1877 | continue; |
||
| 1878 | const auto texture_flags = TmapInfo[uside.tmap_num].flags | TmapInfo[uside.tmap_num2 & 0x3fff].flags; |
||
| 1879 | /* These variables do not need to be named, but naming them |
||
| 1880 | * is the easiest way to establish sequence points, so that |
||
| 1881 | * `sound_flag` is passed to `ambient_mark_bfs` only after |
||
| 1882 | * both ternary expressions have finished. |
||
| 1883 | */ |
||
| 1884 | uint8_t sound_flag = 0; |
||
| 1885 | const auto pl = (texture_flags & TMI_VOLATILE) ? (sound_flag |= S2F_AMBIENT_LAVA, &segdepth_lava) : nullptr; |
||
| 1886 | const auto pw = (texture_flags & TMI_WATER) ? (sound_flag |= S2F_AMBIENT_WATER, &segdepth_water) : nullptr; |
||
| 1887 | if (sound_flag) |
||
| 1888 | ambient_mark_bfs(segp, pl, pw, AMBIENT_SEGMENT_DEPTH, sound_flag); |
||
| 1889 | } |
||
| 1890 | } |
||
| 1891 | } |
||
| 1892 | #endif |
||
| 1893 | } |