/
building_basement.cpp
1785 lines (1630 loc) · 106 KB
/
building_basement.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 Basement and Parking Garage Logic
// by Frank Gennari 03/11/2022
#include "3DWorld.h"
#include "function_registry.h"
#include "buildings.h"
#include "city.h" // for car_t
#include <cfloat> // for FLT_MAX
enum {PIPE_TYPE_SEWER=0, PIPE_TYPE_CW, PIPE_TYPE_HW, PIPE_TYPE_GAS, NUM_PIPE_TYPES};
extern city_params_t city_params; // for num_cars
car_t car_from_parking_space(room_object_t const &o);
void subtract_cube_from_floor_ceil(cube_t const &c, vect_cube_t &fs);
bool line_int_cubes_exp(point const &p1, point const &p2, vect_cube_t const &cubes, vector3d const &expand);
bool enable_parked_cars() {return (city_params.num_cars > 0 && !city_params.car_model_files.empty());}
template<typename T> void add_to_and_clear(T &src, T &dest) {
vector_add_to(src, dest);
src.clear();
}
void subtract_cubes_from_cube_split_in_dim(cube_t const &c, vect_cube_t const &sub, vect_cube_t &out, vect_cube_t &out2, unsigned dim) {
out.clear();
out.push_back(c);
for (auto s = sub.begin(); s != sub.end(); ++s) {
if (!c.intersects(c)) continue; // no overlap with orig cube (optimization)
out2.clear();
// clip all of out against *s, write results to out2, then swap with out
for (auto i = out.begin(); i != out.end(); ++i) {
if (!i->intersects(*s)) {out2.push_back(*i); continue;} // no overlap, keep entire cube
if (i->d[dim][0] < s->d[dim][0]) { // lo side
out2.push_back(*i);
out2.back().d[dim][1] = s->d[dim][0];
}
if (i->d[dim][1] > s->d[dim][1]) { // hi side
out2.push_back(*i);
out2.back().d[dim][0] = s->d[dim][1];
}
} // for i
out.swap(out2);
} // for s
}
// *** Utilities ***
unsigned building_t::add_water_heaters(rand_gen_t &rgen, room_t const &room, float zval, unsigned room_id, float tot_light_amt, unsigned objs_start, bool single_only) {
float const floor_spacing(get_window_vspace()), height(get_floor_ceil_gap());
float const radius((is_house ? 0.18 : 0.20)*height); // larger radius for office buildings
cube_t const room_bounds(get_walkable_room_bounds(room));
cube_t place_area(room_bounds);
place_area.expand_by(-(1.05*radius + get_trim_thickness())); // account for the pan
if (!place_area.is_strictly_normalized()) return 0; // too small to place water heater
vect_room_object_t &objs(interior->room_geom->objs);
point center(0.0, 0.0, zval);
bool first_xdir(rgen.rand_bool()), first_ydir(rgen.rand_bool()), first_dim(0);
if (is_house) { // random corner selection
first_dim = rgen.rand_bool();
}
else { // select corners furthest from the door (back wall)
first_dim = (room.dy() < room.dx()); // face the shorter dim for office buildings so that we can place longer rows
for (door_stack_t const &ds : interior->door_stacks) {
if (!ds.is_connected_to_room(room_id)) continue;
bool const dir(room.get_center_dim(ds.dim) < ds.get_center_dim(ds.dim));
(ds.dim ? first_ydir : first_xdir) = !dir; // opposite the door
//first_dim = ds.dim; // place against back wall; too restrictive?
}
}
for (unsigned n = 0; n < 5; ++n) { // make 5 attempts to place a water heater - one in each corner and 1 along a random wall for variety
bool dim(0), dir(0), xdir(0), ydir(0);
if (n < 4) { // corner
dim = (first_dim ^ (n >= 2));
xdir = (first_xdir ^ bool(n & 1));
ydir = (first_ydir ^ bool(n & 1));
dir = (dim ? ydir : xdir);
center.x = place_area.d[0][xdir];
center.y = place_area.d[1][ydir];
}
else { // wall
dim = rgen.rand_bool();
dir = rgen.rand_bool(); // choose a random wall
center[ dim] = place_area.d[dim][dir]; // against this wall
center[!dim] = rgen.rand_uniform(place_area.d[!dim][0], place_area.d[!dim][1]);
}
cube_t c(get_cube_height_radius(center, radius, height));
if (is_obj_placement_blocked(c, room, 1)) continue;
cube_t c_exp(c);
c_exp.expand_by_xy(0.2*radius); // small keepout in XY
c_exp.d[dim][!dir] += (dir ? -1.0 : 1.0)*0.25*radius; // add more keepout in front where the controls are
c_exp.intersect_with_cube(room); // don't pick up objects on the other side of the wall
if (overlaps_other_room_obj(c_exp, objs_start)) continue; // check existing objects, in particular storage room boxes that will have already been placed
unsigned const flags((is_house ? RO_FLAG_IS_HOUSE : 0) | RO_FLAG_INTERIOR);
objs.emplace_back(c, TYPE_WHEATER, room_id, dim, !dir, flags, tot_light_amt, SHAPE_CYLIN);
unsigned num_added(1);
if (is_house && has_attic()) { // add rooftop vent above the water heater
float const attic_floor_zval(get_attic_part().z2()), vent_radius(0.15*radius);
point const vent_bot_center(center.x, center.y, attic_floor_zval);
add_attic_roof_vent(vent_bot_center, vent_radius, room_id, 1.0); // light_amt=1.0; room_id is for the basement because there's no attic room
}
if (!single_only && !is_house && n < 4) { // office building, placed at corner; try to add additional water heaters along the wall
vector3d step;
step[!dim] = 2.2*radius*((dim ? xdir : ydir) ? -1.0 : 1.0); // step in the opposite dim
// ideally we would iterate over all the plumbing fixtures to determine water usage, but they might not be placed yet, so instead count rooms;
// but rooms can be of very different sizes, so maybe counting volume is the best?
float tot_volume(0.0);
for (auto i = parts.begin(); i != get_real_parts_end(); ++i) {tot_volume += i->get_volume();}
tot_volume /= floor_spacing*floor_spacing*floor_spacing;
unsigned const max_wh(max(1, min(5, round_fp(tot_volume/1000.0))));
for (unsigned m = 1; m < max_wh; ++m) { // one has already been placed
c.translate(step);
if (!room_bounds.contains_cube(c)) break; // went outside the room, done
if (is_obj_placement_blocked(c, room, 1)) continue; // bad placement, skip
c_exp.translate(step);
if (overlaps_other_room_obj(c_exp, objs_start)) continue; // check existing objects
objs.emplace_back(c, TYPE_WHEATER, room_id, dim, !dir, flags, tot_light_amt, SHAPE_CYLIN);
++num_added;
} // for m
}
return num_added; // done
} // for n
return 0;
}
bool gen_furnace_cand(cube_t const &place_area, float floor_spacing, bool near_wall, rand_gen_t &rgen, cube_t &furnace, bool &dim, bool &dir) {
// my furnace is 45" tall, 17.5" wide, and 21" deep, with a 9" tall duct below (total height = 54"); assume floor spacing is 96"
float const height(0.563*floor_spacing), hwidth(0.182*height), hdepth(0.219*height), room_min_sz(min(place_area.dx(), place_area.dy()));
if (hdepth > 5.0*room_min_sz || 2.1*(hdepth + building_t::get_scaled_player_radius()) > room_min_sz) return 0; // place area is too small
dim = rgen.rand_bool();
point center;
float const lo(place_area.d[dim][0] + hdepth), hi(place_area.d[dim][1] - hdepth);
if (near_wall) {
dir = rgen.rand_bool();
center[dim] = (dir ? lo : hi); // dir is which way it's facing, not which wall it's against
}
else {
center[dim] = rgen.rand_uniform(lo, hi);
dir = (center[dim] < place_area.get_center_dim(dim)); // face the center of the room (attic or above hallway/stairs)
}
center[!dim] = rgen.rand_uniform(place_area.d[!dim][0]+hwidth, place_area.d[!dim][1]-hwidth);
set_wall_width(furnace, center[ dim], hdepth, dim);
set_wall_width(furnace, center[!dim], hwidth, !dim);
set_cube_zvals(furnace, place_area.z1(), place_area.z1()+height);
return 1;
}
void building_t::add_breaker_panel(rand_gen_t &rgen, cube_t const &c, float ceil_zval, bool dim, bool dir, unsigned room_id, float tot_light_amt) {
assert(has_room_geom());
auto &objs(interior->room_geom->objs);
objs.emplace_back(c, TYPE_BRK_PANEL, room_id, dim, dir, RO_FLAG_INTERIOR, tot_light_amt, SHAPE_CUBE, colorRGBA(0.5, 0.6, 0.7));
set_obj_id(objs);
if (c.z1() < ground_floor_z1) { // add conduit if in the basement
assert(c.z2() < ceil_zval);
cube_t conduit(cube_top_center(c));
conduit.z2() = ceil_zval;
float const bp_hdepth(0.5*c.get_sz_dim(dim)), conduit_radius(rgen.rand_uniform(0.38, 0.46)*bp_hdepth);
conduit.expand_by_xy(conduit_radius);
objs.emplace_back(conduit, TYPE_PIPE, room_id, 0, 1, (RO_FLAG_NOCOLL | RO_FLAG_INTERIOR), tot_light_amt, SHAPE_CYLIN, LT_GRAY); // vertical pipe
}
}
bool connect_furnace_to_breaker_panel(room_object_t const &furnace, cube_t const &bp, bool dim, bool dir, float conn_height, cube_t &conn) {
assert(furnace.type == TYPE_FURNACE);
if (furnace.dim != dim || furnace.dir == dir) return 0; // wrong orient (note that dir is backwards)
if (furnace.d[dim][dir] < bp.d[dim][0] || furnace.d[dim][dir] > bp.d[dim][1]) return 0; // back against the wrong wall (can't check wall_pos here)
float const radius(0.67*0.2*bp.get_sz_dim(dim)); // 67% of average conduit radius
set_wall_width(conn, conn_height, radius, 2); // set zvals
if (conn.z1() < furnace.z1() || conn.z2() > furnace.z2()) return 0;
bool const pipe_dir(furnace.get_center_dim(!dim) < bp.get_center_dim(!dim));
set_wall_width(conn, conn_height, radius, 2);
float const wall_pos(bp.d[dim][dir]);
conn.d[dim][ dir] = wall_pos;
conn.d[dim][!dir] = wall_pos + (dir ? -1.0 : 1.0)*2.0*radius; // extend outward from wall
conn.d[!dim][!pipe_dir] = furnace.d[!dim][ pipe_dir];
conn.d[!dim][ pipe_dir] = bp .d[!dim][!pipe_dir];
return 1;
}
// Note: for houses
bool building_t::add_basement_utility_objs(rand_gen_t rgen, room_t const &room, float zval, unsigned room_id, float tot_light_amt, unsigned objs_start) {
if (!has_room_geom()) return 0;
// place water heater
bool was_added(add_water_heaters(rgen, room, zval, room_id, tot_light_amt, objs_start));
if (interior->furnace_type == FTYPE_BASEMENT) {was_added |= add_furnace_to_room(rgen, room, zval, room_id, tot_light_amt, objs_start);} // place furnace in basement
return was_added;
}
// Note: this function is here rather than in building_rooms.cpp because utility rooms are connected to utilities in the basement, and it's similar to the code above
bool building_t::add_office_utility_objs(rand_gen_t rgen, room_t const &room, float &zval, unsigned room_id, float tot_light_amt, unsigned objs_start) {
zval = add_flooring(room, zval, room_id, tot_light_amt, FLOORING_CONCRETE); // add concreate and move the effective floor up
objs_start = interior->room_geom->objs.size(); // exclude this from collision checks
unsigned const num_water_heaters(add_water_heaters(rgen, room, zval, room_id, tot_light_amt, objs_start));
if (num_water_heaters == 0) return 0;
// add one furnace per water heater
auto &objs(interior->room_geom->objs);
unsigned const furnaces_start(objs.size());
for (unsigned n = 0; n < num_water_heaters; ++n) {add_furnace_to_room(rgen, room, zval, room_id, tot_light_amt, objs_start);}
unsigned const furnaces_end(objs.size());
cube_t place_area(get_walkable_room_bounds(room));
place_model_along_wall(OBJ_MODEL_SINK, TYPE_SINK, room, 0.45, rgen, zval, room_id, tot_light_amt, place_area, objs_start, 0.6); // place janitorial sink
// add breaker panel
float const floor_spacing(get_window_vspace()), floor_height(floor_spacing - 2.0*get_fc_thickness()), ceil_zval(zval + get_floor_ceil_gap());
float const bp_hwidth(rgen.rand_uniform(0.15, 0.25)*(is_house ? 0.7 : 1.0)*floor_height), bp_hdepth(rgen.rand_uniform(0.05, 0.07)*(is_house ? 0.5 : 1.0)*floor_height);
if (bp_hwidth < 0.25*min(room.dx(), room.dy())) { // if room is large enough
cube_t c;
set_cube_zvals(c, (ceil_zval - 0.75*floor_height), (ceil_zval - rgen.rand_uniform(0.2, 0.35)*floor_height));
for (unsigned n = 0; n < 20; ++n) { // 20 tries
bool const dim(rgen.rand_bool()), dir(rgen.rand_bool());
float const dir_sign(dir ? -1.0 : 1.0), wall_pos(place_area.d[dim][dir]);
float const bp_center(rgen.rand_uniform(place_area.d[!dim][0]+bp_hwidth, place_area.d[!dim][1]-bp_hwidth));
set_wall_width(c, bp_center, bp_hwidth, !dim);
c.d[dim][ dir] = wall_pos;
c.d[dim][!dir] = wall_pos + dir_sign*2.0*bp_hdepth; // extend outward from wall
assert(c.is_strictly_normalized());
cube_t test_cube(c);
test_cube.d[dim][!dir] += dir_sign*2.0*bp_hwidth; // add a width worth of clearance in the front so that the door can be opened
test_cube.z2() = ceil_zval; // extend up to ceiling to ensure space for the conduit
if (is_obj_placement_blocked(test_cube, room, 1) || overlaps_other_room_obj(test_cube, objs_start)) continue;
add_breaker_panel(rgen, c, ceil_zval, dim, dir, room_id, tot_light_amt);
// connect furnaces on the same wall to the breaker box
float const conn_height(c.z1() + rgen.rand_uniform(0.25, 0.75)*c.dz());
for (unsigned f = furnaces_start; f < furnaces_end; ++f) {
cube_t conn;
if (!connect_furnace_to_breaker_panel(objs[f], c, dim, dir, conn_height, conn)) continue;
if (is_obj_placement_blocked(conn, room, 1) || overlaps_other_room_obj(conn, objs_start, 0, &furnaces_start)) continue; // check water heaters only
objs.emplace_back(conn, TYPE_PIPE, room_id, !dim, 0, RO_FLAG_NOCOLL, tot_light_amt, SHAPE_CYLIN, LT_GRAY); // horizontal
}
break; // done
} // for n
}
add_door_sign("Utility", room, zval, room_id, tot_light_amt);
return 1;
}
bool building_t::add_furnace_to_room(rand_gen_t &rgen, room_t const &room, float zval, unsigned room_id, float tot_light_amt, unsigned objs_start) {
float const floor_spacing(get_window_vspace());
cube_t place_area(get_walkable_room_bounds(room));
place_area.expand_by(-get_trim_thickness());
place_area.z1() = zval;
for (unsigned n = 0; n < 100; ++n) { // 100 tries
cube_t furnace;
bool dim(0), dir(0);
if (!gen_furnace_cand(place_area, floor_spacing, 1, rgen, furnace, dim, dir)) break; // near_wall=1
cube_t test_cube(furnace);
test_cube.d[dim][dir] += (dir ? 1.0 : -1.0)*0.5*furnace.get_sz_dim(dim); // add clearance in front
if (zval < ground_floor_z1) {test_cube.z2() = zval + get_floor_ceil_gap();} // basement furnace; extend to the ceiling to make sure there's space for the vent
if (is_obj_placement_blocked(test_cube, room, 1) || overlaps_other_room_obj(test_cube, objs_start)) continue;
unsigned const flags((is_house ? RO_FLAG_IS_HOUSE : 0) | RO_FLAG_INTERIOR);
interior->room_geom->objs.emplace_back(furnace, TYPE_FURNACE, room_id, dim, dir, flags, tot_light_amt);
// no room for exhaust vent, so I guess it's inside the intake vent at the top
return 1; // success/done
} // for n
return 0; // failed
}
// *** Parking Garages ***
vector3d building_t::get_parked_car_size() const {
vector3d car_sz(get_nom_car_size());
if (car_sz.x != 0.0) return car_sz; // valid car size, use this
return get_window_vspace()*vector3d(1.67, 0.73, 0.45); // no cars, use size relative to building floor spacing
}
void building_t::add_parking_garage_objs(rand_gen_t rgen, room_t const &room, float zval, unsigned room_id, unsigned floor_ix,
unsigned num_floors, unsigned &nlights_x, unsigned &nlights_y, float &light_delta_z)
{
assert(has_room_geom());
rgen.rseed1 += 123*floor_ix; // make it unique per floor
rgen.rseed2 += room_id;
// rows are separated by walls and run in dim, with a road and parking spaces on either side of it;
// spaces are arranged in !dim, with roads along the edges of the building that connect to the roads of each row
bool const dim(room.dx() < room.dy()); // long/primary dim; cars are lined up along this dim, oriented along the other dim
vector3d const car_sz(get_parked_car_size()), parking_sz(1.1*car_sz.x, 1.4*car_sz.y, 1.5*car_sz.z); // space is somewhat larger than a car; car length:width = 2.3
float const window_vspacing(get_window_vspace()), floor_thickness(get_floor_thickness()), wall_thickness(1.2*get_wall_thickness()), wall_hc(0.5*wall_thickness); // thicker
float const ceiling_z(zval + window_vspacing - floor_thickness); // Note: zval is at floor level, not at the bottom of the room
float const pillar_width(0.5*car_sz.y), pillar_hwidth(0.5*pillar_width), beam_hwidth(0.5*pillar_hwidth), road_width(2.3*car_sz.y); // road wide enough for two cars
float const wid_sz(room.get_sz_dim(dim)), len_sz(room.get_sz_dim(!dim)), wid_sz_spaces(wid_sz - 2.0*road_width);
float const min_strip_sz(2.0*parking_sz.x + road_width + max(wall_thickness, pillar_width)); // road + parking spaces on each side + wall/pillar
assert(car_sz.z < (window_vspacing - floor_thickness)); // sanity check; may fail for some user parameters, but it's unclear what we do in that case
unsigned const num_space_wid(wid_sz_spaces/parking_sz.y), num_full_strips(max(1U, unsigned(len_sz/min_strip_sz))); // take the floor
bool const half_strip((num_full_strips*min_strip_sz + parking_sz.x + road_width + wall_thickness) < len_sz); // no space for a full row, add a half row
bool const half_row_side(half_strip ? rgen.rand_bool() : 0); // pick a random side
unsigned const num_rows(2*num_full_strips + half_strip), num_strips(num_full_strips + half_strip), num_walls(num_strips - 1);
unsigned const capacity(num_rows*num_space_wid); // ignoring space blocked by stairs and elevators
unsigned &nlights_len(dim ? nlights_x : nlights_y), &nlights_wid(dim ? nlights_y : nlights_x);
nlights_len = num_rows; // lights over each row of parking spaces
nlights_wid = round_fp(0.25*wid_sz/parking_sz.y); // 4 parking spaces per light on average, including roads
//cout << TXT(nlights_len) << TXT(nlights_wid) << TXT(num_space_wid) << TXT(num_rows) << TXT(capacity) << endl; // TESTING
assert(num_space_wid >= 4); // must fit at least 4 cars per row
// add walls and pillars between strips
vect_room_object_t &objs(interior->room_geom->objs);
unsigned const objs_start(objs.size());
colorRGBA const wall_color(WHITE);
cube_t room_floor_cube(room), virt_room_for_wall(room);
set_cube_zvals(room_floor_cube, zval, ceiling_z);
cube_t wall(room_floor_cube), pillar(room_floor_cube), beam(room_floor_cube);
wall.expand_in_dim(dim, -road_width); // wall ends at roads that line the sides of the room; include pillar for better occluder and in case the pillar is skipped
assert(wall.is_strictly_normalized());
float wall_spacing(len_sz/(num_walls + 1));
float const pillar_shift(0.01*pillar_width); // small value to avoid z-fighting
float const wall_len(wall.get_sz_dim(dim) + 2.0f*pillar_shift), pillar_start(wall.d[dim][0] + pillar_hwidth - pillar_shift);
float const row_width(wall_spacing - wall_thickness), space_length(0.5f*(row_width - road_width)), beam_spacing(len_sz/num_rows);
unsigned const num_pillars(max(2U, unsigned(round_fp(0.25*wall_len/parking_sz.y)))); // every 4 spaces, at least 2 at the ends of the wall
float const pillar_spacing((wall_len - pillar_width)/(num_pillars - 1)), beam_delta_z(0.95*wall.dz()), tot_light_amt(room.light_intensity);
bool short_sides[2] = {0,0};
if (half_strip) {
short_sides[half_row_side] = 1;
virt_room_for_wall.d[!dim][half_row_side] += (half_row_side ? 1.0 : -1.0)*space_length;
wall_spacing = virt_room_for_wall.get_sz_dim(!dim)/(num_walls + 1); // recalculate wall spacing
}
light_delta_z = beam_delta_z - wall.dz(); // negative
beam.z1() += beam_delta_z; // shift the bottom up to the ceiling
vect_cube_t obstacles, obstacles_exp, obstacles_ps, wall_parts, temp;
// get obstacles for walls with and without clearance; maybe later add entrance/exit ramps, etc.
interior->get_stairs_and_elevators_bcubes_intersecting_cube(room_floor_cube, obstacles, 0.0); // without clearance, for beams
interior->get_stairs_and_elevators_bcubes_intersecting_cube(room_floor_cube, obstacles_exp, 0.9*window_vspacing); // with clearance in front, for walls and pillars
if (has_ext_basement()) { // everything should avoid the extended basement door
door_t const &eb_door(interior->get_ext_basement_door());
cube_t avoid(eb_door.get_true_bcube());
avoid.expand_in_dim(eb_door.dim, get_doorway_width());
obstacles .push_back(avoid);
obstacles_exp.push_back(avoid);
obstacles_ps .push_back(avoid);
}
cube_with_ix_t const &ramp(interior->pg_ramp);
bool const is_top_floor(floor_ix+1 == num_floors);
// add ramp if one was placed during floorplanning, before adding parking spaces
// Note: lights can be very close to ramps, but I haven't actually seen them touch; do we need to check for and handle that case?
if (!ramp.is_all_zeros()) {
bool const dim(ramp.ix >> 1), dir(ramp.ix & 1), is_blocked(is_top_floor && interior->ignore_ramp_placement);
cube_t rc(ramp); // ramp clipped to this parking garage floor
set_cube_zvals(rc, zval, (zval + window_vspacing));
unsigned const flags(is_blocked ? 0 : RO_FLAG_OPEN); // ramp is open if the top exit is open
objs.emplace_back(rc, TYPE_RAMP, room_id, dim, dir, flags, tot_light_amt, SHAPE_ANGLED, wall_color);
obstacles .push_back(rc); // don't place parking spaces next to the ramp
obstacles_exp.push_back(rc); // clip beams to ramp
obstacles_exp.back().expand_in_dim( dim, road_width); // keep entrance and exit areas clear of parking spaces, even the ones against exterior walls
obstacles_exp.back().expand_in_dim(!dim, 0.75*road_width); // keep walls and pillars away from the sides of ramps
obstacles_ps .push_back(obstacles_exp.back()); // also keep parking spaces away from ramp
// add ramp railings
bool const side(ramp.get_center_dim(!dim) < room.get_center_dim(!dim)); // which side of the ramp the railing is on (opposite the wall the ramp is against)
float const railing_thickness(0.4*wall_thickness), ramp_length(rc.get_sz_dim(dim)), dir_sign(dir ? 1.0 : -1.0), side_sign(side ? 1.0 : -1.0), shorten_factor(0.35);
cube_t railing(rc);
railing.d[!dim][!side] = railing.d[!dim][side] - side_sign*railing_thickness;
railing.z1() += 0.5*railing_thickness; // place bottom of bar along ramp/floor
cube_t ramp_railing(railing);
ramp_railing.d[dim][dir] -= dir_sign*shorten_factor*ramp_length; // shorten length to only the lower part
ramp_railing.z2() -= shorten_factor*railing.dz(); // shorten height by the same amount to preserve the slope
colorRGBA const railing_color(LT_GRAY);
objs.emplace_back(ramp_railing, TYPE_RAILING, room_id, dim, dir, RO_FLAG_OPEN, tot_light_amt, SHAPE_CUBE, railing_color); // lower railing
set_cube_zvals(railing, rc.z2(), (rc.z2() + window_vspacing));
railing.translate_dim(!dim, side_sign*railing_thickness); // shift off the ramp and onto the ajdacent floor
if (!is_top_floor) { // add side railing for lower level
railing.d[dim][!dir] += dir_sign*shorten_factor*ramp_length; // shorten length to only the upper part
objs.emplace_back(railing, TYPE_RAILING, room_id, dim, 0, (RO_FLAG_OPEN | RO_FLAG_TOS), tot_light_amt, SHAPE_CUBE, railing_color);
}
else if (!is_blocked) { // add upper railings at the top for the full length
railing.translate_dim( dim, -0.5*dir_sign*railing_thickness); // shift down the ramp a bit
objs.emplace_back(railing, TYPE_RAILING, room_id, dim, 0, (RO_FLAG_OPEN | RO_FLAG_TOS), tot_light_amt, SHAPE_CUBE, railing_color);
cube_t back_railing(rc);
set_cube_zvals(back_railing, railing.z1(), railing.z2());
back_railing.translate_dim( dim, -dir_sign*railing_thickness); // shift onto the ajdacent floor
back_railing.translate_dim(!dim, 0.5*side_sign*railing_thickness); // shift away from the exterior wall
back_railing.d[dim][dir] = back_railing.d[dim][!dir] + dir_sign*railing_thickness;
objs.emplace_back(back_railing, TYPE_RAILING, room_id, !dim, 0, (RO_FLAG_OPEN | RO_FLAG_TOS), tot_light_amt, SHAPE_CUBE, railing_color);
}
}
// add walls and pillars
bool const no_sep_wall(num_walls == 0 || (capacity < 100 && (room_id & 1))); // use room_id rather than rgen so that this agrees between floors
bool const split_sep_wall(!no_sep_wall && (num_pillars >= 5 || (num_pillars >= 4 && rgen.rand_bool())));
float sp_const(0.0);
if (no_sep_wall) {sp_const = 0.25;} // no separator wall, minimal clearance around stairs
else if (pri_hall_stairs_to_pg) {sp_const = 0.50;} // central stairs should open along the wall, not a tight space, need less clearance
else if (split_sep_wall) {sp_const = 0.75;} // gap should provide access, need slightly less clearance
else {sp_const = 1.00;} // stairs may cut through/along full wall, need max clearance
float const space_clearance(sp_const*max(0.5f*window_vspacing, parking_sz.y));
// obstacles with clearance all sides, for parking spaces
interior->get_stairs_and_elevators_bcubes_intersecting_cube(room_floor_cube, obstacles_ps, max(space_clearance, 0.9f*window_vspacing), space_clearance);
if (interior->room_geom->wall_ps_start == 0) {interior->room_geom->wall_ps_start = objs.size();} // set if not set, on first level
float center_pos(wall.get_center_dim(dim));
// if there's an odd number of pillars, move the gap between two pillars on one side or the other
if (split_sep_wall && (num_pillars & 1)) {center_pos += (rgen.rand_bool() ? -1.0 : 1.0)*0.5*pillar_spacing;}
vect_cube_t pillars; // added after wall segments
for (unsigned n = 0; n < num_walls+2; ++n) { // includes room far walls
if (n < num_walls) { // interior wall
float const pos(virt_room_for_wall.d[!dim][0] + (n + 1)*wall_spacing); // reference from the room far wall, assuming we can fit a full width double row strip
set_wall_width(wall, pos, wall_hc, !dim);
set_wall_width(pillar, pos, pillar_hwidth, !dim);
if (!no_sep_wall) {
cube_t walls[2] = {wall, wall};
if (split_sep_wall) { // add a gap between the walls for people to walk through
walls[0].d[dim][1] = center_pos - 0.4*window_vspacing;
walls[1].d[dim][0] = center_pos + 0.4*window_vspacing;
}
for (unsigned side = 0; side < (split_sep_wall ? 2U : 1U); ++side) {
subtract_cubes_from_cube_split_in_dim(walls[side], obstacles_exp, wall_parts, temp, dim);
for (auto const &w : wall_parts) {
if (w.get_sz_dim(dim) < 2.0*window_vspacing) continue; // too short, skip
objs.emplace_back(w, TYPE_PG_WALL, room_id, !dim, 0, 0, tot_light_amt, SHAPE_CUBE, wall_color);
interior->room_geom->pgbr_walls[!dim].push_back(w); // save for occlusion culling
}
} // for side
}
}
else { // room wall
bool const side(n == num_walls+1);
pillar.d[!dim][ side] = room.d[!dim][side];
pillar.d[!dim][!side] = room.d[!dim][side] + (side ? -1.0 : 1.0)*pillar_hwidth; // half the width of an interior wall pillar
}
for (unsigned p = 0; p < num_pillars; ++p) { // add support pillars
float const ppos(pillar_start + p*pillar_spacing);
set_wall_width(pillar, ppos, pillar_hwidth, dim);
if (has_bcube_int_xy(pillar, obstacles_exp)) continue; // skip entire pillar if it intersects stairs or an elevator
pillars.push_back(pillar);
} // for p
} // for n
for (auto const &p : pillars) {objs.emplace_back(p, TYPE_PG_PILLAR, room_id, !dim, 0, 0, tot_light_amt, SHAPE_CUBE, wall_color);}
// add beams in !dim, at and between pillars
unsigned const beam_flags(RO_FLAG_NOCOLL | RO_FLAG_HANGING);
for (unsigned p = 0; p < (4*(num_pillars - 1) + 1); ++p) { // add beams, 4 per pillar
float const ppos(pillar_start + 0.25*p*pillar_spacing);
set_wall_width(beam, ppos, beam_hwidth, dim);
subtract_cubes_from_cube_split_in_dim(beam, obstacles, wall_parts, temp, !dim);
for (auto const &w : wall_parts) {
if (min(w.dx(), w.dy()) > beam_hwidth) {objs.emplace_back(w, TYPE_PG_BEAM, room_id, !dim, 0, beam_flags, tot_light_amt, SHAPE_CUBE, wall_color);}
}
} // for p
// add beams in dim for each row of lights
for (unsigned n = 0; n < num_rows; ++n) {
float const pos(room.d[!dim][0] + (n + 0.5)*beam_spacing);
cube_t beam(room_floor_cube);
beam.z1() += beam_delta_z; // shift the bottom up to the ceiling
set_wall_width(beam, pos, beam_hwidth, !dim);
subtract_cubes_from_cube_split_in_dim(beam, obstacles, wall_parts, temp, dim);
for (auto const &w : wall_parts) {
if (min(w.dx(), w.dy()) > beam_hwidth) {objs.emplace_back(w, TYPE_PG_BEAM, room_id, dim, 0, beam_flags, tot_light_amt, SHAPE_CUBE, wall_color);}
}
}
// add parking spaces on both sides of each row (one side if half row)
cube_t row(wall); // same length as the wall; includes the width of the pillars
row.z2() = row.z1() + get_rug_thickness(); // slightly above the floor
float const space_width(row.get_sz_dim(dim)/num_space_wid), strips_start(virt_room_for_wall.d[!dim][0]), wall_half_gap(2.0*wall_hc), space_shrink(row_width - space_length);
bool const add_cars(enable_parked_cars() && !is_rotated()); // skip cars for rotated buildings
unsigned const max_handicap_spots(capacity/20 + 1);
unsigned num_handicap_spots(0);
rand_gen_t rgen2(rgen); // make a copy to use with cars so that enabling cars doesn't change the parking garage layout
for (unsigned n = 0; n < num_strips; ++n) {
assert(space_length > 0.0);
assert(space_width > 0.0);
assert(wall_spacing > 2.0*wall_half_gap);
row.d[!dim][0] = strips_start + (n + 0)*wall_spacing + wall_half_gap;
row.d[!dim][1] = strips_start + (n + 1)*wall_spacing - wall_half_gap;
assert(row.is_strictly_normalized());
if (row.get_sz_dim(!dim) < 1.2*space_shrink) continue; // space too small, likely due to incorrect building scale relative to car size; skip parking space placement
for (unsigned d = 0; d < 2; ++d) { // for each side of the row
bool const at_ext_wall[2] = {(n == 0 && d == 0), (n+1 == num_strips && d == 1)}, at_either_ext_wall(at_ext_wall[0] || at_ext_wall[1]);
if ((short_sides[0] && at_ext_wall[0]) || (short_sides[1] && at_ext_wall[1])) continue; // skip this row
float row_left_edge(row.d[dim][0]); // spaces start flush with the row, or flush with the room if this is the exterior wall
unsigned num_spaces_per_row(num_space_wid);
if (at_either_ext_wall) { // at either room exterior wall - can extend spaces up to the wall
float row_right_edge(row.d[dim][1]); // opposite end of the row
while ((row_left_edge - space_width) > room.d[dim][0]) {row_left_edge -= space_width; ++num_spaces_per_row;} // add rows to the left
while ((row_right_edge + space_width) < room.d[dim][1]) {row_right_edge += space_width; ++num_spaces_per_row;} // add rows to the right
}
float const d_sign(d ? 1.0 : -1.0);
cube_t space(row);
space.d[!dim][!d] += d_sign*space_shrink; // shrink
space.d[ dim][0] = row_left_edge;
bool last_was_space(0);
for (unsigned s = 0; s < num_spaces_per_row; ++s) {
space.d[dim][1] = space.d[dim][0] + space_width; // set width
assert(space.is_strictly_normalized());
if (has_bcube_int_xy(space, obstacles_ps)) { // skip entire space if it intersects stairs or an elevator
if (last_was_space) {objs.back().flags &= ~RO_FLAG_ADJ_HI;} // no space to the right for the previous space
last_was_space = 0;
}
else {
unsigned flags(RO_FLAG_NOCOLL);
if (last_was_space ) {flags |= RO_FLAG_ADJ_LO;} // adjacent space to the left
if (s+1 < num_spaces_per_row) {flags |= RO_FLAG_ADJ_HI;} // not the last space - assume there will be a space to the right
bool const add_car(add_cars && rgen2.rand_float() < 0.5); // 50% populated with cars
// make it a handicap spot if near an elevator and there aren't already too many
if (num_handicap_spots < max_handicap_spots) {
cube_t hc_area(space);
hc_area.expand_by(1.5*space_width);
if (!no_sep_wall) {hc_area.intersect_with_cube_xy(row);} // keep within the current row if there are walls in between rows
for (elevator_t const &e : interior->elevators) {
if (e.z1() > space.z2()) continue; // doesn't extend down to this level
if (e.intersects_xy(hc_area)) {flags |= RO_FLAG_IS_ACTIVE; ++num_handicap_spots; break;}
}
}
room_object_t pspace(space, TYPE_PARK_SPACE, room_id, !dim, d, flags, tot_light_amt, SHAPE_CUBE, wall_color); // floor_color?
if (add_car) { // add a collider to block this area from the player, people, and rats; add first so that objs.back() is correct for the next iter
car_t car(car_from_parking_space(pspace));
objs.emplace_back(car.bcube, TYPE_COLLIDER, room_id, !dim, d, (RO_FLAG_INVIS | RO_FLAG_FOR_CAR));
pspace.obj_id = (uint16_t)(objs.size() + rgen2.rand()); // will be used for the car model and color
pspace.flags |= RO_FLAG_USED;
objs.back().obj_id = pspace.obj_id; // will be used for loot collected from the car
}
if (no_sep_wall && !at_either_ext_wall) { // add small yellow curbs to block cars
float const curb_height(0.04*window_vspacing), curb_width(1.5*curb_height);
cube_t curb(space);
curb.z2() += curb_height; // set height
curb.d[!dim][!d] += d_sign*(space.get_sz_dim(!dim) - curb_width); // shrink to the correct width
curb.translate_dim(!dim, -d_sign*(0.5*pillar_hwidth + curb_width)); // move inward to avoid pillars and walls
curb.expand_in_dim(dim, -0.2*space_width);
objs.emplace_back(curb, TYPE_CURB, room_id, dim, 0, 0, 1.0, SHAPE_CUBE, colorRGBA(1.0, 0.8, 0.3)); // dir=0
}
objs.push_back(pspace);
last_was_space = 1;
}
space.d[dim][0] = space.d[dim][1]; // shift to next space
} // for s
} // for d
} // for n
if (is_top_floor) { // place pipes on the top level parking garage ceiling (except for sprinkler pipes, which go on every floor)
float const pipe_light_amt = 1.0; // make pipes and electrical brighter and easier to see
// avoid intersecting lights, pillars, walls, stairs, elevators, and ramps;
// note that lights haven't been added yet though, but they're placed on beams, so we can avoid beams instead
vect_cube_t walls, beams;
for (auto i = objs.begin()+objs_start; i != objs.end(); ++i) {
if (i->type == TYPE_PG_WALL) {walls.push_back(*i);} // wall
else if (i->type == TYPE_PG_PILLAR) { // pillar
walls .push_back(*i); // included in walls
obstacles.push_back(*i); // pillars also count as obstacles
obstacles.back().z1() = room.z1(); // extend down to all floors so that these work with sprinkler pipes on a lower floor
}
else if (i->type == TYPE_PG_BEAM) {beams .push_back(*i);} // ceiling beam
else if (i->type == TYPE_RAMP ) {obstacles.push_back(*i);} // ramps are obstacles for pipes
} // for i
add_basement_electrical(obstacles, walls, beams, room_id, pipe_light_amt, rgen);
// get pipe ends (risers) coming in through the ceiling
vect_riser_pos_t sewer, cold_water, hot_water, gas_pipes;
get_pipe_basement_water_connections(sewer, cold_water, hot_water, rgen);
vect_cube_t pipe_cubes;
// hang sewer pipes under the ceiling beams; hang water pipes from the ceiling, above sewer pipes and through the beams
float const ceil_zval(beam.z1()), water_ceil_zval(beam.z2());
add_basement_pipes(obstacles, walls, beams, sewer, pipe_cubes, room_id, num_floors, objs_start, pipe_light_amt, ceil_zval, rgen, PIPE_TYPE_SEWER); // sewer
add_to_and_clear(pipe_cubes, obstacles); // add sewer pipes to obstacles
add_basement_pipes(obstacles, walls, beams, cold_water, pipe_cubes, room_id, num_floors, objs_start, pipe_light_amt, water_ceil_zval, rgen, PIPE_TYPE_CW ); // cold water
add_to_and_clear(pipe_cubes, obstacles); // add cold water pipes to obstacles
add_basement_pipes(obstacles, walls, beams, hot_water, pipe_cubes, room_id, num_floors, objs_start, pipe_light_amt, water_ceil_zval, rgen, PIPE_TYPE_HW ); // hot water
add_to_and_clear(pipe_cubes, obstacles); // add hot water pipes to obstacles
get_pipe_basement_gas_connections(gas_pipes);
add_basement_pipes(obstacles, walls, beams, gas_pipes, pipe_cubes, room_id, num_floors, objs_start, pipe_light_amt, water_ceil_zval, rgen, PIPE_TYPE_GAS, 1); // gas
add_to_and_clear(pipe_cubes, obstacles); // add gas pipes to obstacles
// if there are multiple parking garage floors, lights have already been added on the floor(s) below; add them as occluders for sprinkler pipes;
// lights on the top floor will be added later and will check for pipe intersections
for (auto i = objs.begin()+interior->room_geom->wall_ps_start; i < objs.begin()+objs_start; ++i) {
if (i->type == TYPE_LIGHT && room.contains_cube(*i)) {obstacles.push_back(*i);}
}
add_sprinkler_pipes(obstacles, walls, beams, pipe_cubes, room_id, num_floors, pipe_light_amt, rgen);
}
}
// *** Pipes ***
// find the closest wall (including room wall) to this location, avoiding obstacles, and shift outward by radius; routes in X or Y only, for now
point get_closest_wall_pos(point const &pos, float radius, cube_t const &room, vect_cube_t const &walls, vect_cube_t const &obstacles, bool vertical) {
if (!room.contains_pt_xy_exp(pos, radius)) {return pos;} // error?
// what about checking pos intersecting walls or obstacles? is that up to the caller to handle?
vector3d const expand(radius, radius, radius);
point best(pos);
float dmin(room.dx() + room.dy()); // use an initial distance larger than what we can return
for (unsigned dim = 0; dim < 2; ++dim) { // check room/part exterior walls first
for (unsigned dir = 0; dir < 2; ++dir) {
float const val(room.d[dim][dir] + (dir ? -radius : radius)), dist(fabs(val - pos[dim])); // shift val inward
if (dist >= dmin) continue;
point cand(pos);
cand[dim] = val;
// check walls as well, even though any wall hit should be replaced with a closer point below
if (line_int_cubes_exp(pos, cand, obstacles, expand) || line_int_cubes_exp(pos, cand, walls, expand)) continue;
if (vertical && line_int_cubes_exp(cand, point(cand.x, cand.y, room.z1()), obstacles, expand)) continue; // check for vertical obstacles
best = cand; dmin = dist; // success
} // for dir
} // for dim
for (cube_t const &wall : walls) { // check all interior walls
for (unsigned dim = 0; dim < 2; ++dim) {
if (pos[!dim] < wall.d[!dim][0]+radius || pos[!dim] > wall.d[!dim][1]-radius) continue; // doesn't project in this dim
bool const dir(wall.get_center_dim(dim) < pos[dim]);
float const val(wall.d[dim][dir] - (dir ? -radius : radius)), dist(fabs(val - pos[dim])); // shift val outward
if (dist >= dmin) continue;
point cand(pos);
cand[dim] = val;
if (line_int_cubes_exp(pos, cand, obstacles, expand)) continue; // check obstacles only
if (vertical && line_int_cubes_exp(cand, point(cand.x, cand.y, room.z1()), obstacles, expand)) continue; // check for vertical obstacles
best = cand; dmin = dist; // success
} // for dim
}
return best;
}
float get_merged_pipe_radius(float r1, float r2, float exponent) {return pow((pow(r1, exponent) + pow(r2, exponent)), 1/exponent);}
float get_merged_risers_radius(vect_riser_pos_t const &risers, int exclude_flow_dir=2) {
float radius(0.0);
for (riser_pos_t const &p : risers) {
if ((int)p.flow_dir == exclude_flow_dir) continue;
radius = get_merged_pipe_radius(radius, + p.radius, 4.0); // higher exponent to avoid pipes that are too large
}
return radius;
}
enum {PIPE_RISER=0, PIPE_CONN, PIPE_MAIN, PIPE_MEC, PIPE_EXIT, PIPE_FITTING};
void expand_cube_except_in_dim(cube_t &c, float expand, unsigned not_dim) {
c.expand_by(expand);
c.expand_in_dim(not_dim, -expand); // oops, we shouldn't have expanded in this dim
}
struct pipe_t {
point p1, p2;
float radius;
unsigned dim, type, end_flags; // end_flags: 1 bit is low end, 2 bit is high end
bool connected, outside_pg;
pipe_t(point const &p1_, point const &p2_, float radius_, unsigned dim_, unsigned type_, unsigned end_flags_) :
p1(p1_), p2(p2_), radius(radius_), dim(dim_), type(type_), end_flags(end_flags_), connected(type != PIPE_RISER), outside_pg(0) {}
float get_length() const {return fabs(p2[dim] - p1[dim]);}
cube_t get_bcube() const {
cube_t bcube(p1, p2);
expand_cube_except_in_dim(bcube, radius, dim);
return bcube;
}
};
void add_insul_exclude(pipe_t const &pipe, cube_t const &fitting, vect_cube_t &insul_exclude) {
cube_t ie(fitting);
ie.expand_by(2.0*pipe.radius); // expand in all dims; needed for right angle and T junctions because they're in multiple dims
ie.expand_in_dim(pipe.dim, 1.0*pipe.radius); // expand a bit more in pipe dim
insul_exclude.push_back(ie);
}
bool has_int_obstacle_or_parallel_wall(cube_t const &c, vect_cube_t const &obstacles, vect_cube_t const &walls) {
if (has_bcube_int(c, obstacles)) return 1;
bool const pipe_dim(c.dx() < c.dy());
for (cube_t const &wall : walls) {
bool const wall_dim(wall.dy() < wall.dx());
if (wall_dim != pipe_dim && wall.intersects(c)) return 1; // check if wall and pipe are parallel
}
return 0;
}
bool building_t::add_basement_pipes(vect_cube_t const &obstacles, vect_cube_t const &walls, vect_cube_t const &beams, vect_riser_pos_t const &risers, vect_cube_t &pipe_cubes,
unsigned room_id, unsigned num_floors, unsigned objs_start, float tot_light_amt, float ceil_zval, rand_gen_t &rgen, unsigned pipe_type, bool allow_place_fail)
{
assert(pipe_type < NUM_PIPE_TYPES);
if (risers.empty()) return 0; // can happen for hot water pipes when there are no hot water fixtures
float const FITTING_LEN(1.2), FITTING_RADIUS(1.1); // relative to radius
bool const is_hot_water(pipe_type == PIPE_TYPE_HW), is_closed_loop(is_hot_water), add_insul(is_hot_water);
vect_room_object_t &objs(interior->room_geom->objs);
assert(objs_start <= objs.size());
cube_t const &basement(get_basement());
float const r_main(get_merged_risers_radius(risers, (is_closed_loop ? 0 : 2))); // exclude incoming water from hot water heaters for hot water pipes
if (r_main == 0.0) return 0; // hot water heater but no hot water pipes?
float const insul_thickness(0.4), min_insum_len(4.0); // both relative to pipe radius
float const window_vspacing(get_window_vspace()), fc_thickness(get_fc_thickness()), wall_thickness(get_wall_thickness());
float const pipe_zval(ceil_zval - FITTING_RADIUS*r_main); // includes clearance for fittings vs. beams (and lights - mostly)
float const pipe_min_z1(pipe_zval - FITTING_RADIUS*r_main);
float const align_dist(2.0*wall_thickness); // align pipes within this range (in particular sinks and stall toilets)
float const radius_factor(add_insul ? 1.0+insul_thickness : 1.0);
float const r_main_spacing(radius_factor*r_main); // include insulation thickness for hot water pipes
assert(pipe_zval > bcube.z1());
vector<pipe_t> pipes, fittings;
cube_t pipe_end_bcube;
unsigned num_valid(0), num_connected(0), num_conn_segs(0);
// build random shifts table; make consistent per pipe to preserve X/Y alignments
unsigned const NUM_SHIFTS = 21; // {0,0} + 20 random shifts
vector3d rshifts[NUM_SHIFTS] = {};
for (unsigned n = 1; n < NUM_SHIFTS; ++n) {rshifts[n][rgen.rand_bool()] = 0.25*window_vspacing*rgen.signed_rand_float();} // random shift in a random dir
// determine if we need to add an entrance/exit pipe
bool add_exit_pipe(1);
if (is_closed_loop) { // hot water pipes; sewer pipes always have an exit and cold water always has an entrance
for (riser_pos_t const &p : risers) {
if (p.flow_dir == 0) {add_exit_pipe = 0; break;} // hot water flowing out of a water heater
}
// if we got here and add_exit_pipe=1, just create the exit pipe and assume the hot water heater is somewhere else
}
// seed the pipe graph with valid vertical segments and build a graph of X/Y values
for (riser_pos_t const &p : risers) {
assert(p.radius > 0.0);
assert(p.pos.z > pipe_zval);
point pos(p.pos);
bool valid(0);
for (unsigned n = 0; n < NUM_SHIFTS; ++n) { // try zero + random shifts
cube_t c(pos);
c.expand_by_xy(p.radius);
//c.z1() = bcube.z1(); // extend all the way down to the floor of the lowest basement
c.z1() = pipe_min_z1; // extend down to the lowest pipe z1
// can't place outside building bcube, or over stairs/elevators/ramps/pillars/walls/beams;
// here beams are included because lights are attached to the underside of them, so avoiding beams should hopefully also avoid lights
if (!bcube.contains_cube_xy(c) || has_bcube_int(c, obstacles) || has_bcube_int(c, walls) || has_bcube_int(c, beams)) {
pos = p.pos + rshifts[n]; // apply shift
continue;
}
valid = 1;
break;
} // for n
if (!valid) continue; // no valid shift, skip this connection
pipes.emplace_back(point(pos.x, pos.y, pipe_zval), pos, p.radius, 2, PIPE_RISER, 0); // neither end capped
pipe_end_bcube.assign_or_union_with_cube(pipes.back().get_bcube());
++num_valid;
} // for pipe_ends
if (pipes.empty()) return 0; // no valid pipes
// calculate unique positions of pipes along the main pipe
bool const dim(pipe_end_bcube.dx() < pipe_end_bcube.dy()); // main sewer line dim
map<float, vector<unsigned>> xy_map;
for (auto p = pipes.begin(); p != pipes.end(); ++p) {
unsigned const pipe_ix(p - pipes.begin());
float &v(p->p1[dim]);
auto it(xy_map.find(v));
if (it != xy_map.end()) {it->second.push_back(pipe_ix); continue;} // found
bool found(0);
// try to find an existing map value that's within align_dist of this value; messy and inefficient, but I'm not sure how else to do this
for (auto &i : xy_map) {
if (fabs(i.first - v) > align_dist) continue; // too far
i.second.push_back(pipe_ix);
v = p->p2[dim] = i.first;
found = 1;
break;
}
if (!found) {xy_map[v].push_back(pipe_ix);}
} // for pipes
// create main pipe that runs in the longer dim (based on drain pipe XY bounds)
pipe_end_bcube.expand_in_dim(dim, r_main);
// use the center of the pipes bcube to minimize run length, but clamp to the interior of the basement;
// in the case where all risers are outside of the basement perimeter, this will make pipes run against the exterior basement wall
float const pipes_bcube_center(max(basement.d[!dim][0]+r_main, min(basement.d[!dim][1]-r_main, pipe_end_bcube.get_center_dim(!dim))));
float centerline(pipes_bcube_center);
point mp[2]; // {lo, hi} ends
bool exit_dir(0);
point exit_pos;
for (unsigned d = 0; d < 2; ++d) { // dim
mp[d][ dim] = pipe_end_bcube.d[dim][d];
mp[d][!dim] = centerline;
mp[d].z = pipe_zval;
}
// shift pipe until it clears all obstacles
float const step_dist(2.0*r_main), step_area(bcube.get_sz_dim(!dim)); // step by pipe radius
unsigned const max_steps(step_area/step_dist);
bool success(0);
for (unsigned n = 0; n < max_steps; ++n) {
cube_t const c(pipe_t(mp[0], mp[1], r_main_spacing, dim, PIPE_MAIN, 3).get_bcube());
if (!has_int_obstacle_or_parallel_wall(c, obstacles, walls)) {
success = 1;
// check for overlap with beam running parallel to the main pipe, and reject it; mostly there to avoid blocking lights that may be on the beam
for (cube_t const &beam : beams) {
if (beam.get_sz_dim(dim) < beam.get_sz_dim(!dim)) continue; // beam not parallel to pipe, ignore
if (c.intersects_xy(beam)) {success = 0; break;}
}
if (success) break; // success/done
}
float const xlate(((n>>1)+1)*((n&1) ? -1.0 : 1.0)*step_dist);
UNROLL_2X(mp[i_][!dim] += xlate;); // try the new position
if (!basement.contains_cube_xy(pipe_t(mp[0], mp[1], r_main_spacing, dim, PIPE_MAIN, 3).get_bcube())) { // outside the basement
UNROLL_2X(mp[i_][!dim] -= 2.0*xlate;); // try shifting in the other direction
if (!basement.contains_cube_xy(pipe_t(mp[0], mp[1], r_main_spacing, dim, PIPE_MAIN, 3).get_bcube())) break; // outside the basement in both dirs, fail
}
} // for n
if (success) {
centerline = mp[0][!dim]; // update centerline based on translate
}
else if (allow_place_fail) { // fail case
// try stripping off some risers from the end and connecting the remaining ones
vect_riser_pos_t risers_sub;
float riser_min(FLT_MAX), riser_max(-FLT_MAX);
for (riser_pos_t const &r : risers) {
min_eq(riser_min, r.pos[dim]);
max_eq(riser_max, r.pos[dim]);
}
for (unsigned dir = 0; dir < 2; ++dir) {
risers_sub.clear();
// try to remove risers at each end recursively until successful
for (riser_pos_t const &r : risers) {
if (r.pos[dim] != (dir ? riser_max : riser_min)) {risers_sub.push_back(r);}
}
if (risers_sub.empty()) continue;
assert(risers_sub.size() < risers.size());
if (add_basement_pipes(obstacles, walls, beams, risers_sub, pipe_cubes, room_id, num_floors, objs_start, tot_light_amt, ceil_zval, rgen, pipe_type, 1)) return 1;
} // for dir
return 0;
}
else {
UNROLL_2X(mp[i_][!dim] = centerline;) // else use the centerline, even though it's invalid; rare, and I don't have a non-house example where it looks wrong
}
mp[0][dim] = bcube.d[dim][1]; mp[1][dim] = bcube.d[dim][0]; // make dim range denormalized; will recalculate below with correct range
bool const d(!dim);
float const conn_pipe_merge_exp = 3.0; // cubic
unsigned const conn_pipes_start(pipes.size());
// connect drains/feeders to main pipe in !dim
for (auto const &v : xy_map) { // for each unique position along the main pipe
float radius(0.0), range_min(centerline), range_max(centerline), unconn_radius(0.0);
point const &ref_p1(pipes[v.second.front()].p1);
unsigned num_keep(0);
for (unsigned ix : v.second) {
assert(ix < pipes.size());
pipe_t &pipe(pipes[ix]);
float const val(pipe.p1[d]);
if (fabs(val - centerline) < r_main) {pipe.p1[d] = pipe.p2[d] = centerline;} // shift to connect directly to main pipe since it's close enough
else {
float lo(val - pipe.radius), hi(val + pipe.radius);
point p1(ref_p1), p2(p1);
if (lo < range_min) {p1[d] = lo; p2[d] = range_min;} // on the lo side
else if (hi > range_max) {p1[d] = range_max; p2[d] = hi;} // on the hi side
float const r_test(radius_factor*pipe.radius);
bool skip(has_int_obstacle_or_parallel_wall(pipe_t(p1, p2, r_test, d, PIPE_CONN, 3).get_bcube(), obstacles, walls));
if (skip && (p2[d] - p1[d]) > 8.0*pipe.radius) { // blocked, can't connect, long segment: try extending halfway
if (lo < range_min) {p1[d] = lo = 0.5*(lo + range_min); pipe.p1[d] = pipe.p2[d] = lo + pipe.radius;} // on the lo side
else if (hi > range_max) {p2[d] = hi = 0.5*(hi + range_max); pipe.p1[d] = pipe.p2[d] = hi - pipe.radius;} // on the hi side
skip = has_int_obstacle_or_parallel_wall(pipe_t(p1, p2, r_test, d, PIPE_CONN, 3).get_bcube(), obstacles, walls);
if (!skip) { // okay so far; now check that the new shortened pipe has a riser pos that's clear of walls and beams
point const new_riser_pos((lo < range_min) ? p1 : p2); // the end that was clipped
cube_t test_cube; test_cube.set_from_sphere(new_riser_pos, pipe.radius);
skip = (has_bcube_int(test_cube, walls) || has_bcube_int(test_cube, beams));
}
}
if (skip) {
unconn_radius = get_merged_pipe_radius(unconn_radius, pipe.radius, conn_pipe_merge_exp); // add this capacity for use in another riser
continue;
}
min_eq(range_min, lo); // update ranges
max_eq(range_max, hi);
}
pipe.connected = 1;
if (unconn_radius > 0.0) {pipe.radius = get_merged_pipe_radius(pipe.radius, unconn_radius, conn_pipe_merge_exp); unconn_radius = 0.0;} // add extra capacity
radius = get_merged_pipe_radius(radius, pipe.radius, conn_pipe_merge_exp);
++num_keep;
} // for ix
if (num_keep == 0) continue; // no valid connections for this row
// we can skip adding a connector if short and under the main pipe
if (range_max - range_min > r_main) {
point p1(ref_p1), p2(p1); // copy dims !d and z from a representative pipe
p1[d] = range_min; p2[d] = range_max;
pipes.emplace_back(p1, p2, radius, d, PIPE_CONN, 3); // cap both ends
for (unsigned ix : v.second) { // add fittings
if (!pipes[ix].connected) continue; // pipe was not connected, don't add the fitting
float const val(pipes[ix].p1[d]), fitting_len(FITTING_LEN*radius);
p1[d] = val - fitting_len; p2[d] = val + fitting_len;
fittings.emplace_back(p1, p2, FITTING_RADIUS*radius, d, PIPE_FITTING, 3);
}
} // end connector
// add fitting to the main pipe
point p1(mp[0]), p2(p1);
float const fitting_len(FITTING_LEN*r_main);
p1[!d] = v.first - fitting_len; p2[!d] = v.first + fitting_len;
fittings.emplace_back(p1, p2, FITTING_RADIUS*r_main, !d, PIPE_FITTING, 3);
// update main pipe endpoints to include this connector pipe range
min_eq(mp[0][dim], v.first-radius);
max_eq(mp[1][dim], v.first+radius);
num_connected += num_keep;
++num_conn_segs;
} // for v
if (mp[0][dim] >= mp[1][dim]) return 0; // no pipes connected to main? I guess there's nothing to do here
unsigned main_pipe_end_flags(0); // start with both ends unconnected
if (add_exit_pipe) {
bool tried_horizontal(0);
for (unsigned attempt = 0; attempt < 2; ++attempt) { // make up to 2 attempts with a horizontal or vertical connection
if (is_closed_loop || num_floors > 1 || attempt == 1 || num_conn_segs == 1 || rgen.rand_bool()) { // exit into the wall of the building
if (tried_horizontal) break; // we got here the previous iteration; give up, exit can't be found
tried_horizontal = 1;
// Note: if roads are added for secondary buildings, we should have the exit on the side of the building closest to the road
bool const first_dir((basement.d[dim][1] - mp[1][dim]) < (mp[0][dim] - basement.d[dim][0])); // closer basement exterior wall
bool added_exit(0);
for (unsigned d = 0; d < 2; ++d) { // dir
bool const dir(bool(d) ^ first_dir);
point ext[2] = {mp[dir], mp[dir]};
ext[dir][dim] = basement.d[dim][dir]; // shift this end to the basement wall
if (has_int_obstacle_or_parallel_wall(pipe_t(ext[0], ext[1], r_main_spacing, dim, PIPE_MAIN, 0).get_bcube(), obstacles, walls)) continue;
mp[dir] = ext[dir];
main_pipe_end_flags = (dir ? 2 : 1); // connect the end going to the exit
added_exit = 1;
break;
} // for d
if (added_exit) break;
// no straight segment? how about a right angle?
bool first_side(0);
if (centerline == pipes_bcube_center) {first_side = rgen.rand_bool();} // centered, choose a random side
else {first_side = ((basement.d[!dim][1] - mp[0][!dim]) < (mp[0][!dim] - basement.d[!dim][0]));} // off-center, choose closer basement exterior wall
for (unsigned d = 0; d < 2 && !added_exit; ++d) { // dir
for (unsigned e = 0; e < 2; ++e) { // side
bool const dir(bool(d) ^ first_dir), side(bool(e) ^ first_side);
point ext[2] = {mp[dir], mp[dir]};
ext[side][!dim] = basement.d[!dim][side]; // shift this end to the basement wall
pipe_t const exit_pipe(ext[0], ext[1], r_main, !dim, PIPE_MEC, (side ? 1 : 2)); // add a bend in the side connecting to the main pipe
cube_t const pipe_bcube(exit_pipe.get_bcube());
if (has_int_obstacle_or_parallel_wall(pipe_bcube, obstacles, walls)) continue; // can't extend to the ext wall in this dim
bool bad_place(0);
// check if the pipe is too close to an existing conn pipe; allow it to contain the other pipe in dim
for (auto p = pipes.begin()+conn_pipes_start; p != pipes.end(); ++p) {
cube_t const other_bcube(p->get_bcube());
if (!pipe_bcube.intersects(other_bcube)) continue;
if (pipe_bcube.d[dim][0] > other_bcube.d[dim][0] || pipe_bcube.d[dim][1] < other_bcube.d[dim][1]) {bad_place = 1; break;}
}
if (bad_place) continue; // seems to usually fail
pipes.push_back(exit_pipe);
main_pipe_end_flags = (dir ? 2 : 1); // connect the end going to the exit connector pipe
added_exit = 1;
break;
} // for e
} // for d
if (added_exit) break;
}
// create exit segment and vertical pipe into the floor
float exit_dmin(0.0);
for (unsigned d = 0; d < 2; ++d) { // dim
point const cand_exit_pos(get_closest_wall_pos(mp[d], r_main_spacing, basement, walls, obstacles, 1)); // vertical=1
float dist(p2p_dist(mp[d], cand_exit_pos));
if (dist == 0.0) {dist = FLT_MAX;} // dist will be 0 if we fail to find a wall, so don't prefer it in that case
if (exit_dmin == 0.0 || dist < exit_dmin) {exit_pos = cand_exit_pos; exit_dir = d; exit_dmin = dist;}
}
point &exit_conn(mp[exit_dir]);
unsigned exit_pipe_end_flags(2); // bend at the top only
float const exit_floor_zval(basement.z1() + fc_thickness); // on the bottom level floor
if (exit_pos[!dim] == exit_conn[!dim]) { // exit point is along the main pipe
if (exit_conn[dim] == exit_pos[dim]) { // exit is exactly at the pipe end
// maybe no valid wall was found above, and this location is also invalid; try with a vertical connection if this was the first attempt;
// this is the only situation where we can get to attempt=1
if (attempt == 0 && has_bcube_int(pipe_t(point(exit_pos.x, exit_pos.y, exit_floor_zval), exit_pos, r_main, 2, PIPE_EXIT, 0).get_bcube(), obstacles)) continue;
if (has_parking_garage) { // check that no parking spaces are blocked by this pipe
auto start(objs.begin() + objs_start);
for (auto i = start; i != objs.end(); ++i) {
if (i->type != TYPE_PARK_SPACE || !i->contains_pt_xy(exit_pos)) continue;
i->remove(); // remove this parking space
// now remove any car collider and curb associated with this parking space (placed before it, optional collider then optional curb)
auto cur(i);
if (cur > start && (cur-1)->type == TYPE_CURB ) {(cur-1)->remove(); --cur;}
if (cur > start && (cur-1)->type == TYPE_COLLIDER) {(cur-1)->remove();}
//break; // maybe can't break here because there may be a parking space to remove on another level?
} // for i
}