-
Notifications
You must be signed in to change notification settings - Fork 96
/
building_room_item_draw.cpp
2795 lines (2587 loc) · 149 KB
/
building_room_item_draw.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// 3D World - Building Interior Room Item Drawing
// by Frank Gennari 4/17/21
#include "3DWorld.h"
#include "function_registry.h"
#include "buildings.h"
#include "city.h" // for object_model_loader_t
#include "subdiv.h" // for sd_sphere_d
#include "profiler.h"
#include "openal_wrap.h"
bool const DEBUG_AI_COLLIDERS = 0;
unsigned room_geom_mem(0);
quad_batch_draw candle_qbd;
vect_room_object_t pending_objs;
object_model_loader_t building_obj_model_loader;
extern bool camera_in_building, player_in_tunnel, building_alarm_active;
extern int display_mode, frame_counter, animate2, player_in_basement, player_in_elevator;
extern unsigned room_mirror_ref_tid;
extern float fticks, office_chair_rot_rate, building_ambient_scale;
extern point actual_player_pos, player_candle_pos, pre_reflect_camera_pos_bs;
extern vector4d clip_plane;
extern colorRGB cur_diffuse, cur_ambient;
extern cube_t smap_light_clip_cube;
extern pos_dir_up camera_pdu;
extern building_t const *player_building;
extern carried_item_t player_held_object;
extern building_params_t global_building_params;
unsigned get_num_screenshot_tids();
tid_nm_pair_t get_phone_tex(room_object_t const &c);
template< typename T > void gen_quad_ixs(vector<T> &ixs, unsigned size, unsigned ix_offset);
void draw_emissive_billboards(quad_batch_draw &qbd, int tid);
void draw_car_in_pspace(car_t &car, shader_t &s, vector3d const &xlate, bool shadow_only);
void set_car_model_color(car_t &car);
bldg_obj_type_t get_taken_obj_type(room_object_t const &obj);
int get_toilet_paper_nm_id();
void setup_monitor_screen_draw(room_object_t const &monitor, rgeom_mat_t &mat, std::string &onscreen_text);
void add_tv_or_monitor_screen(room_object_t const &c, rgeom_mat_t &mat, std::string const &onscreen_text, rgeom_mat_t *text_mat);
bool check_clock_time();
bool have_fish_model();
void register_fishtank(room_object_t const &obj, bool is_visible);
void end_fish_draw(shader_t &s, bool inc_pools_and_fb);
void calc_cur_ambient_diffuse();
void reset_interior_lighting_and_end_shader(shader_t &s);
bool has_key_3d_model () {return building_obj_model_loader.is_model_valid(OBJ_MODEL_KEY);}
bool has_office_chair_model() {return building_obj_model_loader.is_model_valid(OBJ_MODEL_OFFICE_CHAIR);}
colorRGBA room_object_t::get_model_color() const {return building_obj_model_loader.get_avg_color(get_model_id());}
// skip_faces: 1=Z1, 2=Z2, 4=Y1, 8=Y2, 16=X1, 32=X2 to match CSG cube flags
void rgeom_mat_t::add_cube_to_verts(cube_t const &c, colorRGBA const &color, point const &tex_origin, unsigned skip_faces,
bool swap_tex_st, bool mirror_x, bool mirror_y, bool inverted, bool z_dim_uses_ty, float tx_add, float ty_add)
{
//assert(c.is_normalized()); // no, bathroom window is denormalized
vertex_t v;
v.set_c4(color);
tx_add += tex.txoff;
ty_add += tex.tyoff;
// Note: stolen from draw_cube() with tex coord logic, back face culling, etc. removed
for (unsigned i = 0; i < 3; ++i) { // iterate over dimensions, drawn as {Z, X, Y}
unsigned const d[2] = {i, ((i+1)%3)}, n((i+2)%3);
bool const tex_st(swap_tex_st ^ (z_dim_uses_ty && (d[1] == 2)));
for (unsigned j = 0; j < 2; ++j) { // iterate over opposing sides, min then max
if (skip_faces & (1 << (2*(2-n) + j))) continue; // skip this face
v.set_ortho_norm(n, (bool(j) ^ inverted));
v.v[n] = c.d[n][j];
for (unsigned s1 = 0; s1 < 2; ++s1) {
v.v[d[1]] = c.d[d[1]][s1];
v.t[tex_st] = ((tex.tscale_x == 0.0) ? float(s1) : (tex.tscale_x*(v.v[d[1]] - tex_origin[d[1]]) + tx_add)); // tscale==0.0 => fit texture to cube
for (unsigned k = 0; k < 2; ++k) { // iterate over vertices
bool const s2(bool(k^j^s1)^inverted^1); // need to orient the vertices differently for each side
v.v[d[0]] = c.d[d[0]][s2];
v.t[!tex_st] = ((tex.tscale_y == 0.0) ? float(s2) : (tex.tscale_y*(v.v[d[0]] - tex_origin[d[0]]) + ty_add));
quad_verts.push_back(v);
if (mirror_x) {quad_verts.back().t[0] = 1.0 - v.t[0];} // use for pictures and books
if (mirror_y) {quad_verts.back().t[1] = 1.0 - v.t[1];} // used for books
} // for k
} // for s1
} // for j
} // for i
}
// untextured version of the above function
void rgeom_mat_t::add_cube_to_verts_untextured(cube_t const &c, colorRGBA const &color, unsigned skip_faces) { // add an inverted flag?
vertex_t v;
v.set_c4(color);
for (unsigned i = 0; i < 3; ++i) { // iterate over dimensions
unsigned const d[2] = {i, ((i+1)%3)}, n((i+2)%3);
for (unsigned j = 0; j < 2; ++j) { // iterate over opposing sides, min then max
if (skip_faces & (1 << (2*(2-n) + j))) continue; // skip this face
v.set_ortho_norm(n, j);
v.v[n] = c.d[n][j];
for (unsigned s1 = 0; s1 < 2; ++s1) {
v.v[d[1]] = c.d[d[1]][s1];
v.v[d[0]] = c.d[d[0]][!(j^s1)]; quad_verts.push_back(v);
v.v[d[0]] = c.d[d[0]][ (j^s1)]; quad_verts.push_back(v);
} // for s1
} // for j
} // for i
}
template<typename T> void add_inverted_triangles(T &verts, vector<unsigned> &indices, unsigned verts_start, unsigned ixs_start) {
unsigned const verts_end(verts.size()), numv(verts_end - verts_start);
verts.resize(verts_end + numv);
for (unsigned i = verts_start; i < verts_end; ++i) {
verts[i+numv] = verts[i];
verts[i+numv].invert_normal();
}
unsigned const ixs_end(indices.size()), numi(ixs_end - ixs_start);
indices.resize(ixs_end + numi);
for (unsigned i = 0; i < numi; ++i) {indices[ixs_end + i] = (indices[ixs_end - i - 1] + numv);} // copy in reverse order
}
void apply_half_or_quarter(int half_or_quarter, unsigned &s_end) { // 0=full circle, 1=half circle, 2=quarter circle, 3=half a full circle in the other dim
if (half_or_quarter == 0) {} // full
else if (half_or_quarter == 1) {s_end /= 2;} // half
else if (half_or_quarter == 2) {s_end /= 4;} // quarter
else if (half_or_quarter == 3) {} // half in other dim - not handled here
else {assert(0);}
}
void rgeom_mat_t::add_ortho_cylin_to_verts(cube_t const &c, colorRGBA const &color, int dim, bool draw_bot, bool draw_top, bool two_sided,
bool inv_tb, float rs_bot, float rs_top, float side_tscale, float end_tscale, bool skip_sides, unsigned ndiv, float side_tscale_add,
bool swap_txy, float len_tc2, float len_tc1, int half_or_quarter)
{
if (dim == 2) { // Z: this is our standard v_cylinder
add_vcylin_to_verts(c, color, draw_bot, draw_top, two_sided, inv_tb, rs_bot, rs_top, side_tscale, end_tscale, skip_sides,
ndiv, side_tscale_add, swap_txy, len_tc2, len_tc1, half_or_quarter);
return;
}
cube_t c_rot(c);
c_rot.swap_dims(2, dim);
unsigned const itri_verts_start_ix(itri_verts.size()), ixs_start_ix(indices.size());
add_vcylin_to_verts(c_rot, color, draw_bot, draw_top, two_sided, inv_tb, rs_bot, rs_top, side_tscale, end_tscale, skip_sides,
ndiv, side_tscale_add, swap_txy, len_tc2, len_tc1, half_or_quarter);
for (auto v = itri_verts.begin()+itri_verts_start_ix; v != itri_verts.end(); ++v) {v->swap_dims(2, dim);} // swap triangle vertices and normals
std::reverse(indices.begin()+ixs_start_ix, indices.end()); // fix winding order
}
void rgeom_mat_t::add_vcylin_to_verts(cube_t const &c, colorRGBA const &color, bool draw_bot, bool draw_top, bool two_sided,
bool inv_tb, float rs_bot, float rs_top, float side_tscale, float end_tscale, bool skip_sides, unsigned ndiv, float side_tscale_add,
bool swap_txy, float len_tc2, float len_tc1, int half_or_quarter)
{
point const center(c.get_cube_center());
float const radius(0.5*min(c.dx(), c.dy())); // cube X/Y size should be equal/square
add_cylin_to_verts(point(center.x, center.y, c.z1()), point(center.x, center.y, c.z2()), radius*rs_bot, radius*rs_top,
color, draw_bot, draw_top, two_sided, inv_tb, side_tscale, end_tscale, skip_sides, ndiv, side_tscale_add, swap_txy, len_tc2, len_tc1, half_or_quarter);
}
void rgeom_mat_t::add_vcylin_to_verts_tscale(cube_t const &c, colorRGBA const &color, bool draw_bot, bool draw_top) {
vector3d const sz(c.get_size());
// make side_tscale an exact multiple of 1.0 so that there are no seams
float const side_tscale(round_fp(max(1.0, tex.tscale_x*0.5*PI*(sz.x + sz.y)))), len_tscale(tex.tscale_y*sz.z), end_tscale(0.25*(tex.tscale_x*sz.x + tex.tscale_y*sz.y));
add_vcylin_to_verts(c, color, draw_bot, draw_top, 0, 0, 1.0, 1.0, side_tscale, end_tscale, 0, N_CYL_SIDES, 0.0, 0, len_tscale);
}
void rgeom_mat_t::add_cylin_to_verts(point const &bot, point const &top, float bot_radius, float top_radius, colorRGBA const &color,
bool draw_bot, bool draw_top, bool two_sided, bool inv_tb, float side_tscale, float end_tscale, bool skip_sides, unsigned ndiv,
float side_tscale_add, bool swap_txy, float len_tc2, float len_tc1, int half_or_quarter)
{
assert((!skip_sides) || draw_bot || draw_top); // must draw something
point const ce[2] = {bot, top};
float const ndiv_inv(1.0/ndiv), half_end_tscale(0.5*end_tscale);
vector3d v12;
vector_point_norm const &vpn(gen_cylinder_data(ce, bot_radius, top_radius, ndiv, v12));
color_wrapper const cw(color);
unsigned itris_start(itri_verts.size()), ixs_start(indices.size()), itix(itris_start), iix(ixs_start);
if (!skip_sides) {
unsigned const ixs_off[6] = {1,2,0, 3,2,1}; // 1 quad = 2 triangles
bool const flat_sides(ndiv <= 6 && side_tscale == 0.0); // hack to draw bolts untextured with flat sides, since no other cylinders have only 6 sides
unsigned ndiv_draw(ndiv);
assert(half_or_quarter <= 2); // no half-in-other-dim
apply_half_or_quarter(half_or_quarter, ndiv_draw);
unsigned const num_side_verts(flat_sides ? 4*ndiv_draw : 2*(ndiv_draw+1)), unique_verts_per_side(flat_sides ? 4 : 2);
itri_verts.resize(itris_start + num_side_verts);
indices.resize(ixs_start + 6*ndiv_draw);
if (flat_sides) {
for (unsigned i = 0; i < ndiv_draw; ++i) { // vertex data
unsigned const in((i+1)%ndiv);
point const pts[4] = {vpn.p[(i<<1)+0], vpn.p[(i<<1)+1], vpn.p[(in<<1)+0], vpn.p[(in<<1)+1]};
norm_comp const normal(get_poly_norm(pts));
for (unsigned n = 0; n < 4; ++n) {itri_verts[itix++].assign(pts[n], normal, 0.0, 0.0, cw);} // all tcs=0
}
}
else {
for (unsigned i = 0; i <= ndiv_draw; ++i) { // vertex data
unsigned const s(i%ndiv);
float const ts(side_tscale*(1.0f - i*ndiv_inv) + side_tscale_add);
norm_comp const normal(0.5*(vpn.n[s] + vpn.n[(i+ndiv-1)%ndiv])); // normalize?
itri_verts[itix++].assign(vpn.p[(s<<1)+0], normal, (swap_txy ? len_tc1 : ts), (swap_txy ? ts : len_tc1), cw);
itri_verts[itix++].assign(vpn.p[(s<<1)+1], normal, (swap_txy ? len_tc2 : ts), (swap_txy ? ts : len_tc2), cw);
}
}
for (unsigned i = 0; i < ndiv_draw; ++i) { // index data
unsigned const ix0(itris_start + unique_verts_per_side*i);
for (unsigned j = 0; j < 6; ++j) {indices[iix++] = ix0 + ixs_off[j];}
}
// room object drawing uses back face culling and single sided lighting; to make lighting two sided, need to add verts with inverted normals/winding dirs
if (two_sided) {add_inverted_triangles(itri_verts, indices, itris_start, ixs_start);}
}
// maybe add top and bottom end cap using triangles, currently using all TCs=0.0
unsigned const num_ends((unsigned)draw_top + (unsigned)draw_bot);
itris_start = itix = itri_verts.size();
ixs_start = iix = indices.size();
itri_verts.resize(itris_start + (ndiv + 1)*num_ends);
indices.resize(ixs_start + 3*ndiv*num_ends);
for (unsigned bt = 0; bt < 2; ++bt) {
if (!(bt ? draw_top : draw_bot)) continue; // this disk not drawn
assert(half_or_quarter == 0); // half and quarter disk are not supported
norm_comp const normal((bool(bt) ^ inv_tb) ? v12 : -v12);
unsigned const center_ix(itix);
itri_verts[itix++].assign(ce[bt], normal, half_end_tscale, half_end_tscale, cw); // center
for (unsigned I = 0; I < ndiv; ++I) {
unsigned const i(bt ? ndiv-I-1 : I); // invert winding order for top face
vector3d const side_normal(0.5*(vpn.n[i] + vpn.n[(i+ndiv-1)%ndiv])); // normalize?
itri_verts[itix++].assign(vpn.p[(i<<1) + bt], normal, half_end_tscale*(side_normal.x + 1.0), half_end_tscale*(side_normal.y + 1.0), cw); // assign tcs from side normal
indices[iix++] = center_ix; // center
indices[iix++] = center_ix + i + 1;
indices[iix++] = center_ix + ((i+1)%ndiv) + 1;
}
} // for bt
if (inv_tb) {std::reverse(indices.begin()+ixs_start, indices.end());} // reverse the order to swap triangle winding order
if (two_sided) {add_inverted_triangles(itri_verts, indices, itris_start, ixs_start);}
}
void rgeom_mat_t::add_disk_to_verts(point const &pos, float radius, vector3d const &dir, colorRGBA const &color, bool swap_txy, bool inv_ts, bool inv_tt) {
assert(radius > 0.0);
color_wrapper const cw(color);
norm_comp const nc(dir);
unsigned const ndiv(N_CYL_SIDES), itris_start(itri_verts.size());
float const css(-1.0*TWO_PI/(float)ndiv), sin_ds(sin(css)), cos_ds(cos(css));
float sin_s(0.0), cos_s(1.0);
vector3d const v1(cross_product(dir, (fabs(dir.x) > fabs(dir.y) ? plus_y : plus_x)).get_norm()), v2(cross_product(dir, v1).get_norm());
itri_verts.emplace_back(pos, nc, 0.5, 0.5, cw);
for (unsigned i = 0; i < ndiv; ++i) {
float const s(sin_s), c(cos_s), ts(0.5*(1.0 + (swap_txy ? c : s))), tt(0.5*(1.0 + (swap_txy ? s : c)));
itri_verts.emplace_back((pos + (radius*s)*v1 + (radius*c)*v2), nc, (inv_ts ? 1.0-ts : ts), (inv_tt ? 1.0-tt : tt), cw);
indices.push_back(itris_start); // center
indices.push_back(itris_start + i + 1);
indices.push_back(itris_start + ((i+1)%ndiv) + 1);
sin_s = s*cos_ds + c*sin_ds;
cos_s = c*cos_ds - s*sin_ds;
}
}
// Note: size can be nonuniform in X/Y/Z
void rgeom_mat_t::add_sphere_to_verts(point const ¢er, vector3d const &size, colorRGBA const &color, bool low_detail,
vector3d const &skip_hemi_dir, tex_range_t const &tr, xform_matrix const *const matrix, float ts_add, float tt_add)
{
static vector<vert_norm_tc> cached_verts[2]; // high/low detail, reused across all calls
static vector<vert_norm_comp_tc> cached_vncs [2];
static vector<unsigned> cached_ixs [2];
vector<vert_norm_tc> &verts(cached_verts[low_detail]);
vector<vert_norm_comp_tc> &vncs (cached_vncs [low_detail]);
vector<unsigned> &ixs (cached_ixs [low_detail]);
unsigned const ndiv(get_rgeom_sphere_ndiv(low_detail));
if (verts.empty()) { // not yet created, create and cache verts
sd_sphere_d sd(all_zeros, 1.0, ndiv);
sphere_point_norm spn;
sd.gen_points_norms(spn);
sd.get_quad_points(verts, &ixs);
assert((ixs.size()&3) == 0); // must be a multiple of 4
vncs.resize(verts.size());
for (unsigned i = 0; i < verts.size(); ++i) {vncs[i] = vert_norm_comp_tc(verts[i].v, verts[i].n, verts[i].t[0], verts[i].t[1]);} // vntc => vnctc
}
color_wrapper const cw(color);
unsigned const ioff(itri_verts.size());
float const tscale[2] = {(tr.x2 - tr.x1), (tr.y2 - tr.y1)}; // scale existing [0.0, 1.0] texture coords into the specified range
if (matrix) { // must apply matrix transform to verts and normals and reconstruct norm_comps
for (auto i = verts.begin(); i != verts.end(); ++i) {
point pt(i->v*size);
vector3d normal(i->n);
matrix->apply_to_vector3d(pt); matrix->apply_to_vector3d(normal);
itri_verts.emplace_back((pt + center), normal, (tr.x1 + i->t[0]*tscale[0] + ts_add), (tr.y1 + i->t[1]*tscale[1] + tt_add), cw);
}
}
else if (skip_hemi_dir != zero_vector) { // only draw one hemisphere; assumes skip_hemi_dir is along a primary {x, y, z} axis
// spheres are generated in a circle in the XY plane in the outer loop, and from top to bottom in Z in the inner loop;
// to draw the top half we need to draw/include the "equator" plus the first/top half of each circular band
unsigned const stride(ndiv+1), t_end(ndiv/2), out_stride(t_end + 1), indices_start(indices.size());
bool const inv_hemi(skip_hemi_dir == plus_x || skip_hemi_dir == plus_y || skip_hemi_dir == plus_z);
bool const swap_x(skip_hemi_dir == plus_x || skip_hemi_dir == -plus_x), swap_y(skip_hemi_dir == plus_y || skip_hemi_dir == -plus_y);
assert(vncs.size() == stride*stride);
for (unsigned s = 0; s <= ndiv; ++s) { // XY circular band
for (unsigned t = 0; t <= t_end; ++t) { // vertical slices in Z; start in the middle; assumes ndiv is an even number
auto v(vncs[s*stride + t]); // deep copy
unsigned const ix(itri_verts.size());
// rotate into correct orientation by swapping coordinates and mirroring; must apply to both the vertex and the normal
if (inv_hemi) {v.v.z = -v.v.z; v.n[2] = 255 - v.n[2];}
if (swap_x ) {std::swap(v.v.x, v.v.z); std::swap(v.n[0], v.n[2]);}
if (swap_y ) {std::swap(v.v.y, v.v.z); std::swap(v.n[1], v.n[2]);}
if (swap_x || swap_y) {v.v.z = -v.v.z; v.n[2] = 255 - v.n[2];}
itri_verts.emplace_back((v.v*size + center), v, (tr.x1 + v.t[0]*tscale[0] + ts_add), (tr.y1 + v.t[1]*tscale[1] + tt_add), cw);
if (t == t_end || s == ndiv) continue; // no indices added for last s or t values
unsigned const qixs[4] = {ix, ix+out_stride, ix+out_stride+1, ix+1};
for (unsigned i = 0; i < 6; ++i) {indices.push_back(qixs[quad_to_tris_ixs[i]]);} // quads (2 triangles)
} // for t
} // for s
assert(indices.back() < itri_verts.size());
if (inv_hemi) {std::reverse(indices.begin()+indices_start, indices.end());} // set correct winding order
return;
}
else { // can use vncs (norm_comps)
for (auto i = vncs.begin(); i != vncs.end(); ++i) {
itri_verts.emplace_back((i->v*size + center), *i, (tr.x1 + i->t[0]*tscale[0] + ts_add), (tr.y1 + i->t[1]*tscale[1] + tt_add), cw);
}
}
for (auto i = ixs.begin(); i != ixs.end(); i += 4) { // indices are for quads, but we want triangles, so expand them
indices.push_back(*(i+0) + ioff); indices.push_back(*(i+1) + ioff); indices.push_back(*(i+2) + ioff);
indices.push_back(*(i+3) + ioff); indices.push_back(*(i+0) + ioff); indices.push_back(*(i+2) + ioff);
}
assert(indices.back() < itri_verts.size());
}
void rgeom_mat_t::add_vert_torus_to_verts(point const ¢er, float r_inner, float r_outer, colorRGBA const &color,
float tscale, bool low_detail, int half_or_quarter, float s_offset, unsigned ndivo, unsigned ndivi, float spiral_offset)
{
unsigned const def_ndiv(get_rgeom_sphere_ndiv(low_detail)); // calculate ndiv if not set
if (ndivo == 0) {ndivo = def_ndiv;}
if (ndivi == 0) {ndivi = def_ndiv;}
unsigned s_end(ndivo), t_end(ndivi), sin_cos_off(0);
apply_half_or_quarter(half_or_quarter, s_end);
if (half_or_quarter == 3) {t_end /= 2; sin_cos_off += 3*ndivi/4;} // half of a full circle (+z half)
bool const is_offset(spiral_offset != 0.0);
float const ts_tt(tscale/ndivi), ds(TWO_PI/ndivo), cds(cos(ds)), sds(sin(ds));
vector<float> const &sin_cos(gen_torus_sin_cos_vals(ndivi));
color_wrapper const cw(color);
float zval(0.0);
s_offset *= TWO_PI;
if (is_offset) {spiral_offset /= (ndivo*r_outer);}
for (unsigned s = 0; s < s_end; ++s) { // outer
float const theta(s*ds + s_offset), ct(cos(theta)), st(sin(theta)), ct2(ct*cds - st*sds), st2(st*cds + ct*sds);
point const pos [2] = {point(ct, st, zval), point(ct2, st2, (zval + spiral_offset))};
point const vpos[2] = {(center + pos[0]*r_outer), (center + pos[1]*r_outer)};
unsigned const tri_ix_start(itri_verts.size()), ixs_start(indices.size());
// Note: drawn as one triangle strip
for (unsigned t = 0; t <= t_end; ++t) { // inner
unsigned const t_((t + sin_cos_off) % ndivi);
float const cp(sin_cos[(t_<<1)+0]), sp(sin_cos[(t_<<1)+1]);
for (unsigned i = 0; i < 2; ++i) {
vector3d delta(pos[1-i]*sp); // normal
delta.z += cp;
if (is_offset) {delta.normalize();}
itri_verts.emplace_back((vpos[1-i] + delta*r_inner), delta, ts_tt*(s+1-i), ts_tt*t, cw);
}
} // for t
zval += spiral_offset;
for (unsigned n = 0; n < 3; ++n) {indices.push_back(tri_ix_start + n);} // first triangle
for (unsigned n = tri_ix_start+3; n < itri_verts.size(); ++n) { // each vertex after this creates a new triangle
unsigned const ix1(indices[indices.size()-2]), ix2(indices.back()); // two previous indices
indices.push_back(ix1);
indices.push_back(ix2);
indices.push_back(n); // new triangle index
}
// swap the winding order of every other triangle, stepping in triangle pairs
for (unsigned i = ixs_start; i < indices.size(); i += 6) {std::swap(indices[i+4], indices[i+5]);}
} // for s
}
void rgeom_mat_t::add_contained_vert_torus_to_verts(cube_t const &c, colorRGBA const &color, float tscale, bool low_detail) { // unused
float const r_inner(0.5*c.dz()), r_outer(0.25*(c.dx() + c.dy()) - r_inner);
assert(r_inner > 0.0 && r_outer > 0.0); // cube must be wider than it is tall
add_vert_torus_to_verts(c.get_cube_center(), r_inner, r_outer, color, tscale, low_detail);
}
void rgeom_mat_t::add_ortho_torus_to_verts(point const ¢er, float r_inner, float r_outer, unsigned dim, colorRGBA const &color,
float tscale, bool low_detail, int half_or_quarter, float s_offset, unsigned ndivo, unsigned ndivi, float spiral_offset)
{
assert(dim < 3);
unsigned const verts_start(itri_verts.size()), ixs_start(indices.size());
add_vert_torus_to_verts(all_zeros, r_inner, r_outer, color, tscale, low_detail, half_or_quarter, s_offset, ndivo, ndivi, spiral_offset);
if (dim < 2) { // swap X or Y with Z
for (auto i = itri_verts.begin()+verts_start; i != itri_verts.end(); ++i) {
std::swap(i->v[dim], i->v[2]);
std::swap(i->n[dim], i->n[2]);
}
reverse(indices.begin()+ixs_start, indices.end()); // reverse winding order
}
for (auto i = itri_verts.begin()+verts_start; i != itri_verts.end(); ++i) {i->v += center;}
}
void rgeom_mat_t::add_triangle_to_verts(point const v[3], colorRGBA const &color, bool two_sided, float tscale) {
color_wrapper cw(color);
norm_comp normal(get_poly_norm(v));
float const ts[3] = {0.0, 0.0, tscale}, tt[3] = {0.0, tscale, 0.0}; // hard-coded for now, maybe pass in?
for (unsigned side = 0; side < 2; ++side) {
for (unsigned n = 0; n < 3; ++n) {
indices.push_back(itri_verts.size()); // since we only support indexed triangles, we have to assign each vertex its own index
unsigned const ix(side ? 2-n : n); // reverse order for side=1
itri_verts.emplace_back(v[ix], normal, ts[ix], tt[ix], cw);
}
if (side == 0) {
if (!two_sided) break; // single sided
normal.invert_normal();
}
} // for side
}
void rgeom_mat_t::add_quad_to_verts(point const v[4], colorRGBA const &color, float tscale) { // 4 points must be planar
color_wrapper cw(color);
norm_comp normal(get_poly_norm(v));
unsigned const vix(itri_verts.size());
float const ts[4] = {0.0, tscale, tscale, 0.0}, tt[4] = {0.0, 0.0, tscale, tscale}; // hard-coded for now, maybe pass in?
for (unsigned n = 0; n < 4; ++n) {itri_verts.emplace_back(v[n], normal, ts[n], tt[n], cw);}
for (unsigned n = 0; n < 6; ++n) {indices.push_back(vix + quad_to_tris_ixs[n]);}
}
class rgeom_alloc_t {
deque<rgeom_storage_t> free_list; // one per unique texture ID/material
public:
void alloc_safe(rgeom_storage_t &s) {
#pragma omp critical(rgeom_alloc)
alloc(s);
}
void alloc(rgeom_storage_t &s) { // attempt to use free_list entry to reuse existing capacity
if (free_list.empty()) return; // no pre-alloc
//cout << TXT(free_list.size()) << TXT(free_list.back().get_tot_vert_capacity()) << endl; // total mem usage is 913/1045
// try to find a free list element with the same tex so that we balance out material memory usage/capacity better
for (unsigned i = 0; i < free_list.size(); ++i) {
if (!free_list[i].tex.is_compatible(s.tex)) continue;
s.swap_vectors(free_list[i]); // transfer existing capacity from free list
free_list[i].swap(free_list.back());
free_list.pop_back();
return; // done
}
}
void free(rgeom_storage_t &s) {
s.clear(); // in case the caller didn't clear it
if (s.get_mem_usage() == 0) return; // no memory allocated, no point in adding to the free list
free_list.push_back(rgeom_storage_t(s.tex)); // record tex of incoming element
s.swap_vectors(free_list.back()); // transfer existing capacity to free list; clear capacity from s
}
unsigned get_mem_usage() const {
unsigned mem(free_list.size()*sizeof(rgeom_storage_t));
for (auto i = free_list.begin(); i != free_list.end(); ++i) {
//cout << i->tex.tid << "\t" << i->tex.shadowed << "\t" << (i->quad_verts.capacity() + i->itri_verts.capacity()) << "\t" << i->get_mem_usage() << endl; // TESTING
mem += i->get_mem_usage();
}
return mem;
}
unsigned size() const {return free_list.size();}
};
rgeom_alloc_t rgeom_alloc; // static allocator with free list, shared across all buildings; not thread safe
vbo_cache_t::vbo_cache_entry_t vbo_cache_t::alloc(unsigned size, bool is_index) {
assert(size > 0); // not required, but a good sanity check
auto &e(entries[is_index]);
//if ((v_used % 1000) == 0) {print_stats();} // TESTING
unsigned const max_size(size + size/5); // no more than 20% wasted cap
auto best_fit(e.end());
unsigned target_sz(size);
for (auto i = e.begin(); i != e.end(); ++i) {
target_sz += 8; // increase with every iteration
if (i->size < size || i->size > max_size) continue; // too small or too large
if (best_fit != e.end() && i->size >= best_fit->size) continue; // not the best fit
best_fit = i;
if (i->size < target_sz) break; // close fit, done
}
if (best_fit != e.end()) {
vbo_cache_entry_t const ret(*best_fit); // deep copy
swap(*best_fit, e.back());
e.pop_back();
++v_reuse; s_reuse += ret.size; ++v_used; s_used += ret.size; // Note: s_reuse can overflow
assert(v_free > 0); assert(s_free >= size);
--v_free; s_free -= size;
return ret; // done
} // for i
++v_alloc; s_alloc += size; ++v_used; s_used += size;
return vbo_cache_entry_t(create_vbo(), 0); // create a new VBO
}
void vbo_cache_t::free(unsigned &vbo, unsigned size, bool is_index) {
if (!vbo) return; // nothing allocated
if (size == 0) {delete_and_zero_vbo(vbo); return;} // shouldn't get here?
assert(v_used > 0); assert(s_used >= size);
--v_used; s_used -= size; ++v_free; s_free += size;
entries[is_index].emplace_back(vbo, size);
vbo = 0;
}
void vbo_cache_t::clear() { // unused
for (unsigned d = 0; d < 2; ++d) {
for (vbo_cache_entry_t &entry : entries[d]) {
delete_vbo(entry.vbo);
room_geom_mem -= entry.size;
}
entries[d].clear();
}
}
void vbo_cache_t::print_stats() const {
cout << "VBOs: A " << v_alloc << " U " << v_used << " R " << v_reuse << " F " << v_free
<< " SZ: A " << (s_alloc>>20) << " U " << (s_used>>20) << " R " << (s_reuse>>20) << " F " << (s_free>>20) << endl; // in MB
}
/*static*/ vbo_cache_t rgeom_mat_t::vbo_cache;
void rgeom_storage_t::clear(bool free_memory) {
if (free_memory) {clear_container(quad_verts);} else {quad_verts.clear();}
if (free_memory) {clear_container(itri_verts);} else {itri_verts.clear();}
if (free_memory) {clear_container(indices );} else {indices .clear();}
}
void rgeom_storage_t::swap_vectors(rgeom_storage_t &s) { // Note: doesn't swap tex
quad_verts.swap(s.quad_verts);
itri_verts.swap(s.itri_verts);
indices.swap(s.indices);
}
void rgeom_storage_t::swap(rgeom_storage_t &s) {
swap_vectors(s);
std::swap(tex, s.tex);
}
void rgeom_mat_t::clear() {
clear_vbos();
clear_vectors();
num_verts = num_ixs = 0;
}
void rgeom_mat_t::clear_vbos() {
vbo_cache.free(vao_mgr.vbo, vert_vbo_sz, 0);
vbo_cache.free(vao_mgr.ivbo, ixs_vbo_sz, 1);
vao_mgr.clear_vaos(); // Note: VAOs not reused because they generally won't be used with the same {vbo, ivbo} pair
vert_vbo_sz = ixs_vbo_sz = 0;
}
void rotate_verts(vector<rgeom_mat_t::vertex_t> &verts, building_t const &building) {
point const center(building.bcube.get_cube_center());
for (auto i = verts.begin(); i != verts.end(); ++i) {
building.do_xy_rotate(center, i->v);
vector3d n(i->get_norm());
building.do_xy_rotate_normal(n);
i->set_norm(n);
}
}
void rgeom_mat_t::create_vbo(building_t const &building) {
if (building.is_rotated()) { // rotate all vertices to match the building rotation
rotate_verts(quad_verts, building);
rotate_verts(itri_verts, building);
}
create_vbo_inner();
rgeom_alloc.free(*this); // vertex and index data is no longer needed and can be cleared
}
void rgeom_mat_t::create_vbo_inner() {
assert(itri_verts.empty() == indices.empty());
unsigned const qsz(quad_verts.size()*sizeof(vertex_t)), itsz(itri_verts.size()*sizeof(vertex_t)), tot_verts_sz(qsz + itsz);
// in most cases when num_verts starts out nonzero there is no actual update for this material, but accurately skipping the VBO update is difficult;
// hashing the vertex data is too slow, and simply summing the verts is inaccurate for things like light switch rotations and buildings far from the origin
num_verts = quad_verts.size() + itri_verts.size();
if (num_verts == 0) return; // nothing to do
gen_quad_ixs(indices, 6*(quad_verts.size()/4), itri_verts.size()); // append indices for quad_verts
num_ixs = indices.size();
unsigned const ix_data_sz(num_ixs*sizeof(unsigned));
if (vao_mgr.vbo && tot_verts_sz <= vert_vbo_sz && vao_mgr.ivbo && ix_data_sz <= ixs_vbo_sz) { // reuse previous VBOs
update_indices(vao_mgr.ivbo, indices);
check_bind_vbo(vao_mgr.vbo);
}
else { // create a new VBO
clear_vbos(); // free any existing VBO memory
auto vret(vbo_cache.alloc(tot_verts_sz, 0)); // verts
auto iret(vbo_cache.alloc(ix_data_sz, 1)); // indices
vao_mgr.vbo = vret.vbo;
vao_mgr.ivbo = iret.vbo;
check_bind_vbo(vao_mgr.vbo);
if (vret.size == 0) { // newly created
vert_vbo_sz = tot_verts_sz;
upload_vbo_data(nullptr, tot_verts_sz);
room_geom_mem += tot_verts_sz;
}
else { // existing
vert_vbo_sz = vret.size;
assert(tot_verts_sz <= vert_vbo_sz);
}
if (iret.size == 0) { // newly created
ixs_vbo_sz = ix_data_sz;
upload_to_vbo(vao_mgr.ivbo, indices, 1, 1);
room_geom_mem += indices.size()*sizeof(unsigned);
}
else { // existing
ixs_vbo_sz = iret.size;
assert(ix_data_sz <= ixs_vbo_sz );
update_indices(vao_mgr.ivbo, indices, ix_data_sz);
}
}
if (itsz > 0) {upload_vbo_sub_data(itri_verts.data(), 0, itsz);}
if (qsz > 0) {upload_vbo_sub_data(quad_verts.data(), itsz, qsz );}
bind_vbo(0);
check_gl_error(475);
if (num_verts >= 32) {dir_mask = 63;} // too many verts, assume all orients
else {
dir_mask = 0;
for (unsigned n = 0; n < 2; ++n) {
for (auto const &v : (n ? itri_verts : quad_verts)) {
for (unsigned d = 0; d < 3; ++d) {
if (v.n[d] < 0) {dir_mask |= 1<<(2*d);} else if (v.n[d] > 0) {dir_mask |= 1<<(2*d+1);}
}
}
} // for n
}
// calculate bcube, for use in VFC for the shadow pass;
// only enable for small blocks to reduce runtime overhead, plus they're more likely to be occluded
if (num_verts >= 256) {bcube.set_to_zeros();}
else {
bcube.set_from_point(itri_verts.empty() ? quad_verts.front().v : itri_verts.front().v);
for (auto const &v : itri_verts) {bcube.union_with_pt(v.v);}
for (auto const &v : quad_verts) {bcube.union_with_pt(v.v);}
}
}
bool brg_batch_draw_t::has_ext_geom() const {
for (tile_block_t const &tb : ext_by_tile) {
for (mat_entry_t const &e : tb.to_draw) {
if (!e.mats.empty()) return 1;
}
}
return 0;
}
void brg_batch_draw_t::clear() {
to_draw.clear();
ext_by_tile.clear();
tid_to_first_mat_map.clear();
cur_tile_slot = 0;
}
void brg_batch_draw_t::set_camera_dir_mask(point const &camera_bs, cube_t const &bcube) {
camera_dir_mask = 0;
for (unsigned d = 0; d < 3; ++d) {
if (camera_bs[d] < bcube.d[d][1]) {camera_dir_mask |= 1<<(2*d );}
if (camera_bs[d] > bcube.d[d][0]) {camera_dir_mask |= 1<<(2*d+1);}
}
}
void brg_batch_draw_t::next_tile(cube_t const &bcube) {
for (unsigned i = 0; i < ext_by_tile.size(); ++i) { // try to find an unused slot
if (!ext_by_tile[i].bcube.is_all_zeros()) continue; // already used
ext_by_tile[i].bcube = bcube;
cur_tile_slot = i;
return;
}
cur_tile_slot = ext_by_tile.size();
ext_by_tile.emplace_back(bcube); // create a new slot
}
void brg_batch_draw_t::add_material(rgeom_mat_t const &m, bool is_ext_tile) {
if (is_ext_tile) {assert(cur_tile_slot < ext_by_tile.size());}
vector<mat_entry_t>& dest(is_ext_tile ? ext_by_tile[cur_tile_slot].to_draw : to_draw);
for (auto &i : dest) { // check all existing materials for a matching texture, etc.
if (i.tex.is_compat_ignore_shadowed(m.tex)) {i.mats.push_back(&m); return;} // found existing material
}
dest.emplace_back(m); // add a new material entry
}
void brg_batch_draw_t::draw_and_clear_batch(vector<mat_entry_t> &batch, tid_nm_pair_dstate_t &state) {
for (auto &i : batch) {
if (i.mats.empty()) continue; // empty slot
i.tex.set_gl(state);
for (auto const &m : i.mats) {m->draw_inner(0);} // shadow_only=0
i.tex.unset_gl(state);
i.mats.clear(); // clear mats but not batch
}
}
void brg_batch_draw_t::draw_and_clear(shader_t &s) {
if (to_draw.empty()) return;
tid_nm_pair_dstate_t state(s);
enable_blend(); // needed for rugs, book, and sign text
draw_and_clear_batch(to_draw, state);
disable_blend();
indexed_vao_manager_with_shadow_t::post_render();
}
void brg_batch_draw_t::draw_and_clear_ext_tiles(shader_t &s, vector3d const &xlate) {
if (ext_by_tile.empty()) return;
tid_nm_pair_dstate_t state(s);
enable_blend(); // needed for sign text
for (tile_block_t &tb : ext_by_tile) {
if (tb.to_draw.empty()) continue; // skip empty batches
try_bind_tile_smap_at_point((tb.bcube.get_cube_center() + xlate), s);
draw_and_clear_batch(tb.to_draw, state);
}
disable_blend();
indexed_vao_manager_with_shadow_t::post_render();
}
void brg_batch_draw_t::clear_ext_tiles() {
for (tile_block_t &tb : ext_by_tile) {tb.bcube = cube_t();} // reset for next frame
}
// shadow_only: 0=non-shadow pass, 1=shadow pass, 2=shadow pass with alpha mask texture
void rgeom_mat_t::draw(tid_nm_pair_dstate_t &state, brg_batch_draw_t *bbd, int shadow_only, bool reflection_pass, bool exterior_geom) {
assert(!(exterior_geom && shadow_only)); // exterior geom shadows are not yet supported
if (shadow_only && !en_shadows) return; // shadows not enabled for this material (picture, whiteboard, rug, etc.)
if (shadow_only && tex.emissive == 1.0) return; // assume this is a light source and shouldn't produce shadows
if (reflection_pass && tex.tid == REFLECTION_TEXTURE_ID) return; // don't draw reflections of mirrors as this doesn't work correctly
if (bbd != nullptr && tex.tid == REFLECTION_TEXTURE_ID) return; // only draw mirror reflections for player building (which has a null bbd)
if (num_verts == 0) return; // Note: should only happen when reusing materials and all objects using this material were removed
// VFC test for shadow pass on sparse materials that have their bcubes calculated; only really helps with backrooms;
// here we don't add xlate to bcube because it's the location of a light source that's already in building space, not camera space
if (shadow_only && !bcube.is_all_zeros() && !camera_pdu.cube_visible(bcube)) return;
vao_setup(shadow_only);
// Note: the shadow pass doesn't normally bind textures and set uniforms, so we don't need to combine those calls into batches
if (bbd != nullptr && !shadow_only) { // add to batch draw (optimization)
if (dir_mask > 0 && bbd->camera_dir_mask > 0 && (dir_mask & bbd->camera_dir_mask) == 0) return; // check for visible surfaces
bbd->add_material(*this, exterior_geom);
}
else { // draw this material now
if (shadow_only != 1) {tex.set_gl (state);} // ignores texture scale for now; enable alpha texture for shadow pass
draw_inner(shadow_only);
if (shadow_only != 1) {tex.unset_gl(state);}
}
}
void rgeom_mat_t::pre_draw(int shadow_only) const {
vao_mgr.pre_render(shadow_only != 0);
}
void rgeom_mat_t::draw_geom() const {
glDrawRangeElements(GL_TRIANGLES, 0, num_verts, num_ixs, GL_UNSIGNED_INT, nullptr);
++num_frame_draw_calls;
}
void rgeom_mat_t::draw_inner(int shadow_only) const {
pre_draw(shadow_only);
draw_geom();
}
void rgeom_mat_t::vao_setup(bool shadow_only) {
vao_mgr.create_and_upload(vector<vertex_t>(), vector<unsigned>(), shadow_only, 0, 1); // pass empty vectors because data is already uploaded; dynamic_level=0, setup_pointers=1
}
void rgeom_mat_t::upload_draw_and_clear(tid_nm_pair_dstate_t &state) { // Note: called by draw_interactive_player_obj() and water_draw_t
if (empty()) return; // nothing to do; can this happen?
create_vbo_inner();
draw(state, nullptr, 0, 0, 0); // no brg_batch_draw_t, shadow=reflection=exterior=0
clear();
indexed_vao_manager_with_shadow_t::post_render();
}
void building_materials_t::clear() {
invalidate();
for (iterator m = begin(); m != end(); ++m) {m->clear();}
vector<rgeom_mat_t>::clear();
}
unsigned building_materials_t::count_all_verts() const {
unsigned num_verts(0);
for (const_iterator m = begin(); m != end(); ++m) {num_verts += m->num_verts;}
return num_verts;
}
rgeom_mat_t &building_materials_t::get_material(tid_nm_pair_t const &tex, bool inc_shadows) {
// for now we do a simple linear search because there shouldn't be too many unique materials
for (iterator m = begin(); m != end(); ++m) {
if (!m->tex.is_compatible(tex)) continue;
if (inc_shadows) {m->enable_shadows();} // Note: m->en_shadows should already be set
// tscale diffs don't make new materials; copy tscales from incoming tex; this field may be used locally by the caller, but isn't used for drawing
m->tex.tscale_x = tex.tscale_x; m->tex.tscale_y = tex.tscale_y;
if (m->get_tot_vert_capacity() == 0) {rgeom_alloc.alloc_safe(*m);} // existing but empty entry, allocate capacity from the allocator free list
return *m;
}
emplace_back(tex); // not found, add a new material
if (inc_shadows) {back().enable_shadows();}
rgeom_alloc.alloc_safe(back());
return back();
}
void building_materials_t::create_vbos(building_t const &building) {
for (iterator m = begin(); m != end(); ++m) {m->create_vbo(building);}
valid = 1;
}
void building_materials_t::draw(brg_batch_draw_t *bbd, shader_t &s, int shadow_only, bool reflection_pass, bool exterior_geom) {
if (!valid) return; // pending generation of data, don't draw yet
//highres_timer_t timer("Draw Materials"); // 0.0168
tid_nm_pair_dstate_t state(s);
for (iterator m = begin(); m != end(); ++m) {m->draw(state, bbd, shadow_only, reflection_pass, exterior_geom);}
}
void building_materials_t::upload_draw_and_clear(shader_t &s) {
tid_nm_pair_dstate_t state(s);
for (iterator m = begin(); m != end(); ++m) {m->upload_draw_and_clear(state);}
}
void building_room_geom_t::add_tquad(building_geom_t const &bg, tquad_with_ix_t const &tquad, cube_t const &bcube, tid_nm_pair_t const &tex,
colorRGBA const &color, bool invert_tc_x, bool exclude_frame, bool no_tc)
{
assert(tquad.npts == 4); // quads only, for doors
add_tquad_to_verts(bg, tquad, bcube, tex, color, mats_doors.get_material(tex, 1).quad_verts, invert_tc_x, exclude_frame, no_tc, 1); // inc_shadows=1, no_rotate=1
}
void building_room_geom_t::clear() {
clear_materials();
objs.clear();
light_bcubes.clear();
}
void building_room_geom_t::clear_materials() { // clears all materials
mats_static .clear();
mats_alpha .clear();
mats_small .clear();
mats_text .clear();
mats_amask .clear();
mats_dynamic.clear();
mats_doors .clear();
mats_lights .clear();
mats_detail .clear();
mats_exterior.clear();
mats_ext_detail.clear();
obj_model_insts.clear(); // these are associated with static VBOs
door_handles .clear();
for (unsigned d = 0; d < 2; ++d) {mats_glass[d].clear();}
}
// Note: used for room lighting changes; detail object changes are not supported
void building_room_geom_t::check_invalid_draw_data() {
if (invalidate_mats_mask & (1 << MAT_TYPE_SMALL )) { // small objects
mats_small.invalidate();
mats_amask.invalidate();
mats_text .invalidate(); // Note: for now text is assigned to type MAT_TYPE_SMALL since it's always drawn with small objects
}
if (invalidate_mats_mask & (1 << MAT_TYPE_STATIC )) { // large objects and 3D models
mats_static .invalidate(); // obj_model_insts will also be recreated
mats_alpha .invalidate();
mats_exterior.invalidate(); // not needed since this is immutable?
}
//if (invalidate_mats_mask & (1 << MAT_TYPE_TEXT )) {mats_text .invalidate();} // text objects
if (invalidate_mats_mask & (1 << MAT_TYPE_DYNAMIC )) {mats_dynamic .invalidate();} // dynamic objects
if (invalidate_mats_mask & (1 << MAT_TYPE_DOORS )) {mats_doors .invalidate();} // door_handles will also be created
if (invalidate_mats_mask & (1 << MAT_TYPE_LIGHTS )) {mats_lights .invalidate();}
if (invalidate_mats_mask & (1 << MAT_TYPE_DETAIL )) {mats_detail .invalidate();}
invalidate_mats_mask = 0; // reset for next frame
}
void building_room_geom_t::invalidate_draw_data_for_obj(room_object_t const &obj, bool was_taken) {
if (obj.is_dynamic() || (obj.type == TYPE_BUTTON && obj.in_elevator())) { // elevator buttons are drawn as dynamic objects
update_dynamic_draw_data();
return;
}
bldg_obj_type_t const type(was_taken ? get_taken_obj_type(obj) : get_room_obj_type(obj));
if (type.lg_sm & 2) {invalidate_small_geom ();} // small objects
if (type.lg_sm & 1) {invalidate_static_geom();} // large objects and 3D models
if (type.is_model ) {invalidate_model_geom ();} // model
else if (type.lg_sm == 0) {invalidate_detail_geom();} // detail object
if (obj.type == TYPE_CEIL_FAN) {invalidate_lights_geom();} // invalidate the light on the fan as well
}
// Note: called when adding, removing, or moving objects
void building_room_geom_t::update_draw_state_for_room_object(room_object_t const &obj, building_t &building, bool was_taken) {
invalidate_draw_data_for_obj(obj, was_taken);
//if (type.ai_coll) {building.invalidate_nav_graph();} // removing this object should not affect the AI navigation graph
modified_by_player = 1; // flag so that we avoid re-generating room geom if the player leaves and comes back
}
unsigned building_room_geom_t::get_num_verts() const {
return (mats_static.count_all_verts() +
mats_small.count_all_verts() +
mats_text.count_all_verts() +
mats_detail.count_all_verts() +
mats_dynamic.count_all_verts() +
mats_lights.count_all_verts() +
mats_amask.count_all_verts() +
mats_alpha.count_all_verts() +
mats_doors.count_all_verts() +
mats_exterior.count_all_verts()) +
mats_ext_detail.count_all_verts();
}
building_materials_t &building_room_geom_t::get_building_mat(tid_nm_pair_t const &tex, bool dynamic, unsigned small, bool transparent, bool exterior) {
assert(!(dynamic && exterior));
assert(small <= 2); // 0=mats_static, 1=mats_small, 2=mats_detail
if (transparent) {assert(!small && !dynamic && !exterior);} // transparent objects must be static and can't be small
if (exterior) return (small ? mats_ext_detail : mats_exterior);
if (dynamic) return mats_dynamic;
if (small == 2) return mats_detail;
if (small) return ((tex.tid == FONT_TEXTURE_ID) ? mats_text : mats_small);
return (transparent ? mats_alpha : mats_static);
}
rgeom_mat_t &building_room_geom_t::get_metal_material(bool inc_shadows, bool dynamic, unsigned small, bool exterior, colorRGBA const &spec_color) {
tid_nm_pair_t tex(-1, 1.0, inc_shadows);
tex.set_specular_color(spec_color, 0.8, 60.0);
return get_material(tex, inc_shadows, dynamic, small, 0, exterior);
}
void room_object_t::set_as_bottle(unsigned rand_id, unsigned max_type, bool no_empty, unsigned exclude_mask) {
assert(max_type > 0 && max_type < NUM_BOTTLE_TYPES);
obj_id = (uint16_t)rand_id;
// cycle with a prime number until a valid type is selected; it's up to the caller to not exclude everything and make this infinite loop
while (get_bottle_type() > max_type || ((1 << get_bottle_type()) & exclude_mask)) {obj_id += 13;}
if (no_empty) {obj_id &= 127;} // strip off second empty bit
color = bottle_params[get_bottle_type()].glass_color;
}
void building_room_geom_t::create_static_vbos(building_t const &building) {
float const tscale(2.0/obj_scale);
tid_nm_pair_t const &wall_tex(building.get_material().wall_tex);
static vect_room_object_t rugs;
rugs.clear();
for (auto i = objs.begin(); i != objs.end(); ++i) {
if (!i->is_visible() || i->is_dynamic()) continue; // skip invisible and dynamic objects
if (!i->is_strictly_normalized()) {cout << "Denormalized object of type " << unsigned(i->type) << ": " << i->str() << endl; assert(0);}
assert(i->type < NUM_ROBJ_TYPES);
switch (i->type) {
case TYPE_TABLE: add_table (*i, tscale, 0.1, 0.08); break; // top_dz=10% of height, leg_width=8% of height
case TYPE_CHAIR: add_chair (*i, tscale); break;
case TYPE_STAIR: add_stair (*i, tscale, tex_origin); break;
case TYPE_STAIR_WALL: add_stairs_wall(*i, tex_origin, wall_tex); break;
case TYPE_RUG: rugs.push_back(*i); break; // must be drawn last - save until later
case TYPE_PICTURE: add_picture (*i); break;
case TYPE_WBOARD: add_picture (*i); break;
case TYPE_BOOK: add_book (*i, 1, 0, 0); break; // lg
case TYPE_BCASE: add_bookcase(*i, 1, 0, 0, tscale, 0); break; // lg
case TYPE_WINE_RACK: add_wine_rack(*i, 1, 0, tscale); break;
case TYPE_DESK: add_desk (*i, tscale, 1, 0); break;
case TYPE_RDESK: add_reception_desk(*i, tscale); break;
case TYPE_BED: add_bed (*i, 1, 0, tscale); break;
case TYPE_WINDOW: add_window (*i, tscale); break;
case TYPE_TUB: add_tub_outer (*i); break;
case TYPE_SINK: add_sink_water(*i); break;
case TYPE_TV: case TYPE_MONITOR: add_tv_picture(*i); break;
case TYPE_CUBICLE: add_cubicle (*i, tscale); break;
case TYPE_STALL: add_br_stall(*i); break;
case TYPE_COUNTER: add_counter (*i, tscale, 1, 0); break; // lg
case TYPE_CABINET: add_cabinet (*i, tscale, 1, 0); break; // lg
case TYPE_KSINK: add_counter (*i, tscale, 1, 0); break; // counter with kitchen sink; lg
case TYPE_BRSINK: add_counter (*i, tscale, 1, 0); break; // counter with bathroom sink; lg
case TYPE_PLANT: add_potted_plant(*i, 1, 0); break; // pot only
case TYPE_TREE: add_tree(*i, 1, 0); break; // pot only
case TYPE_DRESSER: case TYPE_NIGHTSTAND: add_dresser(*i, tscale, 1, 0); break;
case TYPE_DRESS_MIR: add_dresser_mirror(*i, tscale); break;
case TYPE_FLOORING:add_flooring(*i, tscale); break;
case TYPE_CLOSET: add_closet (*i, wall_tex, building.get_trim_color(), 1, 0); break; // inc_lg=1, inc_sm=0
case TYPE_MIRROR: add_mirror (*i); break;
case TYPE_SHOWER: add_shower (*i, tscale, 1, 0); break; // inc_lg=1, inc_sm=0
case TYPE_SHOWERTUB: add_shower_tub(*i, wall_tex, building.get_trim_color(), tscale, 1, 0); break; // inc_lg=1, inc_sm=0
case TYPE_BLINDS: add_blinds (*i); break;
case TYPE_FPLACE: add_fireplace(*i, tscale); break;
case TYPE_FCABINET: add_filing_cabinet(*i, 1, 0); break; // lg
case TYPE_SIGN: add_sign (*i, 1, 1, 1); break; // lg, exterior_only=1
case TYPE_PIPE: add_pipe(*i, 1); break; // add_exterior=1
case TYPE_WIND_SILL: add_window_sill (*i); break;
case TYPE_EXT_STEP: add_exterior_step(*i); break;
case TYPE_BALCONY: add_balcony(*i, building.ground_floor_z1, building.is_in_city); break;
case TYPE_FALSE_DOOR: add_false_door(*i); break;
case TYPE_RAILING: if (i->is_exterior()) {add_railing(*i);} break; // exterior only
case TYPE_DOWNSPOUT: add_downspout(*i); break;
case TYPE_SHELFRACK: add_rack(*i, 1, 0); break; // add_rack=1, add_objs=0
case TYPE_CHIM_CAP: add_chimney_cap(*i); break;
case TYPE_LADDER: add_ext_ladder(*i); break;
case TYPE_CHECKOUT: add_checkout(*i, tscale); break;
case TYPE_FISHTANK: add_fishtank(*i); break;
case TYPE_OFF_PILLAR:add_wall_or_pillar(*i, tex_origin, wall_tex); break;
case TYPE_CONF_TABLE:add_conference_table(*i, tscale); break;
case TYPE_INT_WINDOW:add_int_window(*i); break;
case TYPE_BUCKET: add_bucket(*i, 0, 1); break; // draw_metal=0, draw_liquid=1
//case TYPE_FRIDGE: if (i->is_open()) {} break; // draw open fridge?
case TYPE_ELEVATOR: break; // not handled here
case TYPE_BLOCKER: break; // not drawn
case TYPE_COLLIDER: break; // not drawn
default: break;
} // end switch
} // for i
for (escalator_t const &e : building.interior->escalators) {add_escalator(e, building.get_window_vspace(), 1, 0);} // draw_static=1, draw_dynamic=0
add_skylights_details(building);
for (room_object_t &rug : rugs) {add_rug(rug);} // rugs are added last so that alpha blending of their edges works
// Note: verts are temporary, but cubes are needed for things such as collision detection with the player and ray queries for indir lighting
//highres_timer_t timer2("Gen Room Geom VBOs"); // < 2ms
mats_static .create_vbos(building);
mats_alpha .create_vbos(building);
mats_exterior.create_vbos(building); // Note: ideally we want to include window dividers from trim_objs, but that may not have been created yet
//cout << "static: size: " << rgeom_alloc.size() << " mem: " << rgeom_alloc.get_mem_usage() << endl; // start=47MB, peak=132MB
}
void building_room_geom_t::create_small_static_vbos(building_t const &building) {
//highres_timer_t timer("Gen Room Geom Small"); // 7.8ms, slow building at 26,16; up to 36ms on new computer for buildings with large retail areas
float const floor_ceil_gap(building.get_floor_ceil_gap());
model_objs.clear(); // currently model_objs are only created for small objects in drawers, so we clear this here
add_small_static_objs_to_verts(expanded_objs, building.get_trim_color(), 0, floor_ceil_gap); // inc_text=0
add_small_static_objs_to_verts(objs, building.get_trim_color(), 0, floor_ceil_gap); // inc_text=0
add_attic_interior_and_rafters(building, 2.0/obj_scale, 0); // only if there's an attic; detail_pass=0
for (tunnel_seg_t const &t : building.interior->tunnels) {add_tunnel(t);}
}
void building_room_geom_t::add_nested_objs_to_verts(vect_room_object_t const &objs_to_add) {
vector_add_to(objs_to_add, pending_objs); // nested objects are added at the end so that small and text materials are thread safe
}
void building_room_geom_t::add_small_static_objs_to_verts(vect_room_object_t const &objs_to_add, colorRGBA const &trim_color, bool inc_text, float floor_ceil_gap) {
if (objs_to_add.empty()) return; // don't add untextured material, otherwise we may fail the (num_verts > 0) assert
float const tscale(2.0/obj_scale);
for (unsigned i = 0; i < objs_to_add.size(); ++i) { // Note: iterating with indices to avoid invalid ref when add_nested_objs_to_verts() is called
room_object_t const &c(objs_to_add[i]);
if (!c.is_visible() || c.is_dynamic()) continue; // skip invisible and dynamic objects
assert(c.is_strictly_normalized());
assert(c.type < NUM_ROBJ_TYPES);