Subversion Repositories Games.Descent

Rev

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
}