Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 4680 lines (4304 sloc) 242 KB
// 3D World - City Generation
// by Frank Gennari
// 2/10/18
#include "3DWorld.h"
#include "mesh.h"
#include "heightmap.h"
#include "file_utils.h"
#include "draw_utils.h"
#include "shaders.h"
#include "model3d.h"
#include "lightmap.h"
#include "buildings.h"
#include "tree_3dw.h"
#include "openal_wrap.h"
#include "explosion.h" // for add_blastr()
#include <cfloat> // for FLT_MAX
using std::string;
bool const CHECK_HEIGHT_BORDER_ONLY = 1; // choose building site to minimize edge discontinuity rather than amount of land that needs to be modified
float const ROAD_HEIGHT = 0.002;
float const OUTSIDE_TERRAIN_HEIGHT = 0.0;
float const CAR_LANE_OFFSET = 0.15; // in units of road width
float const CONN_ROAD_SPEED_MULT = 2.0; // twice the speed limit on connector roads
float const MIN_CAR_STOP_SEP = 0.25; // in units of car lengths
float const PARK_SPACE_WIDTH = 1.6;
float const PARK_SPACE_LENGTH = 1.8;
float const STREETLIGHT_BEAMWIDTH = 0.25;
float const CITY_LIGHT_FALLOFF = 0.2;
float const STREETLIGHT_ON_RAND = 0.05;
float const HEADLIGHT_ON_RAND = 0.1;
float const TRACKS_WIDTH = 0.5; // relative to road width
float const TUNNEL_WALL_THICK = 0.25; // relative to radius
vector3d const CAR_SIZE(0.30, 0.13, 0.08); // {length, width, height} in units of road width
unsigned const CONN_CITY_IX((1<<16)-1); // uint16_t max
enum {TID_SIDEWLAK=0, TID_STRAIGHT, TID_BEND_90, TID_3WAY, TID_4WAY, TID_PARK_LOT, TID_TRACKS, NUM_RD_TIDS};
enum {TYPE_PLOT =0, TYPE_RSEG, TYPE_ISEC2, TYPE_ISEC3, TYPE_ISEC4, TYPE_PARK_LOT, TYPE_TRACKS, NUM_RD_TYPES};
enum {TURN_NONE=0, TURN_LEFT, TURN_RIGHT, TURN_UNSPEC};
enum {INT_NONE=0, INT_ROAD, INT_PLOT, INT_PARKING};
enum {RTYPE_ROAD=0, RTYPE_TRACKS};
unsigned const CONN_TYPE_NONE = 0;
colorRGBA const road_colors[NUM_RD_TYPES] = {WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE}; // parking lots are darker than roads
int const FORCE_MODEL_ID = -1; // -1 disables
unsigned const NUM_CAR_COLORS = 10;
colorRGBA const car_colors[NUM_CAR_COLORS] = {WHITE, GRAY_BLACK, GRAY, ORANGE, RED, DK_RED, DK_BLUE, DK_GREEN, YELLOW, BROWN};
extern bool enable_dlight_shadows, dl_smap_enabled, tt_fire_button_down;
extern int rand_gen_index, display_mode, animate2, game_mode, frame_counter;
extern unsigned shadow_map_sz;
extern float water_plane_z, shadow_map_pcf_offset, cobj_z_bias, fticks, FAR_CLIP;
extern double tfticks;
extern vector<light_source> dl_sources;
extern tree_placer_t tree_placer;
void add_dynamic_lights_city(cube_t const &scene_bcube);
void disable_shadow_maps(shader_t &s);
vector3d get_tt_xlate_val();
struct car_model_t {
string fn;
int body_mat_id, fixed_color_id;
float xy_rot, dz, lod_mult, scale; // xy_rot in degrees
vector<unsigned> shadow_mat_ids;
car_model_t() : body_mat_id(-1), fixed_color_id(-1), xy_rot(0.0), dz(0.0), lod_mult(1.0), scale(1.0) {}
car_model_t(string const &fn_, int bmid, int fcid, float rot, float dz_, float lm, vector<unsigned> const &smids) :
fn(fn_), body_mat_id(bmid), fixed_color_id(fcid), xy_rot(rot), dz(dz_), lod_mult(lm), shadow_mat_ids(smids) {}
bool read(FILE *fp) { // filename body_material_id fixed_color_id xy_rot dz lod_mult shadow_mat_ids
assert(fp);
if (!read_string(fp, fn)) return 0;
if (!read_int(fp, body_mat_id)) return 0;
if (!read_int(fp, fixed_color_id)) return 0;
if (!read_float(fp, xy_rot)) return 0;
if (!read_float(fp, dz)) return 0;
if (!read_float(fp, scale)) return 0;
if (!read_float(fp, lod_mult) || lod_mult <= 0.0) return 0;
unsigned shadow_mat_id;
while (read_uint(fp, shadow_mat_id)) {shadow_mat_ids.push_back(shadow_mat_id);}
return 1;
}
};
struct city_params_t {
unsigned num_cities, num_samples, num_conn_tries, city_size_min, city_size_max, city_border, road_border, slope_width, num_rr_tracks;
float road_width, road_spacing, conn_road_seg_len, max_road_slope;
unsigned make_4_way_ints; // 0=all 3-way intersections; 1=allow 4-way; 2=all connector roads must have at least a 4-way on one end; 4=only 4-way (no straight roads)
// cars
unsigned num_cars;
float car_speed, traffic_balance_val, new_city_prob, max_car_scale;
bool enable_car_path_finding;
vector<car_model_t> car_model_files;
// parking lots
unsigned min_park_spaces, min_park_rows;
float min_park_density, max_park_density;
// lighting
bool car_shadows;
unsigned max_lights, max_shadow_maps;
// trees
unsigned max_trees_per_plot;
float tree_spacing;
// detail objects
unsigned max_benches_per_plot;
city_params_t() : num_cities(0), num_samples(100), num_conn_tries(50), city_size_min(0), city_size_max(0), city_border(0), road_border(0), slope_width(0),
num_rr_tracks(0), road_width(0.0), road_spacing(0.0), conn_road_seg_len(1000.0), max_road_slope(1.0), make_4_way_ints(0), num_cars(0), car_speed(0.0),
traffic_balance_val(0.5), new_city_prob(1.0), max_car_scale(1.0), enable_car_path_finding(0), min_park_spaces(12), min_park_rows(1), min_park_density(0.0),
max_park_density(1.0), car_shadows(0), max_lights(1024), max_shadow_maps(0), max_trees_per_plot(0), tree_spacing(1.0), max_benches_per_plot(0) {}
bool enabled() const {return (num_cities > 0 && city_size_min > 0);}
bool roads_enabled() const {return (road_width > 0.0 && road_spacing > 0.0);}
float get_road_ar() const {return nearbyint(road_spacing/road_width);} // round to nearest texture multiple
static bool read_error(string const &str) {cout << "Error reading city config option " << str << "." << endl; return 0;}
bool read_option(FILE *fp) {
char strc[MAX_CHARS] = {0};
if (!read_str(fp, strc)) return 0;
string const str(strc);
if (str == "num_cities") {
if (!read_uint(fp, num_cities)) {return read_error(str);}
}
else if (str == "num_rr_tracks") {
if (!read_uint(fp, num_rr_tracks)) {return read_error(str);}
}
else if (str == "num_samples") {
if (!read_uint(fp, num_samples) || num_samples == 0) {return read_error(str);}
}
else if (str == "num_conn_tries") {
if (!read_uint(fp, num_conn_tries) || num_conn_tries == 0) {return read_error(str);}
}
else if (str == "city_size_min") {
if (!read_uint(fp, city_size_min)) {return read_error(str);}
if (city_size_max == 0) {city_size_max = city_size_min;}
if (city_size_max < city_size_min) {return read_error(str);}
}
else if (str == "city_size_max") {
if (!read_uint(fp, city_size_max)) {return read_error(str);}
if (city_size_min == 0) {city_size_min = city_size_max;}
if (city_size_max < city_size_min) {return read_error(str);}
}
else if (str == "city_border") {
if (!read_uint(fp, city_border)) {return read_error(str);}
}
else if (str == "road_border") {
if (!read_uint(fp, road_border)) {return read_error(str);}
}
else if (str == "slope_width") {
if (!read_uint(fp, slope_width)) {return read_error(str);}
}
else if (str == "road_width") {
if (!read_float(fp, road_width) || road_width < 0.0) {return read_error(str);}
}
else if (str == "road_spacing") {
if (!read_float(fp, road_spacing) || road_spacing < 0.0) {return read_error(str);}
}
else if (str == "conn_road_seg_len") {
if (!read_float(fp, conn_road_seg_len) || conn_road_seg_len <= 0.0) {return read_error(str);}
}
else if (str == "max_road_slope") {
if (!read_float(fp, max_road_slope) || max_road_slope <= 0.0) {return read_error(str);}
}
else if (str == "make_4_way_ints") {
if (!read_uint(fp, make_4_way_ints) || make_4_way_ints > 3) {return read_error(str);}
}
// cars
else if (str == "num_cars") {
if (!read_uint(fp, num_cars)) {return read_error(str);}
}
else if (str == "car_speed") {
if (!read_float(fp, car_speed) || car_speed < 0.0) {return read_error(str);}
}
else if (str == "traffic_balance_val") {
if (!read_float(fp, traffic_balance_val) || traffic_balance_val > 1.0 || traffic_balance_val < 0.0) {return read_error(str);}
}
else if (str == "new_city_prob") {
if (!read_float(fp, new_city_prob) || new_city_prob < 0.0) {return read_error(str);}
}
else if (str == "enable_car_path_finding") {
if (!read_bool(fp, enable_car_path_finding)) {return read_error(str);}
}
else if (str == "car_model") {
car_model_t car_model;
if (!car_model.read(fp)) {return read_error(str);}
car_model_files.push_back(car_model);
max_eq(max_car_scale, car_model.scale);
}
// parking lots
else if (str == "min_park_spaces") { // with default road parameters, can be up to 28
if (!read_uint(fp, min_park_spaces)) {return read_error(str);}
}
else if (str == "min_park_rows") { // with default road parameters, can be up to 8
if (!read_uint(fp, min_park_rows)) {return read_error(str);}
}
else if (str == "min_park_density") {
if (!read_float(fp, min_park_density)) {return read_error(str);}
}
else if (str == "max_park_density") {
if (!read_float(fp, max_park_density) || max_park_density < 0.0) {return read_error(str);}
}
// lighting
else if (str == "max_lights") {
if (!read_uint(fp, max_lights)) {return read_error(str);}
}
else if (str == "max_shadow_maps") {
if (!read_uint(fp, max_shadow_maps)) {return read_error(str);}
}
else if (str == "car_shadows") {
if (!read_bool(fp, car_shadows)) {return read_error(str);}
}
// trees
else if (str == "max_trees_per_plot") {
if (!read_uint(fp, max_trees_per_plot)) {return read_error(str);}
}
else if (str == "tree_spacing") {
if (!read_float(fp, tree_spacing) || tree_spacing <= 0.0) {return read_error(str);}
}
// detail objects
else if (str == "max_benches_per_plot") {
if (!read_uint(fp, max_benches_per_plot)) {return read_error(str);}
}
else {
cout << "Unrecognized city keyword in input file: " << str << endl;
return 0;
}
return 1;
}
vector3d get_nom_car_size() const {return CAR_SIZE*road_width;}
vector3d get_max_car_size() const {return max_car_scale*get_nom_car_size();}
}; // city_params_t
city_params_t city_params;
template<typename T> void get_all_bcubes(vector<T> const &v, vector<cube_t> &bcubes) {
for (auto i = v.begin(); i != v.end(); ++i) {bcubes.push_back(*i);}
}
template<typename T> static void add_flat_road_quad(T const &r, quad_batch_draw &qbd, colorRGBA const &color, float ar) { // z1 == z2
float const z(r.z1());
point const pts[4] = {point(r.x1(), r.y1(), z), point(r.x2(), r.y1(), z), point(r.x2(), r.y2(), z), point(r.x1(), r.y2(), z)};
qbd.add_quad_pts(pts, color, plus_z, r.get_tex_range(ar));
}
float smooth_interp(float a, float b, float mix) {
mix = mix * mix * (3.0 - 2.0 * mix); // cubic Hermite interoplation (smoothstep)
return mix*a + (1.0 - mix)*b;
}
bool is_isect(unsigned type) {return (type >= TYPE_ISEC2 && type <= TYPE_ISEC4);}
int encode_neg_ix(unsigned ix) {return -(int(ix)+1);}
unsigned decode_neg_ix(int ix) {assert(ix < 0); return -(ix+1);}
bool is_night(float adj) {return (light_factor - adj < 0.5);} // for car headlights and streetlights
float rand_hash(float to_hash) {return fract(12345.6789*to_hash);}
float signed_rand_hash(float to_hash) {return 0.5*(rand_hash(to_hash) - 1.0);}
bool check_line_clip_update_t(point const &p1, point const &p2, float &t, cube_t const &c) {
float tmin(0.0), tmax(1.0);
if (get_line_clip(p1, p2, c.d, tmin, tmax) && tmin < t) {t = tmin; return 1;}
return 0;
}
class road_mat_mgr_t {
bool inited;
unsigned tids[NUM_RD_TIDS], sl_tid;
public:
road_mat_mgr_t() : inited(0), sl_tid(0) {}
void ensure_road_textures() {
if (inited) return;
timer_t timer("Load Road Textures");
string const img_names[NUM_RD_TIDS] = {"sidewalk.jpg", "straight_road.jpg", "bend_90.jpg", "int_3_way.jpg", "int_4_way.jpg", "parking_lot.png", "rail_tracks.jpg"};
float const aniso[NUM_RD_TIDS] = {4.0, 16.0, 8.0, 8.0, 8.0, 4.0, 16.0};
for (unsigned i = 0; i < NUM_RD_TIDS; ++i) {tids[i] = get_texture_by_name(("roads/" + img_names[i]), 0, 0, 1, aniso[i]);}
sl_tid = get_texture_by_name("roads/traffic_light.png");
inited = 1;
}
void set_texture(unsigned type) {
assert(type < NUM_RD_TYPES);
ensure_road_textures();
select_texture(tids[type]);
}
void set_stoplight_texture() {
ensure_road_textures();
select_texture(sl_tid);
}
};
road_mat_mgr_t road_mat_mgr;
void set_city_lighting_shader_opts(shader_t &s, cube_t const &lights_bcube, bool use_dlights, bool use_smap) {
if (use_dlights) {
s.add_uniform_vector3d("scene_llc", lights_bcube.get_llc()); // reset with correct values
s.add_uniform_vector3d("scene_scale", lights_bcube.get_size());
s.add_uniform_float("LT_DIR_FALLOFF", CITY_LIGHT_FALLOFF); // smooth falloff for car headlights and streetlights
}
if (use_smap) {
s.add_uniform_float("z_bias", cobj_z_bias);
s.add_uniform_float("pcf_offset", 8.0*shadow_map_pcf_offset);
s.add_uniform_float("dlight_pcf_offset", 0.0005);
}
}
void city_shader_setup(shader_t &s, bool use_dlights, bool use_smap, int use_bmap) {
cube_t const lights_bcube(get_city_lights_bcube());
use_dlights &= !lights_bcube.is_zero_area();
setup_smoke_shaders(s, 0.0, 0, 0, 0, 1, use_dlights, 0, 0, (use_smap ? 2 : 0), use_bmap, 0, use_dlights, 0, 0.0, 0.0, 0, 0, 1); // is_outside=1
set_city_lighting_shader_opts(s, lights_bcube, use_dlights, use_smap);
}
struct draw_state_t {
shader_t s;
vector3d xlate;
protected:
bool use_smap, use_bmap, shadow_only, use_dlights, emit_now;
point_sprite_drawer_sized light_psd; // for car/traffic lights
string label_str;
point label_pos;
public:
draw_state_t() : xlate(zero_vector), use_smap(0), use_bmap(0), shadow_only(0), use_dlights(0), emit_now(0) {}
virtual void draw_unshadowed() {}
void begin_tile(point const &pos, bool will_emit_now=0) {
emit_now = (use_smap && try_bind_tile_smap_at_point((pos + xlate), s));
if (will_emit_now && !emit_now) {disable_shadow_maps(s);} // not using shadow maps or second (non-shadow map) pass - disable shadow maps
}
void pre_draw(vector3d const &xlate_, bool use_dlights_, bool shadow_only_, bool always_setup_shader) {
xlate = xlate_;
shadow_only = shadow_only_;
use_dlights = (use_dlights_ && !shadow_only);
use_smap = (shadow_map_enabled() && !shadow_only);
if (!use_smap && !always_setup_shader) return;
city_shader_setup(s, use_dlights, use_smap, (use_bmap && !shadow_only));
}
virtual void post_draw() {
emit_now = 0;
if (s.is_setup()) {s.end_shader();} // use_smap case
ensure_shader_active();
draw_unshadowed();
s.end_shader();
}
void ensure_shader_active() {
if (s.is_setup()) return; // already active
if (shadow_only) {s.begin_color_only_shader();}
else {city_shader_setup(s, use_dlights, 0, use_bmap);} // no smap
}
void draw_and_clear_light_flares() {
if (light_psd.empty()) return; // no lights to draw
enable_blend();
set_additive_blend_mode();
light_psd.draw_and_clear(BLUR_TEX, 0.0, 0, 1, 0.005); // use geometry shader for unlimited point size
set_std_blend_mode();
disable_blend();
}
bool check_sphere_visible(point const &pos, float radius) const {return camera_pdu.sphere_visible_test((pos + xlate), radius);}
bool check_cube_visible(cube_t const &bc, float dist_scale=1.0, bool shadow_only=0) const {
cube_t const bcx(bc + xlate);
if (dist_scale > 0.0) {
float const dmax(shadow_only ? camera_pdu.far_ : dist_scale*get_draw_tile_dist());
if (!dist_less_than(camera_pdu.pos, bcx.closest_pt(camera_pdu.pos), dmax)) return 0;
}
return camera_pdu.cube_visible(bcx);
}
static void set_cube_pts(cube_t const &c, float z1f, float z1b, float z2f, float z2b, bool d, bool D, point p[8]) {
p[0][!d] = p[4][!d] = c.d[!d][1]; p[0][d] = p[4][d] = c.d[d][ D]; p[0].z = z1f; p[4].z = z2f; // front right
p[1][!d] = p[5][!d] = c.d[!d][0]; p[1][d] = p[5][d] = c.d[d][ D]; p[1].z = z1f; p[5].z = z2f; // front left
p[2][!d] = p[6][!d] = c.d[!d][0]; p[2][d] = p[6][d] = c.d[d][!D]; p[2].z = z1b; p[6].z = z2b; // back left
p[3][!d] = p[7][!d] = c.d[!d][1]; p[3][d] = p[7][d] = c.d[d][!D]; p[3].z = z1b; p[7].z = z2b; // back right
}
static void set_cube_pts(cube_t const &c, float z1, float z2, bool d, bool D, point p[8]) {set_cube_pts(c, z1, z1, z2, z2, d, D, p);}
static void set_cube_pts(cube_t const &c, bool d, bool D, point p[8]) {set_cube_pts(c, c.z1(), c.z2(), d, D, p);}
static void rotate_pts(point const &center, float sine_val, float cos_val, int d, int e, point p[8]) {
for (unsigned i = 0; i < 8; ++i) {
point &v(p[i]); // rotate p[i]
v -= center; // translate to origin
float const a(v[d]*cos_val - v[e]*sine_val), b(v[e]*cos_val + v[d]*sine_val);
v[d] = a; v[e] = b; // rotate
v += center; // translate back
}
}
void draw_cube(quad_batch_draw &qbd, color_wrapper const &cw, point const &center, point const p[8], bool skip_bottom, bool invert_normals=0, float tscale=0.0) const {
vector3d const cview_dir((camera_pdu.pos - xlate) - center);
float const sign(invert_normals ? -1.0 : 1.0);
vector3d const top_n (cross_product((p[2] - p[1]), (p[0] - p[1]))*sign); // Note: normalization not needed
vector3d const front_n(cross_product((p[5] - p[1]), (p[0] - p[1]))*sign);
vector3d const right_n(cross_product((p[6] - p[2]), (p[1] - p[2]))*sign);
tex_range_t tr_top, tr_front, tr_right;
if (tscale > 0.0) { // compute texture s/t parameters from cube side lengths to get a 1:1 AR
tr_top = tex_range_t(0.0, 0.0, tscale*(p[0] - p[1]).mag(), tscale*(p[2] - p[1]).mag());
tr_front = tex_range_t(0.0, 0.0, tscale*(p[0] - p[1]).mag(), tscale*(p[5] - p[1]).mag());
tr_right = tex_range_t(0.0, 0.0, tscale*(p[1] - p[2]).mag(), tscale*(p[6] - p[2]).mag());
}
if (dot_product(cview_dir, top_n) > 0) {qbd.add_quad_pts(p+4, cw, top_n, tr_top);} // top
else if (!skip_bottom) {qbd.add_quad_pts(p+0, cw, -top_n, tr_top);} // bottom - not always drawn
if (dot_product(cview_dir, front_n) > 0) {point const pts[4] = {p[0], p[1], p[5], p[4]}; qbd.add_quad_pts(pts, cw, front_n, tr_front);} // front
else {point const pts[4] = {p[2], p[3], p[7], p[6]}; qbd.add_quad_pts(pts, cw, -front_n, tr_front);} // back
if (dot_product(cview_dir, right_n) > 0) {point const pts[4] = {p[1], p[2], p[6], p[5]}; qbd.add_quad_pts(pts, cw, right_n, tr_right);} // right
else {point const pts[4] = {p[3], p[0], p[4], p[7]}; qbd.add_quad_pts(pts, cw, -right_n, tr_right);} // left
}
void draw_cube(quad_batch_draw &qbd, cube_t const &c, color_wrapper const &cw, bool skip_bottom, float tscale=0.0) const {
point pts[8];
set_cube_pts(c, 0, 0, pts);
draw_cube(qbd, cw, c.get_cube_center(), pts, skip_bottom, 0, tscale);
}
bool add_light_flare(point const &flare_pos, vector3d const &n, colorRGBA const &color, float alpha, float radius) {
point pos(xlate + flare_pos);
vector3d const view_dir((camera_pdu.pos - pos).get_norm());
float dp(dot_product(n, view_dir));
pos += 0.75*radius*view_dir; // move toward the camera, away from the stoplight, to prevent clipping
if (dp < 0.05) return 0; // back facing, skip
light_psd.add_pt(sized_vert_t<vert_color>(vert_color(pos, colorRGBA(color, dp*alpha)), radius));
return 1;
}
void set_label_text(string const &str, point const &pos) {label_str = str; label_pos = pos;}
void show_label_text() {
if (label_str.empty()) return;
text_drawer_t text_drawer;
text_drawer.strs.push_back(text_string_t(label_str, label_pos, 20.0, CYAN));
text_drawer.draw();
label_str.clear(); // drawn, clear for next frame
}
}; // draw_state_t
class city_road_gen_t;
struct car_t {
cube_t bcube, prev_bcube;
bool dim, dir, stopped_at_light, entering_city, in_tunnel, dest_valid, destroyed;
unsigned char cur_road_type, color_id, turn_dir, front_car_turn_dir, model_id;
unsigned short cur_city, cur_road, cur_seg, dest_city, dest_isec;
float height, dz, rot_z, turn_val, cur_speed, max_speed, waiting_pos, waiting_start;
car_t const *car_in_front;
car_t() : bcube(all_zeros), dim(0), dir(0), stopped_at_light(0), entering_city(0), in_tunnel(0), dest_valid(0), destroyed(0), cur_road_type(TYPE_RSEG),
color_id(0), turn_dir(TURN_NONE), front_car_turn_dir(TURN_UNSPEC), model_id(0), cur_city(0), cur_road(0), cur_seg(0), dest_city(0), dest_isec(0),
height(0.0), dz(0.0), rot_z(0.0), turn_val(0.0), cur_speed(0.0), max_speed(0.0), waiting_pos(0.0), waiting_start(0.0), car_in_front(nullptr) {}
bool is_valid() const {return !bcube.is_all_zeros();}
point get_center() const {return bcube.get_cube_center();}
unsigned get_orient() const {return (2*dim + dir);}
unsigned get_orient_in_isec() const {return (2*dim + (!dir));} // invert dir (incoming, not outgoing)
float get_max_speed() const {return ((cur_city == CONN_CITY_IX) ? CONN_ROAD_SPEED_MULT : 1.0)*max_speed;}
float get_length() const {return (bcube.d[ dim][1] - bcube.d[ dim][0]);}
float get_width () const {return (bcube.d[!dim][1] - bcube.d[!dim][0]);}
float get_max_lookahead_dist() const {return (get_length() + city_params.road_width);} // extend one car length + one road width in front
bool is_almost_stopped() const {return (cur_speed < 0.1*max_speed);}
bool is_stopped () const {return (cur_speed == 0.0);}
bool is_parked () const {return (max_speed == 0.0);}
bool in_isect () const {return is_isect(cur_road_type);}
bool headlights_on() const {return(!is_parked() && (in_tunnel || is_night(HEADLIGHT_ON_RAND*signed_rand_hash(height + max_speed))));} // no headlights when parked
unsigned get_isec_type() const {assert(in_isect()); return (cur_road_type - TYPE_ISEC2);}
void park() {cur_speed = max_speed = 0.0;}
float get_turn_rot_z(float dist_to_turn) const {return (1.0 - CLIP_TO_01(4.0f*fabs(dist_to_turn)/city_params.road_width));}
float get_wait_time_secs () const {return (float(tfticks) - waiting_start)/TICKS_PER_SECOND;} // Note: only meaningful for cars stopped at lights
colorRGBA const &get_color() const {assert(color_id < NUM_CAR_COLORS); return car_colors[color_id];}
void apply_scale(float scale) {
if (scale == 1.0) return; // no scale
float const prev_height(height);
height *= scale;
point const pos(get_center());
bcube.z2() += height - prev_height; // z1 is unchanged
float const dx(bcube.x2() - pos.x), dy(bcube.y2() - pos.y);
bcube.x1() = pos.x - scale*dx; bcube.x2() = pos.x + scale*dx;
bcube.y1() = pos.y - scale*dy; bcube.y2() = pos.y + scale*dy;
}
void destroy() { // Note: not calling create_explosion(), so no chain reactions
point const pos(get_center() + get_tiled_terrain_model_xlate());
float const length(get_length());
static rand_gen_t rgen;
for (int n = 0; n < rgen.rand_int(3, 5); ++n) {
vector3d off(rgen.signed_rand_vector_spherical()*(0.5*length));
off.z = abs(off.z); // not into the ground
point const exp_pos(pos + off);
float const radius(rgen.rand_uniform(1.0, 1.5)*length), time(rgen.rand_uniform(0.3, 0.8));
add_blastr(exp_pos, (exp_pos - get_camera_pos()), radius, 0.0, time*TICKS_PER_SECOND, CAMERA_ID, YELLOW, RED, ETYPE_ANIM_FIRE, nullptr, 1);
gen_smoke(exp_pos, 1.0, rgen.rand_uniform(0.4, 0.6));
} // for n
gen_delayed_from_player_sound(SOUND_EXPLODE, pos, 1.0);
park();
destroyed = 1;
}
float get_min_sep_dist_to_car(car_t const &c, bool add_one_car_len=0) const {
float const avg_len(0.5*(get_length() + c.get_length())); // average length of the two cars
float const min_speed(max(0.0f, (min(cur_speed, c.cur_speed) - 0.1f*max_speed))); // relative to max speed of 1.0, clamped to 10% at bottom end for stability
return avg_len*(MIN_CAR_STOP_SEP + 1.11*min_speed + (add_one_car_len ? 1.0 : 0.0)); // 25% to 125% car length, depending on speed (2x on connector roads)
}
string str() const {
std::ostringstream oss;
oss << "Car " << TXT(dim) << TXT(dir) << TXT(cur_city) << TXT(cur_road) << TXT(cur_seg) << TXT(dz) << TXT(max_speed) << TXT(cur_speed)
<< TXTi(cur_road_type) << TXTi(color_id) << " bcube=" << bcube.str();
return oss.str();
}
string label_str() const {
std::ostringstream oss;
oss << TXT(dim) << TXTn(dir) << TXT(cur_city) << TXT(cur_road) << TXTn(cur_seg) << TXT(dz) << TXTn(turn_val) << TXT(max_speed) << TXTn(cur_speed)
<< "wait_time=" << get_wait_time_secs() << "\n" << TXTin(cur_road_type)
<< TXTn(stopped_at_light) << TXTn(in_isect()) << "cars_in_front=" << count_cars_in_front() << "\n" << TXT(dest_city) << TXTn(dest_isec);
oss << "car=" << this << " car_in_front=" << car_in_front << endl; // debugging
return oss.str();
}
void move(float speed_mult) {
prev_bcube = bcube;
if (destroyed || stopped_at_light || is_stopped()) return;
assert(speed_mult >= 0.0 && cur_speed > 0.0 && cur_speed <= CONN_ROAD_SPEED_MULT*max_speed); // Note: must be valid for connector road => city transitions
float dist(cur_speed*speed_mult);
if (dz != 0.0) {dist *= min(1.25, max(0.75, (1.0 - 0.5*dz/get_length())));} // slightly faster down hills, slightly slower up hills
min_eq(dist, 0.25f*city_params.road_width); // limit to half a car length to prevent cars from crossing an intersection in a single frame
move_by(dir ? dist : -dist);
// update waiting state
float const cur_pos(bcube.d[dim][dir]);
if (fabs(cur_pos - waiting_pos) > get_length()) {waiting_pos = cur_pos; waiting_start = tfticks;} // update when we move at least a car length
}
void maybe_accelerate(float mult=0.02) {
if (car_in_front) {
float const dist_sq(p2p_dist_xy_sq(get_center(), car_in_front->get_center())), length(get_length());
if (dist_sq > length*length) { // if cars are colliding, let the collision detection system handle it
float const dmin(get_min_sep_dist_to_car(*car_in_front, 1)); // add_one_car_len=1; space between the two car centers
if (dist_sq < dmin*dmin) {decelerate(mult); return;} // too close to the car in front - decelerate instead
}
}
accelerate(mult);
}
void accelerate(float mult=0.02) {cur_speed = min(get_max_speed(), (cur_speed + mult*fticks*max_speed));}
void decelerate(float mult=0.05) {cur_speed = max(0.0f, (cur_speed - mult*fticks*max_speed));}
void decelerate_fast() {decelerate(10.0);} // Note: large decel to avoid stopping in an intersection
void stop() {cur_speed = 0.0;} // immediate stop
void move_by(float val) {bcube.d[dim][0] += val; bcube.d[dim][1] += val;}
bool check_collision(car_t &c, city_road_gen_t const &road_gen);
bool proc_sphere_coll(point &pos, point const &p_last, float radius, vector3d const &xlate, vector3d *cnorm) const;
point get_front(float dval=0.5) const {
point car_front(get_center());
car_front[dim] += (dir ? dval : -dval)*get_length(); // half length
return car_front;
}
bool front_intersects_car(car_t const &c) const {
return (c.bcube.contains_pt(get_front(0.25)) || c.bcube.contains_pt(get_front(0.5))); // check front-middle and very front
}
void honk_horn_if_close() const {
point const pos(get_center());
if (dist_less_than((pos + get_tiled_terrain_model_xlate()), get_camera_pos(), 1.0)) {gen_sound(SOUND_HORN, pos);}
}
void honk_horn_if_close_and_fast() const {
if (cur_speed > 0.25*max_speed) {honk_horn_if_close();}
}
void on_alternate_turn_dir(rand_gen_t &rgen) {
honk_horn_if_close();
if ((rgen.rand()&3) == 0) {dest_valid = 0;} // 25% chance of choosing a new destination rather than driving in circles; will be in current city
}
void register_adj_car(car_t &c) {
if (car_in_front != nullptr && p2p_dist_xy_sq(get_center(), c.get_center()) > p2p_dist_xy_sq(get_center(), car_in_front->get_center())) return; // already found a closer car
cube_t cube(bcube);
cube.d[dim][!dir] = cube.d[dim][dir];
cube.d[dim][dir] += (dir ? 1.0 : -1.0)*get_max_lookahead_dist();
if (cube.intersects_xy(c.bcube)) {car_in_front = &c;} // projected cube intersects other car
}
unsigned count_cars_in_front(cube_t const &range=cube_t(all_zeros)) const {
unsigned num(0);
car_t const *cur_car(this);
for (unsigned i = 0; i < 50; ++i) { // limit iterations
cur_car = cur_car->car_in_front;
if (!cur_car || (!range.is_all_zeros() && !range.contains_pt_xy(cur_car->get_center()))) break;
if (cur_car->dim != dim || cur_car->dir == dir) {++num;} // include if not going in opposite direction
}
return num;
}
float get_sum_len_space_for_cars_in_front(cube_t const &range) const {
float len(0.0);
car_t const *cur_car(this);
for (unsigned i = 0; i < 50; ++i) { // limit iterations; avg len = city_params.get_nom_car_size().x (FIXME: 50 not high enough for connector roads)
if (cur_car->dim != dim || cur_car->dir == dir) {len += cur_car->get_length();} // include if not going in opposite direction
cur_car = cur_car->car_in_front;
if (!cur_car || !range.contains_pt_xy(cur_car->get_center())) break;
}
return len * (1.0 + MIN_CAR_STOP_SEP); // car length + stopped space (including one extra space for the car behind us)
}
};
struct comp_car_road_then_pos {
vector3d const &xlate;
comp_car_road_then_pos(vector3d const &xlate_) : xlate(xlate_) {}
bool operator()(car_t const &c1, car_t const &c2) const { // sort spatially for collision detection and drawing
if (c1.cur_city != c2.cur_city) return (c1.cur_city < c2.cur_city);
if (c1.is_parked() != c2.is_parked()) {return c2.is_parked();} // parked cars last
if (c1.cur_road != c2.cur_road) return (c1.cur_road < c2.cur_road);
if (c1.is_parked()) { // sort parked cars back to front relative to camera so that alpha blending works
return (p2p_dist_sq((c1.bcube.get_cube_center() + xlate), camera_pdu.pos) > p2p_dist_sq((c2.bcube.get_cube_center() + xlate), camera_pdu.pos));
}
return (c1.bcube.d[c1.dim][c1.dir] < c2.bcube.d[c2.dim][c2.dir]); // compare front end of car (used for collisions)
}
};
struct rect_t {
unsigned x1, y1, x2, y2;
rect_t() : x1(0), y1(0), x2(0), y2(0) {}
rect_t(unsigned x1_, unsigned y1_, unsigned x2_, unsigned y2_) : x1(x1_), y1(y1_), x2(x2_), y2(y2_) {}
bool is_valid() const {return (x1 < x2 && y1 < y2);}
unsigned get_area() const {return (x2 - x1)*(y2 - y1);}
bool operator== (rect_t const &r) const {return (x1 == r.x1 && y1 == r.y1 && x2 == r.x2 && y2 == r.y2);}
bool has_overlap(rect_t const &r) const {return (x1 < r.x2 && y1 < r.y2 && r.x1 < x2 && r.y1 < y2);}
};
struct flatten_op_t : public rect_t {
float z1, z2;
bool dim;
unsigned border, skip_six, skip_eix;
flatten_op_t() : z1(0.0), z2(0.0), dim(0), border(0) {}
flatten_op_t(unsigned x1_, unsigned y1_, unsigned x2_, unsigned y2_, float z1_, float z2_, bool dim_, unsigned border_) :
rect_t(x1_, y1_, x2_, y2_), z1(z1_), z2(z2_), dim(dim_), border(border_), skip_six(0), skip_eix(0) {}
};
struct road_t : public cube_t {
unsigned road_ix;
//unsigned char type; // road, railroad, etc. {RTYPE_ROAD, RTYPE_TRACKS}
bool dim; // dim the road runs in
bool slope; // 0: z1 applies to first (lower) point; 1: z1 applies to second (upper) point
road_t(cube_t const &c, bool dim_, bool slope_=0, unsigned road_ix_=0) : cube_t(c), road_ix(road_ix_), dim(dim_), slope(slope_) {}
road_t(point const &s, point const &e, float width, bool dim_, bool slope_=0, unsigned road_ix_=0) : road_ix(road_ix_), dim(dim_), slope(slope_) {
assert(s != e);
assert(width > 0.0);
vector3d const dw(0.5*width*cross_product((e - s), plus_z).get_norm());
point const pts[4] = {(s - dw), (s + dw), (e + dw), (e - dw)};
set_from_points(pts, 4);
}
float get_length () const {return (d[ dim][1] - d[ dim][0]);}
float get_width () const {return (d[!dim][1] - d[!dim][0]);}
float get_slope_val() const {return get_dz()/get_length();}
float get_start_z () const {return (slope ? z2() : z1());}
float get_end_z () const {return (slope ? z1() : z2());}
float get_z_adj () const {return (ROAD_HEIGHT + 0.5*get_slope_val()*(dim ? DY_VAL : DX_VAL));} // account for a half texel of error for sloped roads
tex_range_t get_tex_range(float ar) const {return tex_range_t(0.0, 0.0, -ar, (dim ? -1.0 : 1.0), 0, dim);}
cube_t const &get_bcube() const {return *this;}
cube_t &get_bcube() {return *this;}
void add_road_quad(quad_batch_draw &qbd, colorRGBA const &color, float ar) const { // specialized here for sloped roads (road segments and railroad tracks)
if (z1() == z2()) {add_flat_road_quad(*this, qbd, color, ar); return;}
bool const s(slope ^ dim);
point pts[4] = {point(x1(), y1(), d[2][!s]), point(x2(), y1(), d[2][!s]), point(x2(), y2(), d[2][ s]), point(x1(), y2(), d[2][ s])};
if (!dim) {swap(pts[0].z, pts[2].z);}
vector3d const normal(cross_product((pts[2] - pts[1]), (pts[0] - pts[1])).get_norm());
qbd.add_quad_pts(pts, color, normal, get_tex_range(ar));
}
};
struct road_seg_t : public road_t {
unsigned short road_ix, conn_ix[2], conn_type[2]; // {dim=0, dim=1}
mutable unsigned short car_count; // can be written to during car update logic
void init_ixs() {conn_ix[0] = conn_ix[1] = 0; conn_type[0] = conn_type[1] = CONN_TYPE_NONE;}
road_seg_t(road_t const &r, unsigned rix) : road_t(r), road_ix(rix), car_count(0) {init_ixs();}
road_seg_t(cube_t const &c, unsigned rix, bool dim_, bool slope_=0) : road_t(c, dim_, slope_), road_ix(rix), car_count(0) {init_ixs();}
void next_frame() {car_count = 0;}
};
namespace stoplight_ns {
enum {GREEN_LIGHT=0, YELLOW_LIGHT, RED_LIGHT}; // colors, unused (only have stop and go states anyway)
enum {EGL=0, EGWG, WGL, NGL, NGSG, SGL, NUM_STATE}; // E=car moving east, W=west, N=sorth, S=south, G=straight|right, L=left turn
enum {CW_WALK=0, CW_WARN, CW_STOP}; // crosswalk state
float const state_times[NUM_STATE] = {5.0, 6.0, 5.0, 5.0, 6.0, 5.0}; // in seconds
unsigned const st_r_orient_masks[NUM_STATE] = {2, 3, 1, 8, 12, 4}; // {W=1, E=2, S=4, N=8}, for straight and right turns
unsigned const left_orient_masks[NUM_STATE] = {2, 0, 1, 8, 0, 4}; // {W=1, E=2, S=4, N=8}, for left turns only
unsigned const to_right [4] = {3, 2, 0, 1}; // {N, S, W, E}
unsigned const to_left [4] = {2, 3, 1, 0}; // {S, N, E, W}
unsigned const other_lane[4] = {1, 0, 3, 2}; // {E, W, N, S}
unsigned const conn_left[4] = {3,2,0,1}, conn_right[4] = {2,3,1,0};
colorRGBA const stoplight_colors[3] = {GREEN, YELLOW, RED};
colorRGBA const crosswalk_colors[3] = {WHITE, ORANGE, ORANGE};
rand_gen_t stoplight_rgen;
class stoplight_t {
uint8_t num_conn, conn, cur_state;
bool at_conn_road; // longer light times in this case
float cur_state_ticks;
// these are mutable because they are set during car update logic, where roads are supposed to be const
mutable uint8_t car_waiting_sr, car_waiting_left;
mutable bool blocked[4]; // Note: 4 bit flags corresponding to conn bits
void next_state() {
++cur_state;
if (cur_state == NUM_STATE) {cur_state = 0;} // wraparound
}
void advance_state() {
if (num_conn == 4) {next_state();} // all states are valid for 4-way intersections
else { // 3-way intersection
assert(num_conn == 3);
while (1) {
next_state();
bool valid(0);
switch (conn) {
case 7 : {bool const allow[6] = {0,1,1,1,0,0}; valid = allow[cur_state]; break;} // no +y / S
case 11: {bool const allow[6] = {1,1,0,0,0,1}; valid = allow[cur_state]; break;} // no -y / N
case 13: {bool const allow[6] = {1,0,0,1,1,0}; valid = allow[cur_state]; break;} // no +x / W
case 14: {bool const allow[6] = {0,0,1,0,1,1}; valid = allow[cur_state]; break;} // no -x / E
default: assert(0);
}
if (valid) break;
} // end while
}
cur_state_ticks = 0.0; // reset for this state
}
bool any_blocked() const {return (blocked[0] || blocked[1] || blocked[2] || blocked[3]);}
bool is_any_car_waiting_at_this_state() const {
if (num_conn == 2) return 0; // 2-way intersection, no cross traffic
return ((left_orient_masks[cur_state] & car_waiting_left) || (st_r_orient_masks[cur_state] & car_waiting_sr));
}
void find_state_with_waiting_car() {
uint8_t const prev_state(cur_state);
while (1) {
advance_state();
if (any_blocked()) break; // if some car is blocking the intersection in some dir, force all states (no skipped lights) to guarantee we can make progress
if (is_any_car_waiting_at_this_state()) break; // car is waiting at this state
if (cur_state == prev_state) {advance_state(); break;} // wrapped around, leave at the next valid state after the prev state
}
car_waiting_sr = car_waiting_left = 0; // waiting bits have been used, clear for next state change
}
void run_update_logic() {
assert(cur_state < NUM_STATE);
//if (cur_state_ticks > get_cur_state_time_secs()) {advance_state();} // time to update to next state
if (cur_state_ticks > get_cur_state_time_secs()) {find_state_with_waiting_car();} // time to update to next state
}
float get_cur_state_time_secs() const {return (at_conn_road ? 2.0 : 1.0)*TICKS_PER_SECOND*state_times[cur_state];}
void ffwd_to_future(float time_secs) {
cur_state_ticks += TICKS_PER_SECOND*time_secs;
run_update_logic();
}
public:
stoplight_t(bool at_conn_road_) : num_conn(0), conn(0), cur_state(RED_LIGHT), at_conn_road(at_conn_road_), cur_state_ticks(0.0), car_waiting_sr(0), car_waiting_left(0) {
reset_blocked();
}
void reset_blocked() {UNROLL_4X(blocked[i_] = 0;)}
void mark_blocked(bool dim, bool dir) const {blocked[2*dim + dir] = 1;} // Note: not actually const, but blocked is mutable
bool is_blocked(bool dim, bool dir) const {return (blocked[2*dim + dir] != 0);}
void init(uint8_t num_conn_, uint8_t conn_) {
num_conn = num_conn_; conn = conn_;
if (num_conn == 2) return; // nothing else to do
cur_state = stoplight_rgen.rand() % NUM_STATE; // start at a random state
advance_state(); // make sure cur_state is valid
cur_state_ticks = get_cur_state_time_secs()*stoplight_rgen.rand_float(); // start at a random time within this state
}
void next_frame() {
reset_blocked();
if (num_conn == 2) return; // nothing else to do
cur_state_ticks += fticks;
run_update_logic();
}
void notify_waiting_car(bool dim, bool dir, unsigned turn) const {
unsigned const orient(2*dim + dir); // {W, E, S, N}
((turn == TURN_LEFT) ? car_waiting_left : car_waiting_sr) |= (1 << orient);
}
bool red_light(bool dim, bool dir, unsigned turn) const {
assert(cur_state < NUM_STATE);
assert(turn == TURN_LEFT || turn == TURN_RIGHT || turn == TURN_NONE);
if (num_conn == 2) return 0; // 2-way intersection, no cross traffic
unsigned const mask(((turn == TURN_LEFT) ? left_orient_masks : st_r_orient_masks)[cur_state]);
unsigned const orient(2*dim + dir); // {W, E, S, N}
return (((1<<orient) & mask) == 0);
}
unsigned get_light_state(bool dim, bool dir, unsigned turn) const { // 0=green, 1=yellow, 2=red
if (red_light(dim, dir, turn)) return RED_LIGHT;
if (num_conn == 2) return GREEN_LIGHT;
stoplight_t future_self(*this);
future_self.ffwd_to_future(2.0); // yellow light time = 2.0s
return (future_self.red_light(dim, dir, turn) ? YELLOW_LIGHT : GREEN_LIGHT);
}
bool can_walk(bool dim, bool dir) const { // Note: symmetric across the two sides of the street
// Note: ignores blocked state and right-on-red
unsigned const orient(2*dim + dir); // {W, E, S, N}
assert(conn & (1<<orient));
// check for cars entering the isec from this side
if ((1<<orient) & (left_orient_masks[cur_state] | st_r_orient_masks[cur_state])) return 0; // any turn dir
// check for cars entering the isec from the other side; Note that we don't check conn mask here because unconnected dirs don't get green lights
if (st_r_orient_masks[cur_state] & (1<<other_lane[orient])) return 0; // opposing traffic going straight
if (left_orient_masks[cur_state] & (1<<to_right [orient])) return 0; // traffic to the right turning left
//if (st_r_orient_masks[cur_state] & (1<<to_left [orient])) return 0; // traffic to the left turning right (skip)
return 1;
}
unsigned get_crosswalk_state(bool dim, bool dir) const {
if (!can_walk(dim, dir)) return CW_STOP;
stoplight_t future_self(*this);
future_self.ffwd_to_future(2.0); // crosswalk warn time = 2.0s
if (!future_self.can_walk(dim, dir)) return CW_WARN;
return CW_WALK;
}
bool check_int_clear(unsigned orient, unsigned turn_dir) const { // check for cars on other lanes blocking the intersection
switch (turn_dir) { // orient: {W, E, S, N}
case TURN_NONE: return (!blocked[to_right[orient]] && !blocked[to_left[orient]]); // straight
case TURN_LEFT: return (!blocked[to_right[orient]] && !blocked[to_left[orient]] && !blocked[other_lane[orient]]);
case TURN_RIGHT: return (!blocked[to_right[orient]]);
}
return 1;
}
bool check_int_clear(car_t const &car) const {return check_int_clear(car.get_orient(), car.turn_dir);}
bool can_turn_right_on_red(car_t const &car) const { // check for legal right on red (no other lanes turning into the road to our right)
if (car.turn_dir != TURN_RIGHT) return 0;
unsigned const orient(car.get_orient()); // {W, E, S, N}
if (!red_light(!car.dim, (to_right [orient] & 1), TURN_NONE)) return 0; // traffic to our left has a green or yellow light for going straight
if (!red_light( car.dim, (other_lane[orient] & 1), TURN_LEFT)) return 0; // opposing traffic has a green or yellow light for turning left
// Note: there are no U-turns, so we don't need to worry about
return 1; // can turn right on red to_left[orient]
}
string str() const {
std::ostringstream oss;
oss << TXTi(num_conn) << TXTi(conn) << TXTi(cur_state) << TXT(at_conn_road) << TXT(cur_state_ticks) << TXTi(car_waiting_sr) << TXTi(car_waiting_left)
<< "blocked: " << blocked[0] << blocked[1] << blocked[2] << blocked[3];
return oss.str();
}
string label_str() const {
std::ostringstream oss;
oss << TXTi(num_conn) << TXTin(conn) << TXTi(cur_state) << TXTn(cur_state_ticks) << TXTn(at_conn_road) << TXTi(car_waiting_sr) << TXTin(car_waiting_left)
<< "blocked: " << blocked[0] << blocked[1] << blocked[2] << blocked[3];
return oss.str();
}
colorRGBA get_stoplight_color(bool dim, bool dir, unsigned turn) const {return stoplight_colors[get_light_state(dim, dir, turn)];}
};
static float stoplight_max_height() {return 9.2*(0.03*city_params.road_width);} // 2.0*h + 1.2*h*num_segs
} // end stoplight_ns
struct road_isec_t : public cube_t {
uint8_t num_conn, conn; // connected roads in {-x, +x, -y, +y} = {W, E, S, N} facing = car traveling {E, W, N, S}
short conn_to_city;
short rix_xy[4], conn_ix[4]; // road/segment index: pos=cur city road, neg=global road; always segment ix
stoplight_ns::stoplight_t stoplight; // Note: not always needed, maybe should be by pointer/index?
road_isec_t(cube_t const &c, int rx, int ry, uint8_t conn_, bool at_conn_road, short conn_to_city_=-1) : cube_t(c), conn(conn_), conn_to_city(conn_to_city_), stoplight(at_conn_road) {
rix_xy[0] = rix_xy[1] = rx; rix_xy[2] = rix_xy[3] = ry; conn_ix[0] = conn_ix[1] = conn_ix[2] = conn_ix[3] = 0;
if (conn == 15) {num_conn = 4;} // 4-way
else if (conn == 7 || conn == 11 || conn == 13 || conn == 14) {num_conn = 3;} // 3-way
else if (conn == 5 || conn == 6 || conn == 9 || conn == 10) {num_conn = 2;} // 2-way
else {assert(0);}
stoplight.init(num_conn, conn);
}
tex_range_t get_tex_range(float ar) const {
switch (conn) {
case 5 : return tex_range_t(0.0, 0.0, -1.0, 1.0, 0, 0); // 2-way: MX
case 6 : return tex_range_t(0.0, 0.0, 1.0, 1.0, 0, 0); // 2-way: R0
case 9 : return tex_range_t(0.0, 0.0, -1.0, -1.0, 0, 0); // 2-way: MXMY
case 10: return tex_range_t(0.0, 0.0, 1.0, -1.0, 0, 0); // 2-way: MY
case 7 : return tex_range_t(0.0, 0.0, 1.0, 1.0, 0, 0); // 3-way: R0
case 11: return tex_range_t(0.0, 0.0, -1.0, -1.0, 0, 0); // 3-way: MY
case 13: return tex_range_t(0.0, 0.0, 1.0, -1.0, 0, 1); // 3-way: R90MY
case 14: return tex_range_t(0.0, 0.0, -1.0, 1.0, 0, 1); // 3-way: R90MX
case 15: return tex_range_t(0.0, 0.0, 1.0, 1.0, 0, 0); // 4-way: R0
default: assert(0);
}
return tex_range_t(0.0, 0.0, 1.0, 1.0); // never gets here
}
void make_4way(unsigned conn_to_city_) {
num_conn = 4; conn = 15;
assert(conn_to_city < 0); conn_to_city = conn_to_city_;
stoplight.init(num_conn, conn); // re-init with new conn
}
void next_frame() {stoplight.next_frame();}
void notify_waiting_car(car_t const &car) const {stoplight.notify_waiting_car(car.dim, car.dir, car.turn_dir);}
bool is_global_conn_int() const {return (rix_xy[0] < 0 || rix_xy[1] < 0 || rix_xy[2] < 0 || rix_xy[3] < 0);}
bool red_light(car_t const &car) const {return stoplight.red_light(car.dim, car.dir, car.turn_dir);}
bool red_or_yellow_light(car_t const &car) const {return (stoplight.get_light_state(car.dim, car.dir, car.turn_dir) != stoplight_ns::GREEN_LIGHT);}
bool yellow_light(car_t const &car) const {return (stoplight.get_light_state(car.dim, car.dir, car.turn_dir) == stoplight_ns::YELLOW_LIGHT);}
bool can_go_based_on_light(car_t const &car) const {
return (!red_or_yellow_light(car) || stoplight.can_turn_right_on_red(car)); // green light or right turn on red
}
bool is_orient_currently_valid(unsigned orient, unsigned turn_dir) const {
if (!(conn & (1<<orient))) return 0; // no road connection in this orient
//if (!can_go_based_on_light(car)) return 1; // Note: can't call this without more car info
//return stoplight.check_int_clear(orient, turn_dir); // intersection not clear (Note: too strong of a check, blocking car may be exiting the intersection)
return 1;
}
unsigned get_dest_orient_for_car_in_isec(car_t const &car, bool is_entering) const {
unsigned const orient_in(car.get_orient_in_isec()); // invert dir (incoming, not outgoing)
//cout << TXT(car.rot_z) << TXT(car.turn_val) << TXT(unsigned(car.turn_dir)) << TXT(car.dim) << TXT(car.dir) << TXT(orient_in) << hex << unsigned(conn) << dec << endl;
if (is_entering) {assert(conn & (1<<orient_in));} // car must come from an enabled orient
unsigned new_orient(0);
switch (car.turn_dir) {
case TURN_NONE: new_orient = car.get_orient(); break;
case TURN_LEFT: new_orient = stoplight_ns::conn_left [orient_in]; break;
case TURN_RIGHT: new_orient = stoplight_ns::conn_right[orient_in]; break;
default: assert(0);
}
assert(conn & (1<<new_orient)); // car mustto go an enabled orient
return new_orient;
}
bool can_go_now(car_t const &car) const {
if (!can_go_based_on_light(car)) return 0;
if (stoplight.check_int_clear(car)) return 1;
if ((frame_counter&15) == 0) {car.honk_horn_if_close_and_fast();} // honk every so often
return 0; // intersection not clear
}
bool is_blocked(car_t const &car) const {
return (can_go_based_on_light(car) && !stoplight.check_int_clear(car)); // light is green but intersection is blocked
}
bool has_left_turn_signal(unsigned orient) const {
if (num_conn == 2) return 0; // never
if (num_conn == 4) return 1; // always
assert(num_conn == 3);
switch (conn) {
case 7 : return (orient == 1 || orient == 2);
case 11: return (orient == 0 || orient == 3);
case 13: return (orient == 0 || orient == 2);
case 14: return (orient == 1 || orient == 3);
default: assert(0);
}
return 0;
}
cube_t get_stoplight_cube(unsigned n) const { // Note: mostly duplicated with draw_stoplights(), but difficult to factor the code out and share it
assert(conn & (1<<n));
float const sz(0.03*city_params.road_width), h(1.0*sz);
bool const dim((n>>1) != 0), dir((n&1) == 0), side((dir^dim^1) != 0); // Note: dir is inverted here to represent car dir
float const zbot(z1() + 2.0*h), dim_pos(d[dim][!dir] + (dir ? sz : -sz)); // location in road dim
float const v1(d[!dim][side]), v2(v1 + (side ? -sz : sz)); // location in other dim
unsigned const num_segs(has_left_turn_signal(n) ? 6 : 3);
float const sl_top(zbot + 1.2*h*num_segs), sl_lo(min(v1, v2) - 0.25*sz), sl_hi(max(v1, v2) + 0.25*sz);
cube_t c;
c.z1() = z1(); c.z2() = sl_top;
c.d[ dim][0] = dim_pos - (dir ? -0.04 : 0.5)*sz; c.d[dim][1] = dim_pos + (dir ? 0.5 : -0.04)*sz;
c.d[!dim][0] = sl_lo; c.d[!dim][1] = sl_hi;
return c;
}
bool proc_sphere_coll(point &pos, point const &p_last, float radius, vector3d const &xlate, float dist, vector3d *cnorm) const {
if (num_conn == 2) return 0; // no stoplights
if (!sphere_cube_intersect_xy(pos, (radius + dist), (*this + xlate))) return 0;
for (unsigned n = 0; n < 4; ++n) {
if (!(conn & (1<<n))) continue; // no road in this dir
if (sphere_cube_int_update_pos(pos, radius, (get_stoplight_cube(n) + xlate), p_last, 1, 0, cnorm)) return 1; // typically only one coll, just return the first one
}
return 0;
}
bool line_intersect(point const &p1, point const &p2, float &t) const {
if (num_conn == 2) return 0; // no stoplights
cube_t c(*this); // deep copy
c.z2() += stoplight_ns::stoplight_max_height();
if (!c.line_intersects(p1, p2)) return 0;
bool ret(0);
for (unsigned n = 0; n < 4; ++n) {
if (conn & (1<<n)) {ret |= check_line_clip_update_t(p1, p2, t, get_stoplight_cube(n));}
}
return ret;
}
void draw_sl_block(quad_batch_draw &qbd, draw_state_t &dstate, point p[4], float h, unsigned state, bool draw_unlit, float flare_alpha, vector3d const &n, tex_range_t const &tr) const {
for (unsigned j = 0; j < 3; ++j) {
colorRGBA const &color(stoplight_ns::stoplight_colors[j]);
if (j == state) {
qbd.add_quad_pts(p, color, n, tr);
if (flare_alpha > 0.0) {dstate.add_light_flare(0.25*(p[0] + p[1] + p[2] + p[3]), n, color, flare_alpha, 2.0*h);}
}
else if (draw_unlit) {
qbd.add_quad_pts(p, (color + WHITE)*0.05, n, tr);
}
for (unsigned e = 0; e < 4; ++e) {p[e].z += 1.2*h;}
}
}
void draw_stoplights(quad_batch_draw &qbd, draw_state_t &dstate, bool shadow_only) const {
if (num_conn == 2) return; // no stoplights
if (!dstate.check_cube_visible(*this, 0.16, shadow_only)) return; // dist_scale=0.16
point const center(get_cube_center() + dstate.xlate);
float const dist_val(shadow_only ? 0.0 : p2p_dist(camera_pdu.pos, center)/get_draw_tile_dist());
vector3d const cview_dir(camera_pdu.pos - center);
float const sz(0.03*city_params.road_width), h(1.0*sz);
color_wrapper cw(BLACK);
for (unsigned n = 0; n < 4; ++n) { // {-x, +x, -y, +y} = {W, E, S, N} facing = car traveling {E, W, N, S}
if (!(conn & (1<<n))) continue; // no road in this dir
bool const dim((n>>1) != 0), dir((n&1) == 0), side((dir^dim^1) != 0); // Note: dir is inverted here to represent car dir
float const zbot(z1() + 2.0*h), dim_pos(d[dim][!dir] + (dir ? sz : -sz)); // location in road dim
float const v1(d[!dim][side]), v2(v1 + (side ? -sz : sz)); // location in other dim
// draw base
unsigned const num_segs(has_left_turn_signal(n) ? 6 : 3);
float const sl_top(zbot + 1.2*h*num_segs), sl_lo(min(v1, v2) - 0.25*sz), sl_hi(max(v1, v2) + 0.25*sz);
if (dist_val > 0.06) { // draw front face only
point pts[4];
pts[0][dim] = pts[1][dim] = pts[2][dim] = pts[3][dim] = dim_pos;
pts[0][!dim] = pts[3][!dim] = sl_lo;
pts[1][!dim] = pts[2][!dim] = sl_hi;
pts[0].z = pts[1].z = z1();
pts[2].z = pts[3].z = sl_top;
qbd.add_quad_pts(pts, cw, (dim ? (dir ? plus_y : -plus_y) : (dir ? plus_x : -plus_x))); // Note: normal doesn't really matter since color is black
}
else {
cube_t c;
c.z1() = z1(); c.z2() = sl_top;
c.d[ dim][0] = dim_pos - (dir ? -0.04 : 0.5)*sz; c.d[dim][1] = dim_pos + (dir ? 0.5 : -0.04)*sz;
c.d[!dim][0] = sl_lo; c.d[!dim][1] = sl_hi;
dstate.draw_cube(qbd, c, cw, 1); // skip_bottom=1; Note: uses traffic light texture, but color is black so it's all black anyway
if (!shadow_only && tt_fire_button_down && !game_mode) {
point const p1(camera_pdu.pos - dstate.xlate), p2(p1 + camera_pdu.dir*FAR_CLIP);
if (c.line_intersects(p1, p2)) {dstate.set_label_text(stoplight.label_str(), (c.get_cube_center() + dstate.xlate));}
}
}
bool const draw_detail(dist_val < 0.05); // add flares only when very close
// Note skip crosswalk signal for road to the right of an unconnected 3-way int (T-junction)
// because it's not needed and will never get to walk (due to opposing left turn light), and there's no stoplight to attach it to
if (dist_val <= 0.06 && (conn & (1<<stoplight_ns::to_right[2*dim + (!dir)])) != 0) { // draw crosswalk signal
int const cw_state(shadow_only ? 0 : stoplight.get_crosswalk_state(dim, !dir)); // Note: backwards dir
colorRGBA cw_color(stoplight_ns::crosswalk_colors[cw_state] * 0.6); // less bright
if (cw_state == stoplight_ns::CW_WARN) { // flashing
float const flash_period = 0.5; // in seconds
double const time(fract(tfticks/(flash_period*TICKS_PER_SECOND)));
if (time > 0.5) {cw_color = BLACK;}
}
float const cw_dim_pos(d[dim][!dir] + 1.7*(dir ? sz : -sz)); // location in road dim
cube_t c;
c.z1() = zbot + 1.2*h + 1.4*h*dim; c.z2() = c.z1() + 1.6*h;
c.d[dim][0] = cw_dim_pos - 0.5*sz; c.d[dim][1] = cw_dim_pos + 0.5*sz;
for (unsigned i = 0; i < 2; ++i) { // opposite sides of the road
float const ndp(d[!dim][i] - 1.2*(i ? sz : -sz));
c.d[!dim][0] = ndp - 0.1*sz; c.d[!dim][1] = ndp + 0.1*sz;
dstate.draw_cube(qbd, c, cw, 0); // skip_bottom=0
if (!shadow_only && cw_color != BLACK) { // draw light
point p[4];
p[0][!dim] = p[1][!dim] = p[2][!dim] = p[3][!dim] = c.d[!dim][!i] + 0.01*(i ? -sz : sz);
p[0][dim] = p[3][dim] = c.d[dim][0]; p[1][dim] = p[2][dim] = c.d[dim][1];
p[0].z = p[1].z = c.z1(); p[2].z = p[3].z = c.z2();
vector3d normal(zero_vector);
normal[!dim] = (i ? -1.0 : 1.0);
point const cw_center(0.25*(p[0] + p[1] + p[2] + p[3]));
vector3d const cw_cview_dir(camera_pdu.pos - (cw_center + dstate.xlate));
float dp(dot_product(normal, cw_cview_dir));
if (dp > 0.0) { // if back facing, don't draw the lights
float const mag(CLIP_TO_01(2.5f*dp/cw_cview_dir.mag() - 1.0f)); // normalize and strengthen slope
if (mag > 0.0) {
bool const is_walk(cw_state == stoplight_ns::CW_WALK);
qbd.add_quad_pts(p, WHITE*mag, normal, tex_range_t((is_walk ? 0.25 : 0.0), 0.0, (is_walk ? 0.5 : 0.25), 0.5)); // color is stored in the texture
if (draw_detail) {dstate.add_light_flare(cw_center, normal, cw_color*mag, 1.0, 0.8*h);}
}
}
}
} // for i
} // end crosswalk signal
if (shadow_only) continue; // no lights in shadow pass
if (dist_val > 0.1) continue; // too far away
vector3d normal(zero_vector);
normal[dim] = (dir ? -1.0 : 1.0);
if (dot_product(normal, cview_dir) < 0.0) continue; // if back facing, don't draw the lights
// draw straight/line turn light
point p[4];
p[0][dim] = p[1][dim] = p[2][dim] = p[3][dim] = dim_pos;
p[0][!dim] = p[3][!dim] = v1; p[1][!dim] = p[2][!dim] = v2;
p[0].z = p[1].z = zbot; p[2].z = p[3].z = zbot + h;
draw_sl_block(qbd, dstate, p, h, stoplight.get_light_state(dim, dir, TURN_NONE), draw_detail, draw_detail, normal, tex_range_t(0.0, 0.5, 0.5, 1.0));
if (has_left_turn_signal(n)) { // draw left turn light (upper light section)
draw_sl_block(qbd, dstate, p, h, stoplight.get_light_state(dim, dir, TURN_LEFT), draw_detail, 0.5*draw_detail, normal, tex_range_t(1.0, 0.5, 0.5, 1.0));
}
} // for n
}
};
struct road_plot_t : public cube_t {
bool has_parking;
road_plot_t(cube_t const &c) : cube_t(c), has_parking(0) {}
tex_range_t get_tex_range(float ar) const {return tex_range_t(0.0, 0.0, ar, ar);}
};
struct parking_lot_t : public cube_t {
bool dim, dir;
unsigned short row_sz, num_rows;
parking_lot_t(cube_t const &c, bool dim_, bool dir_, unsigned row_sz_=0, unsigned num_rows_=0) : cube_t(c), dim(dim_), dir(dir_), row_sz(row_sz_), num_rows(num_rows_) {}
tex_range_t get_tex_range(float ar) const {
bool const d(!dim); // Note: R90
float const xscale(1.0/(2.0*PARK_SPACE_WIDTH *city_params.get_nom_car_size().y));
float const yscale(1.0/(1.0*PARK_SPACE_LENGTH*city_params.get_nom_car_size().x));
float const dx(get_dx()), dy(get_dy()), tx(0.24), ty(0.0); // x=cols, y=rows
return tex_range_t(tx, ty, (xscale*(d ? dy : dx) + tx), (yscale*(d ? dx : dy) + ty), 0, d);
}
};
namespace streetlight_ns {
colorRGBA const pole_color(BLACK); // so we don't have to worry about shadows
colorRGBA const light_color(1.0, 0.9, 0.7, 1.0);
float const light_height = 0.5; // in units of road width
float const pole_radius = 0.015;
float const light_radius = 0.025;
float const light_dist = 3.0;
static float get_streetlight_height() {return light_height*city_params.road_width;}
struct streetlight_t {
point pos; // bottom point
vector3d dir;
streetlight_t(point const &pos_, vector3d const &dir_) : pos(pos_), dir(dir_) {}
bool is_lit(bool always_on) const {return (always_on || is_night(STREETLIGHT_ON_RAND*signed_rand_hash(pos.x + pos.y)));}
point get_lpos() const {
float const height(get_streetlight_height());
return (pos + vector3d(0.0, 0.0, 1.1*height) + 0.4*height*dir);
}
void draw(shader_t &s, vector3d const &xlate, bool shadow_only, bool is_local_shadow, bool always_on) const { // Note: translate has already been applied as a transform
float const height(get_streetlight_height());
point const center(pos + xlate + vector3d(0.0, 0.0, 0.5*height));
if (shadow_only && is_local_shadow && !dist_less_than(camera_pdu.pos, center, 0.8*camera_pdu.far_)) return;
if (!camera_pdu.sphere_visible_test(center, height)) return; // VFC
float const dist_val(shadow_only ? 0.06 : p2p_dist(camera_pdu.pos, center)/get_draw_tile_dist());
if (dist_val > 0.2) return; // too far
if (!s.is_setup()) {s.begin_color_only_shader();} // likely only needed for shadow pass, could do better with this
float const pradius(pole_radius*city_params.road_width), lradius(light_radius*city_params.road_width);
int const ndiv(max(4, min(N_SPHERE_DIV, int(0.5/dist_val))));
point const top(pos + vector3d(0.0, 0.0, 0.96*height)), lpos(get_lpos()), arm_end(lpos + vector3d(0.0, 0.0, 0.025*height) - 0.06*height*dir);
if (!shadow_only) {s.set_cur_color(pole_color);}
draw_cylinder_at(pos, height, pradius, 0.7*pradius, min(ndiv, 24), 0); // vertical post, no ends
if (dist_val < 0.12) {draw_fast_cylinder(top, arm_end, 0.5*pradius, 0.4*pradius, min(ndiv, 16), 0, 0);} // untextured, no ends
bool const is_on(is_lit(always_on));
if (shadow_only) {
if (dist_less_than(camera_pdu.pos, (get_lpos() + xlate), 0.01*lradius)) return; // this is the light source, don't make it shadow itself
}
else {
if (!is_on && dist_val > 0.15) return; // too far
if (is_on) {s.set_color_e(light_color);} else {s.set_cur_color(light_color);} // emissive when lit
}
fgPushMatrix();
translate_to(lpos);
scale_by(lradius*vector3d(1.0+fabs(dir.x), 1.0+fabs(dir.y), 1.0)); // scale 2x in dir
draw_sphere_vbo(all_zeros, 1.0, ndiv, 0); // untextured
if (!shadow_only && is_on) {s.clear_color_e();}
if (!shadow_only && dist_val < 0.12) {
fgTranslate(0.0, 0.0, 0.1); // translate up slightly and draw top cap of light
s.set_cur_color(pole_color);
draw_sphere_vbo(all_zeros, 1.0, ndiv, 0); // untextured
}
fgPopMatrix();
}
void add_dlight(vector3d const &xlate, cube_t &lights_bcube, bool always_on) const {
if (!is_lit(always_on)) return;
float const ldist(light_dist*city_params.road_width);
if (!lights_bcube.contains_pt_xy(pos)) return; // not contained within the light volume
point const lpos(get_lpos());
if (!camera_pdu.sphere_visible_test((lpos + xlate), ldist)) return; // VFC
min_eq(lights_bcube.z1(), (lpos.z - ldist));
max_eq(lights_bcube.z2(), (lpos.z + ldist));
dl_sources.push_back(light_source(ldist, lpos, lpos, light_color, 0, -plus_z, STREETLIGHT_BEAMWIDTH)); // points down
}
bool proc_sphere_coll(point &center, float radius, vector3d const &xlate, vector3d *cnorm) const {
point const p2(pos + xlate);
float const pradius(pole_radius*city_params.road_width);
if (!dist_xy_less_than(p2, center, (pradius + radius))) return 0;
return sphere_vert_cylin_intersect(center, radius, cylinder_3dw(p2, (p2 + vector3d(0.0, 0.0, get_streetlight_height())), pradius, 0.7*pradius), cnorm);
}
bool line_intersect(point const &p1, point const &p2, float &t) const {
float const pradius(pole_radius*city_params.road_width);
float t_new(0.0);
if (line_int_cylinder(p1, p2, pos, (pos + vector3d(0.0, 0.0, get_streetlight_height())), pradius, 0.7*pradius, 0, t_new) && t_new < t) {t = t_new; return 1;}
return 0;
}
};
} // streetlight_ns
struct streetlights_t {
vector<streetlight_ns::streetlight_t> streetlights;
void draw_streetlights(shader_t &s, vector3d const &xlate, bool shadow_only, bool always_on) const {
if (streetlights.empty()) return;
//timer_t t("Draw Streetlights");
select_texture(WHITE_TEX);
bool const is_local_shadow(camera_pdu.far_ < 0.1*FAR_CLIP); // Note: somewhat of a hack, but I don't have a better way to determine this
for (auto i = streetlights.begin(); i != streetlights.end(); ++i) {i->draw(s, xlate, shadow_only, is_local_shadow, always_on);}
}
void add_streetlight_dlights(vector3d const &xlate, cube_t &lights_bcube, bool always_on) const {
if (!always_on && !is_night(STREETLIGHT_ON_RAND)) return; // none of them can be on
for (auto i = streetlights.begin(); i != streetlights.end(); ++i) {i->add_dlight(xlate, lights_bcube, always_on);}
}
bool proc_streetlight_sphere_coll(point &pos, float radius, vector3d const &xlate, vector3d *cnorm) const {
for (auto i = streetlights.begin(); i != streetlights.end(); ++i) {
if (i->proc_sphere_coll(pos, radius, xlate, cnorm)) return 1;
}
return 0;
}
bool line_intersect_streetlights(point const &p1, point const &p2, float &t) const {
bool ret(0);
for (auto i = streetlights.begin(); i != streetlights.end(); ++i) {ret |= i->line_intersect(p1, p2, t);}
return ret;
}
};
struct road_connector_t : public road_t, public streetlights_t {
road_t src_road;
road_connector_t(road_t const &road) : road_t(road), src_road(road) {}
float get_player_zval(point const &center, cube_t const &c) const {
float const t((center[dim] - c.d[dim][0])/(c.d[dim][1] - c.d[dim][0]));
float const za(slope ? c.z2() : c.z1()), zb(slope ? c.z1() : c.z2()), zval(za + (zb - za)*t); // z-value at x/y location
return zval - src_road.get_z_adj() - ROAD_HEIGHT; // place exactly on mesh under the road/bridge/tunnel surface
}
void add_streetlights(unsigned num_per_side, bool staggered, float dn_shift_mult, float za, float zb) {
streetlights.reserve(2*num_per_side);
float const dsz(d[dim][1] - d[dim][0]), dnsz(d[!dim][1] - d[!dim][0]), dn_shift(dn_shift_mult*dnsz);
if (staggered) {num_per_side *= 2;}
for (unsigned n = 0; n < num_per_side; ++n) {
float const v((n + 0.5)/num_per_side); // 1/8, 3/8, 5/8, 7/8
point pos1, pos2;
pos1[ dim] = pos2[dim] = d[dim][0] + v*dsz;
pos1[!dim] = d[!dim][0] - dn_shift; pos2[!dim] = d[!dim][1] + dn_shift;
pos1.z = pos2.z = za + v*(zb - za);
vector3d dir(zero_vector); dir[!dim] = 1.0;
if (!staggered || (n&1) == 0) {streetlights.emplace_back(pos1, dir);}
if (!staggered || (n&1) == 1) {streetlights.emplace_back(pos2, -dir);}
} // for n
}
};
struct bridge_t : public road_connector_t {
bool make_bridge;
bridge_t(road_t const &road) : road_connector_t(road), make_bridge(0) {}
void add_streetlights() {road_connector_t::add_streetlights(4, 0, 0.05, get_start_z(), get_end_z());} // 4 per side
bool proc_sphere_coll(point &center, point const &prev, float sradius, float prev_frame_zval, vector3d const &xlate, vector3d *cnorm) const {
if (proc_streetlight_sphere_coll(center, sradius, xlate, cnorm)) return 1;
float const exp(0.5*sradius);
bool const prev_int(contains_pt_xy_exp((prev - xlate), exp));
if (!prev_int && !src_road.contains_pt_xy_exp((center - xlate), exp)) return 0; // no x/y containment for road segment (cur or prev)
cube_t const c(src_road + xlate);
float const zval(get_player_zval(center, c));
// Note: we need to use prev_frame_zval for camera collisions because center.z will always start at the mesh zval;
// we can't just always place the camera on the bridge because the player may be walking on the ground under the bridge
if (center.z - sradius > zval || prev_frame_zval + sradius < zval) return 0; // completely above or below the road/bridge
center.z = zval + sradius; // place exactly on the road/bridge surface
if (prev_int) {center[!dim] = min(c.d[!dim][1], max(c.d[!dim][0], center[!dim]));} // keep the sphere from falling off the bridge (approximate; assumes bridge has walls)
if (cnorm) {*cnorm = plus_z;} // approximate - assume collision with bottom surface of road (intended for player)
return 1;
}
bool line_intersect(point const &p1, point const &p2, float &t) const {return 0;} // TODO
};
struct tunnel_t : public road_connector_t {
cube_t ends[2];
float radius, height, facade_height[2];
tunnel_t(road_t const &road) : road_connector_t(road), radius(0.0), height(0.0) {}
bool enabled() const {return (radius > 0.0);}
void init(point const &start, point const &end, float radius_, bool dim) {
radius = radius_;
height = (1.0 + TUNNEL_WALL_THICK)*radius;
vector3d const dir((end - start).get_norm());
point const extend[2] = {(start + dir*radius), (end - dir*radius)};
ends[0].set_from_point(start);
ends[1].set_from_point(end);
for (unsigned d = 0; d < 2; ++d) {
ends[d].z2() += height; // height
ends[d].d[!dim][0] -= radius; // width
ends[d].d[!dim][1] += radius; // width
ends[d].union_with_pt(extend[d]); // length
facade_height[d] = 2.0*TUNNEL_WALL_THICK*radius; // min value - will likely be increased later
}
get_bcube() = ends[0]; get_bcube().union_with_cube(ends[1]); // bounding cube of both ends - overwrite road bcube
}
void add_streetlights() {road_connector_t::add_streetlights(2, 1, -0.15, ends[0].z1(), ends[1].z1());} // 2 per side, staggered
cube_t get_tunnel_bcube() const {
cube_t bcube(*this);
bcube.expand_by(vector3d(radius, radius, 0.0));
bcube.z2() += max(facade_height[0], facade_height[1]); // should be close enough
return bcube;
}
void calc_top_bot_side_cubes(cube_t cubes[4]) const {
float const wall_thick(TUNNEL_WALL_THICK*city_params.road_width), end_ext(2.0*(dim ? DY_VAL : DX_VAL));
cube_t tc(*this);
tc.d[dim][0] -= end_ext; tc.d[dim][1] += end_ext; // extend to cover the gaps in the mesh
cubes[0] = cubes[1] = cubes[2] = cubes[3] = tc;
cubes[0].z1() = -wall_thick; // bottom cube extends below the road surface
cubes[0].z2() = -ROAD_HEIGHT; // top of bottom cube is just below the road surface
cubes[1].z1() = height - wall_thick; // top cube
cubes[1].z2() = height;
cubes[2].z1() = cubes[3].z1() = cubes[0].z2(); // bottom of sides meet bottom cube
cubes[2].z2() = cubes[3].z2() = cubes[1].z1(); // top of sides meet top cube
cubes[2].d[!dim][1] = cubes[2].d[!dim][0] + wall_thick; // left side
cubes[3].d[!dim][0] = cubes[3].d[!dim][1] - wall_thick; // right side
}
bool check_mesh_disable(cube_t const &query_region) const {
return (ends[0].intersects_xy(query_region) || ends[1].intersects_xy(query_region)); // check both ends
}
bool proc_sphere_coll(point &center, point const &prev, float sradius, float prev_frame_zval, vector3d const &xlate, vector3d *cnorm) const {
if (proc_streetlight_sphere_coll(center, sradius, xlate, cnorm)) return 1;
bool const prev_int(contains_pt_xy(prev - xlate));
if (!prev_int && !contains_pt_xy(center - xlate)) return 0; // no x/y containment for road segment (cur or prev)
cube_t const c(src_road + xlate);
float const zval(get_player_zval(center, c));
// Note: we need to use prev_frame_zval for camera collisions because center.z will always start at the mesh zval;
// we can't just always place the camera on the tunnel because the player may be walking on the ground above the tunnel
if (prev_frame_zval - sradius > zval + height) return 0; // completely above the tunnel
center.z = zval + sradius; // place exactly on mesh under the road/tunnel surface
if (prev_int) {center[!dim] = min(c.d[!dim][1], max(c.d[!dim][0], center[!dim]));} // keep the sphere inside the tunnel (approximate)
if (cnorm) {*cnorm = plus_z;} // approximate - assume collision with bottom surface of road (intended for player)
return 1;
}
bool line_intersect(point const &p1, point const &p2, float &t) const {
cube_t const bcube(get_tunnel_bcube());
if (!check_line_clip(p1, p2, bcube.d)) return 0;
// FIXME: remove duplicate code with draw_tunnel()?
cube_t cubes[4];
calc_top_bot_side_cubes(cubes);
float const wall_thick(TUNNEL_WALL_THICK*city_params.road_width), width(max(0.5*get_width(), 2.0*(d ? DX_VAL : DY_VAL)));
float zf(ends[0].z1()), zb(ends[1].z1());
float const end_ext(2.0*(d ? DY_VAL : DX_VAL)), dz_ext(end_ext*(zb - zf)/get_length());
zf -= dz_ext; zb += dz_ext; // adjust zvals for extension
bool const d(dim);
bool ret(0);
for (unsigned i = 0; i < 4; ++i) { // check tunnel top, bottom, and sides
//cube_t const &c(cubes[i]);
//set_cube_pts(c, c.z1()+zf, c.z1()+zb, c.z2()+zf, c.z2()+zb, d, 0, pts); // TODO: tilted cube case
}
for (unsigned n = 0; n < 2; ++n) { // check tunnel facades
cube_t const &tend(ends[n]);
cube_t c(tend);
c.z1() += height - 0.5*wall_thick; // tunnel ceiling
c.z2() += facade_height[n]; // high enough to cover the hole in the mesh
c.d[d][0] -= 0.9*end_ext; c.d[d][1] += 0.9*end_ext; // extend to cover the gaps in the mesh (both dirs) - slightly less than interior so that it sticks out
ret |= check_line_clip_update_t(p1, p2, t, c);
c.z1() = ends[n].z1() - wall_thick; // extend to the bottom
c.d[!d][0] = ends[n].d[!d][0] - width; c.d[!d][1] = tend.d[!d][0]; // left side
ret |= check_line_clip_update_t(p1, p2, t, c);
c.d[!d][1] = ends[n].d[!d][1] + width; c.d[!d][0] = tend.d[!d][1]; // right side
ret |= check_line_clip_update_t(p1, p2, t, c);
} // for n
return ret;
}
};
bool check_bcube_sphere_coll(cube_t const &bcube, point const &sc, float radius, bool xy_only) {
return (xy_only ? sphere_cube_intersect_xy(sc, radius, bcube) : sphere_cube_intersect(sc, radius, bcube));
}
bool check_bcube_sphere_coll(bridge_t const &bridge, point const &sc, float radius, bool xy_only) {
cube_t bcube(bridge);
float const shrink(2.0*(bridge.dim ? DY_VAL : DX_VAL));
bcube.d[bridge.dim][0] += shrink; bcube.d[bridge.dim][1] -= shrink;
return check_bcube_sphere_coll(bcube, sc, radius, xy_only);
}
template<typename T> bool check_bcubes_sphere_coll(vector<T> const &bcubes, point const &sc, float radius, bool xy_only) {
for (auto i = bcubes.begin(); i != bcubes.end(); ++i) {
if (check_bcube_sphere_coll(*i, sc, radius, xy_only)) return 1;
}
return 0;
}
template<typename T> cube_t calc_cubes_bcube(vector<T> const &cubes) {
if (cubes.empty()) return cube_t(all_zeros);
cube_t bcube(cubes.front()); // first cube
for (auto r = cubes.begin()+1; r != cubes.end(); ++r) {bcube.union_with_cube(*r);} // skip first cube
return bcube;
}
class heightmap_query_t {
protected:
float *heightmap;
unsigned xsize, ysize;
public:
flatten_op_t last_flatten_op;
heightmap_query_t() : heightmap(nullptr), xsize(0), ysize(0) {}
heightmap_query_t(float *hmap, unsigned xsize_, unsigned ysize_) : heightmap(hmap), xsize(xsize_), ysize(ysize_) {}
float get_x_value(int x) const {return get_xval(x - int(xsize)/2);} // convert from center to LLC
float get_y_value(int y) const {return get_yval(y - int(ysize)/2);}
int get_x_pos(float x) const {return (get_xpos(x) + int(xsize)/2);}
int get_y_pos(float y) const {return (get_ypos(y) + int(ysize)/2);}
float get_height(unsigned x, unsigned y) const {return heightmap[y*xsize + x];} // Note: not bounds checked
float &get_height(unsigned x, unsigned y) {return heightmap[y*xsize + x];} // Note: not bounds checked
bool is_normalized_region(unsigned x1, unsigned y1, unsigned x2, unsigned y2) const {return (x1 < x2 && y1 < y2 && x2 <= xsize && y2 <= ysize);}
bool is_valid_region (unsigned x1, unsigned y1, unsigned x2, unsigned y2) const {return (x1 <= x2 && y1 <= y2 && x2 <= xsize && y2 <= ysize);}
bool is_inside_terrain(int x, int y) const {return (x >= 0 && y >= 0 && x < (int)xsize && y < (int)ysize);}
cube_t get_full_hmap_bcube() const {return get_cube_for_bounds(0, 0, xsize, ysize, 0.0);}
cube_t get_cube_for_bounds(unsigned x1, unsigned y1, unsigned x2, unsigned y2, float elevation) const {
cube_t c;
c.x1() = get_x_value(x1);
c.x2() = get_x_value(x2);
c.y1() = get_y_value(y1);
c.y2() = get_y_value(y2);
c.z1() = c.z2() = elevation;
return c;
}
float get_height_at(float xval, float yval) const {
int const x(get_x_pos(xval)), y(get_y_pos(yval));
return (is_inside_terrain(x, y) ? get_height(x, y) : OUTSIDE_TERRAIN_HEIGHT);
}
float any_underwater(unsigned x1, unsigned y1, unsigned x2, unsigned y2, bool check_border=0) const {
min_eq(x2, xsize); min_eq(y2, ysize); // clamp upper bound
assert(is_valid_region(x1, y1, x2, y2));
for (unsigned y = y1; y < y2; ++y) {
for (unsigned x = x1; x < x2; ++x) {
if (check_border && y != y1 && y != y2-1 && x == x1+1) {x = x2-1;} // jump to right edge
if (get_height(x, y) < water_plane_z) return 1;
}
}
return 0;
}
void get_segment_end_pts(road_t const &r, unsigned six, unsigned eix, point &ps, point &pe) const {
float const sv(r.dim ? get_y_value(six) : get_x_value(six)), ev(r.dim ? get_y_value(eix) : get_x_value(eix)); // start/end pos of bridge in road dim
float const z1(r.get_start_z()), z2(r.get_end_z()), dz(z2 - z1), len(r.get_length()), v0(r.dim ? r.y1() : r.x1());
ps[ r.dim] = sv;
pe[ r.dim] = ev;
ps[!r.dim] = pe[!r.dim] = 0.5*(r.d[!r.dim][0] + r.d[!r.dim][1]);
ps.z = z1 + dz*CLIP_TO_01((sv - v0)/len);
pe.z = z1 + dz*CLIP_TO_01((ev - v0)/len);
}
void flatten_region_to(cube_t const c, unsigned slope_width, bool decrease_only=0) {
flatten_region_to(get_x_pos(c.x1()), get_y_pos(c.y1()), get_x_pos(c.x2()), get_y_pos(c.y2()), slope_width, (c.z1() - ROAD_HEIGHT), decrease_only);
}
void flatten_region_to(unsigned x1, unsigned y1, unsigned x2, unsigned y2, unsigned slope_width, float elevation, bool decrease_only=0) {
assert(is_valid_region(x1, y1, x2, y2));
for (unsigned y = max((int)y1-(int)slope_width, 0); y < min(y2+slope_width, ysize); ++y) {
for (unsigned x = max((int)x1-(int)slope_width, 0); x < min(x2+slope_width, xsize); ++x) {
float &h(get_height(x, y));
if (decrease_only && h < elevation) continue; // don't increase
if (slope_width > 0) {
float const dx(max(0, max(((int)x1 - (int)x), ((int)x - (int)x2 + 1))));
float const dy(max(0, max(((int)y1 - (int)y), ((int)y - (int)y2 + 1))));
h = smooth_interp(h, elevation, min(1.0f, sqrt(dx*dx + dy*dy)/slope_width));
} else {h = elevation;}
} // for x
} // for y
}
float flatten_sloped_region(unsigned x1, unsigned y1, unsigned x2, unsigned y2, float z1, float z2, bool dim, unsigned border,
unsigned skip_six=0, unsigned skip_eix=0, bool stats_only=0, bool decrease_only=0, bridge_t *bridge=nullptr, tunnel_t *tunnel=nullptr)
{
if (!stats_only) {last_flatten_op = flatten_op_t(x1, y1, x2, y2, z1, z2, dim, border);} // cache for later replay
assert(is_valid_region(x1, y1, x2, y2));
if (x1 == x2 || y1 == y2) return 0.0; // zero area
float const run_len(dim ? (y2 - y1) : (x2 - x1)), denom(1.0f/max(run_len, 1.0f)), dz(z2 - z1), border_inv(1.0/border);
int const pad(border + 1U); // pad an extra 1 texel to handle roads misaligned with the texture
unsigned px1(x1), py1(y1), px2(x2), py2(y2), six(dim ? ysize : xsize), eix(0);
float tot_dz(0.0), seg_min_dh(0.0);
float const bridge_cost(0.0), bridge_dist_cost(0.0), tunnel_cost(0.0), tunnel_dist_cost(0.0); // Note: currently set to zero, but could be used
unsigned const min_bridge_len(12), min_tunnel_len(12); // in mesh texels
if (dim) {
px1 = max((int)x1-pad, 0);
px2 = min(x2+pad, xsize);
py1 = max((int)y1-1, 0); // pad by 1 in road dim as well to blend with edge of city
py2 = min(y2+1, ysize);
}
else {
py1 = max((int)y1-pad, 0);
py2 = min(y2+pad, ysize);
px1 = max((int)x1-1, 0);
px2 = min(x2+1, xsize);
}
if (!stats_only && !decrease_only && bridge != nullptr && fabs(bridge->get_slope_val()) < 0.1) { // determine if we should add a bridge here
float added(0.0), removed(0.0), total(0.0);
bool end_bridge(0);
for (unsigned y = y1; y < y2; ++y) { // Note: not padded
for (unsigned x = x1; x < x2; ++x) {
float const t(((dim ? int(y - y1) : int(x - x1)) + ((dz < 0.0) ? 1 : -1))*denom); // bias toward the lower zval
float const road_z(z1 + dz*t - ROAD_HEIGHT), h(get_height(x, y));
if (road_z > h) {
added += (road_z - h);
if (!end_bridge && road_z > h + 1.0*city_params.road_width) { // higher than terrain by a significant amount
min_eq(six, (dim ? y : x));
max_eq(eix, (dim ? y : x));
}
}
else {
removed += (h - road_z);
if (eix > 0) {end_bridge = 1;} // done with bridge - don't create bridge past high point
}
total += 1.0;
} // for x
} // for y
max_eq(six, (dim ? y1+border : x1+border)); // keep away from segment end points (especially connector road jogs)
min_eq(eix, (dim ? y2-border : x2-border));
if (eix > six+min_bridge_len && added > 1.5*city_params.road_width*total && added > 2.0*removed) {
point ps, pe;
get_segment_end_pts(bridge->src_road, six, eix, ps, pe);
bridge->d[dim][0] = ps[dim];
bridge->d[dim][1] = pe[dim];
bridge->z1() = min(ps.z, pe.z);
bridge->z2() = max(ps.z, pe.z);
bridge->make_bridge = 1;
skip_six = six; skip_eix = eix; // mark so that mesh height isn't updated in this region
tot_dz += bridge_cost + bridge_dist_cost*bridge->get_length();
}
} // end bridge logic
if (!stats_only && tunnel != nullptr && skip_eix == 0 && fabs(tunnel->get_slope_val()) < 0.2) { // determine if we should add a tunnel here
float const radius(1.0*city_params.road_width), min_height((1.0 + TUNNEL_WALL_THICK)*radius);
float added(0.0), removed(0.0), total(0.0);
bool end_tunnel(0);
for (unsigned y = y1; y < y2; ++y) { // Note: not padded
for (unsigned x = x1; x < x2; ++x) {
float const t(((dim ? int(y - y1) : int(x - x1)) + ((dz < 0.0) ? 1 : -1))*denom); // bias toward the lower zval
float const road_z(z1 + dz*t), h(get_height(x, y));
if (road_z < h) {
removed += (h - road_z);
if (!end_tunnel && road_z + min_height < h) { // below terrain by a significant amount
min_eq(six, (dim ? y : x));
max_eq(eix, (dim ? y : x));
}
}
else {
added += (road_z - h);
if (eix > 0) {end_tunnel = 1;} // done with tunnel - don't create tunnel past low point
}
total += 1.0;
} // for x
} // for y
max_eq(six, (dim ? y1+border : x1+border)); // keep away from segment end points (especially connector road jogs)
min_eq(eix, (dim ? y2-border : x2-border));
if (eix > six+min_tunnel_len && removed > 1.0*city_params.road_width*total && removed > 2.0*added) {
point ps, pe;
get_segment_end_pts(*tunnel, six, eix, ps, pe);
float const len(fabs(ps[dim] - pe[dim]));
if (len > 4.0*radius) { // don't make the tunnel too short
tunnel->init(ps, pe, radius, dim);
seg_min_dh = tunnel->height + TUNNEL_WALL_THICK*radius; // add another wall thickness to account for sloped terrain minima
skip_six = six; skip_eix = eix; // mark so that mesh height isn't updated in this region
tot_dz += tunnel_cost + tunnel_dist_cost*tunnel->get_length();
int const rwidth(ceil(city_params.road_width/(dim ? DX_VAL : DY_VAL)));
for (int dxy = -rwidth; dxy <= rwidth; ++dxy) { // shifts in !dim
unsigned qpt[2] = {(x1 + x2)/2, (y1 + y2)/2}; // start at center
qpt[!dim] += dxy;
for (unsigned n = 0; n < 4; ++n) { // take several samples and find the peak mesh height for the tunnel facades
qpt[dim] = six + n;
max_eq(tunnel->facade_height[0], (get_height(qpt[0], qpt[1]) - ps.z));
qpt[dim] = eix - n;
max_eq(tunnel->facade_height[1], (get_height(qpt[0], qpt[1]) - pe.z));
} // for n
} // for dxy
}
}
} // end tunnel logic
if (!stats_only && skip_six < skip_eix) {last_flatten_op.skip_six = skip_six; last_flatten_op.skip_eix = skip_eix;} // clip to a partial range
for (unsigned y = py1; y < py2; ++y) {
for (unsigned x = px1; x < px2; ++x) {
float const t(((dim ? int(y - y1) : int(x - x1)) + ((dz < 0.0) ? 1 : -1))*denom); // bias toward the lower zval
float const road_z(z1 + dz*t - ROAD_HEIGHT);
float &h(get_height(x, y));
if (decrease_only && h < road_z) continue; // don't increase
float new_h;
unsigned dist(0);
if (border > 0) {
dist = (dim ? max(0, max(((int)x1 - (int)x - 1), ((int)x - (int)x2))) : max(0, max(((int)y1 - (int)y - 1), ((int)y - (int)y2))));
new_h = smooth_interp(h, road_z, dist*border_inv);
} else {new_h = road_z;}
tot_dz += fabs(h - new_h);
if (stats_only) continue; // no height update
unsigned const dv(dim ? y : x);
if (dv > skip_six && dv < skip_eix) { // don't modify mesh height at bridges or tunnels, but still count it toward the cost
if (seg_min_dh > 0.0) { // clamp to roof of tunnel (Note: doesn't count toward tot_dz)
float const zmin(road_z + seg_min_dh);
if (h < zmin) {h = smooth_interp(h, zmin, dist*border_inv);}
}
}
else {h = new_h;} // apply the height change
} // for x
} // for y
return tot_dz;
}
float flatten_for_road(road_t const &road, unsigned border, bool stats_only=0, bool decrease_only=0, bridge_t *bridge=nullptr, tunnel_t *tunnel=nullptr) {
float const z_adj(road.get_z_adj());
unsigned const rx1(get_x_pos(road.x1())), ry1(get_y_pos(road.y1())), rx2(get_x_pos(road.x2())), ry2(get_y_pos(road.y2()));
return flatten_sloped_region(rx1, ry1, rx2, ry2, road.d[2][road.slope]-z_adj, road.d[2][!road.slope]-z_adj, road.dim, border, 0, 0, stats_only, decrease_only, bridge, tunnel);
}
}; // heightmap_query_t
class city_plot_gen_t : public heightmap_query_t {
protected:
int last_rgi;
rand_gen_t rgen;
vector<rect_t> used;
vector<cube_t> plots; // same size as used
cube_t bcube;
bool overlaps_used(unsigned x1, unsigned y1, unsigned x2, unsigned y2) const {
rect_t const cur(x1, y1, x2, y2);
for (vector<rect_t>::const_iterator i = used.begin(); i != used.end(); ++i) {if (i->has_overlap(cur)) return 1;} // simple linear iteration
return 0;
}
cube_t add_plot(unsigned x1, unsigned y1, unsigned x2, unsigned y2, float elevation) {
cube_t const c(get_cube_for_bounds(x1, y1, x2, y2, elevation));
if (plots.empty()) {bcube = c;} else {bcube.union_with_cube(c);}
plots.push_back(c);
used.emplace_back(x1, y1, x2, y2);
return c;
}
float get_avg_height(unsigned x1, unsigned y1, unsigned x2, unsigned y2) const {
assert(is_normalized_region(x1, y1, x2, y2));
float sum(0.0), denom(0.0);
for (unsigned y = y1; y < y2; ++y) {
for (unsigned x = x1; x < x2; ++x) {
if (CHECK_HEIGHT_BORDER_ONLY && y != y1 && y != y2-1 && x == x1+1) {x = x2-1;} // jump to right edge
sum += get_height(x, y);
denom += 1.0;
}
}
return sum/denom;
}
float get_rms_height_diff(unsigned x1, unsigned y1, unsigned x2, unsigned y2) const {
float const avg(get_avg_height(x1, y1, x2, y2));
float diff(0.0);
for (unsigned y = y1; y < y2; ++y) {
for (unsigned x = x1; x < x2; ++x) {
if (CHECK_HEIGHT_BORDER_ONLY && y != y1 && y != y2-1 && x == x1+1) {x = x2-1;} // jump to right edge
float const delta(get_height(x, y) - avg);
diff += delta*delta; // square the difference
}
}
return diff;
}
public:
city_plot_gen_t() : last_rgi(0), bcube(all_zeros) {}
void init(float *heightmap_, unsigned xsize_, unsigned ysize_) {
heightmap = heightmap_; xsize = xsize_; ysize = ysize_;
assert(heightmap != nullptr);
assert(xsize > 0 && ysize > 0); // any size is okay
if (rand_gen_index != last_rgi) {rgen.set_state(rand_gen_index, 12345); last_rgi = rand_gen_index;} // only when rand_gen_index changes
}
bool find_best_city_location(unsigned wmin, unsigned hmin, unsigned wmax, unsigned hmax, unsigned border, unsigned slope_width, unsigned num_samples,
unsigned &cx1, unsigned &cy1, unsigned &cx2, unsigned &cy2)
{
assert(num_samples > 0);
assert((wmax + 2*border) < xsize && (hmax + 2*border) < ysize); // otherwise the city can't fit in the map
unsigned const num_iters(100*num_samples); // upper bound
unsigned xend(xsize - wmax - 2*border + 1), yend(ysize - hmax - 2*border + 1); // max rect LLC, inclusive
unsigned num_cands(0);
float best_diff(0.0);
for (unsigned n = 0; n < num_iters; ++n) { // find min RMS height change across N samples
unsigned const x1(border + (rgen.rand()%xend)), y1(border + (rgen.rand()%yend));
unsigned const x2(x1 + ((wmin == wmax) ? wmin : rgen.rand_int(wmin, wmax)));
unsigned const y2(y1 + ((hmin == hmax) ? hmin : rgen.rand_int(hmin, hmax)));
if (overlaps_used (x1-slope_width, y1-slope_width, x2+slope_width, y2+slope_width)) continue; // skip if plot expanded by slope_width overlaps an existing city
if (any_underwater(x1, y1, x2, y2, CHECK_HEIGHT_BORDER_ONLY)) continue; // skip
float const diff(get_rms_height_diff(x1, y1, x2, y2));
if (num_cands == 0 || diff < best_diff) {cx1 = x1; cy1 = y1; cx2 = x2; cy2 = y2; best_diff = diff;}
if (++num_cands == num_samples) break; // done
} // for n
if (num_cands == 0) return 0;
//cout << "City cands: " << num_cands << ", diff: " << best_diff << ", loc: " << (cx1+cx2)/2 << "," << (cy1+cy2)/2 << endl;
return 1; // success
}
float flatten_region(unsigned x1, unsigned y1, unsigned x2, unsigned y2, unsigned slope_width, float const *const height=nullptr) {
float const elevation(height ? *height : get_avg_height(x1, y1, x2, y2));
flatten_region_to(x1, y1, x2, y2, slope_width, elevation);
return elevation;
}
bool check_plot_sphere_coll(point const &pos, float radius, bool xy_only=1) const {
if (plots.empty()) return 0;
point const query_pos(pos - get_camera_coord_space_xlate());
if (!check_bcube_sphere_coll(bcube, query_pos, radius, xy_only)) return 0;
return check_bcubes_sphere_coll(plots, query_pos, radius, xy_only);
}
}; // city_plot_gen_t
class city_road_gen_t {
struct range_pair_t {
unsigned s, e; // Note: e is one past the end
range_pair_t(unsigned s_=0, unsigned e_=0) : s(s_), e(e_) {}
void update(unsigned v) {
if (s == 0 && e == 0) {s = v;} // first insert
else {assert(s < e && v >= e);} // v must strictly increase
e = v+1; // one past the end
}
};
class road_draw_state_t : public draw_state_t {
quad_batch_draw qbd_batched[NUM_RD_TYPES], qbd_sl, qbd_bridge;
float ar;
public:
road_draw_state_t() : ar(1.0) {}
void pre_draw(vector3d const &xlate_, bool use_dlights_, bool shadow_only) {
draw_state_t::pre_draw(xlate_, use_dlights_, shadow_only, 0); // always_setup_shader=0
ar = city_params.get_road_ar();
}
virtual void draw_unshadowed() {
for (unsigned i = 0; i < NUM_RD_TYPES; ++i) { // only unshadowed blocks
road_mat_mgr.set_texture(i);
qbd_batched[i].draw_and_clear();
}
}
virtual void post_draw() {
draw_state_t::post_draw();
if (qbd_sl.empty()) return; // no stoplights to draw
glDepthFunc(GL_LEQUAL); // helps prevent Z-fighting
shader_t s;
s.begin_simple_textured_shader(); // Note: no lighting
road_mat_mgr.set_stoplight_texture();
qbd_sl.draw_and_clear();
s.end_shader();
glDepthFunc(GL_LESS);
}
template<typename T> void add_road_quad(T const &r, quad_batch_draw &qbd, colorRGBA const &color) {add_flat_road_quad(r, qbd, color, ar);} // generic flat road case (plot/park)
void add_road_quad(road_seg_t const &r, quad_batch_draw &qbd, colorRGBA const &color) {r.add_road_quad(qbd, color, ar);} // road segment
void add_road_quad(road_t const &r, quad_batch_draw &qbd, colorRGBA const &color) {r.add_road_quad(qbd, color, ar/TRACKS_WIDTH);} // tracks
template<typename T> void draw_road_region(vector<T> const &v, range_pair_t const &rp, quad_batch_draw &cache, unsigned type_ix) {
if (rp.s == rp.e) return; // empty
assert(rp.s <= rp.e);
assert(rp.e <= v.size());
assert(type_ix < NUM_RD_TYPES);
colorRGBA const color(road_colors[type_ix]);
if (cache.empty()) { // generate and cache quads
for (unsigned i = rp.s; i < rp.e; ++i) {add_road_quad(v[i], cache, color);}
}
if (emit_now) { // draw shadow blocks directly
road_mat_mgr.set_texture(type_ix);
cache.draw();
} else {qbd_batched[type_ix].add_quads(cache);} // add non-shadow blocks for drawing later
}
void draw_bridge(bridge_t const &bridge, bool shadow_only) { // Note: called rarely, so doesn't need to be efficient
//timer_t timer("Draw Bridge"); // 0.065ms - 0.11ms
cube_t bcube(bridge);
float const scale(1.0*city_params.road_width);
bcube.z2() += 2.0*scale; // make it higher
unsigned const d(bridge.dim);
float const l_expand(2.0*(d ? DY_VAL : DY_VAL)); // slight expand along road dim so that we're sure to cover the entire gap
float const w_expand(0.25*scale); // expand width to add space for supports
bcube.d[ d][0] -= l_expand; bcube.d[ d][1] += l_expand;
bcube.d[!d][0] -= w_expand; bcube.d[!d][1] += w_expand;
max_eq(bcube.d[d][0], bridge.src_road.d[d][0]); // clamp to orig road segment length
min_eq(bcube.d[d][1], bridge.src_road.d[d][1]);
if (!check_cube_visible(bcube, 1.0, shadow_only)) return; // VFC/too far
point const cpos(camera_pdu.pos - xlate);
float const center(0.5*(bcube.d[!d][1] + bcube.d[!d][0])), len(bcube.d[d][1] - bcube.d[d][0]);
point p1, p2; // centerline end points
p1.z = bridge.get_start_z();
p2.z = bridge.get_end_z();
p1[!d] = p2[!d] = center;
p1[ d] = bcube.d[d][0];
p2[ d] = bcube.d[d][1];
vector3d const delta(p2 - p1);
colorRGBA const main_color(WHITE), cables_color(LT_GRAY), concrete_color(LT_GRAY);
color_wrapper const cw_main(main_color), cw_cables(cables_color), cw_concrete(concrete_color);
float const thickness(0.2*scale), conn_thick(0.25*thickness), cable_thick(0.1*thickness), wall_width(0.25*thickness), wall_height(0.5*thickness);
point const closest_pt((bridge + xlate).closest_pt(camera_pdu.pos));
float const dist_val(shadow_only ? 1.0 : p2p_dist(camera_pdu.pos, closest_pt)/get_draw_tile_dist());
int const cable_ndiv(min(24, max(4, int(0.4/dist_val))));
unsigned const num_segs(max(16U, min(48U, unsigned(ceil(2.5*len/scale))))); // scale to fit the gap, with reasonable ranges
float const step_sz(1.0/num_segs), delta_d(step_sz*delta[d]), delta_z(step_sz*delta.z);
float zvals[48+1], cur_zpos(p1.z), cur_dval(p1[d]); // resize zvals based on max num_segs
vector<float> sm_split_pos;
uint64_t prev_tile_id(0);
point query_pt(bridge.get_cube_center());
ensure_shader_active(); // needed for use_smap=0 case
if (!shadow_only) {select_texture(WHITE_TEX);}
for (unsigned n = 0; n <= num_segs; ++n) { // populate zvals and dvals
float const t(n*step_sz), v(2.0*fabs(t - 0.5)), zpos(p1.z + delta.z*t);
zvals[n] = zpos + 0.3*len*(1.0 - v*v) - 0.5*scale;
}
for (unsigned n = 0; n < num_segs; ++n) { // add arches
float const zval(zvals[n+1]), next_dval(cur_dval + delta_d);
point pts[4], conn_pts[2];
pts[0][d] = pts[3][d] = cur_dval;
pts[1][d] = pts[2][d] = next_dval;
if (!shadow_only) {
query_pt[d] = 0.5*(cur_dval + next_dval); // center point of this segment
uint64_t const tile_id(get_tile_id_containing_point(query_pt + xlate));
if (n == 0 || tile_id != prev_tile_id) { // first segment, or new tile for this segment
qbd_bridge.draw_and_clear(); // flush
begin_tile(query_pt, 1); // will_emit_now=1
prev_tile_id = tile_id;
if (n > 0) {sm_split_pos.push_back(query_pt[d]);} // record split point for splitting road surface and guardrails
}
}
for (unsigned e = 0; e < 2; ++e) { // two sides
float const ndv(bcube.d[!d][e]);
pts[0][!d] = pts[1][!d] = (e ? ndv-w_expand : ndv);
pts[2][!d] = pts[3][!d] = (e ? ndv : ndv+w_expand);
for (unsigned f = 0; f < 2; ++f) { // top/bottom surfaces
float const dz(f ? 0.0 : thickness);
pts[0].z = pts[3].z = zvals[n] + dz;
pts[1].z = pts[2].z = zval + dz;
// construct previous and next pts in order to calculate the face normals below
point prev_pt(pts[0]); prev_pt[d] -= delta_d; if (n > 0 ) {prev_pt.z = zvals[n-1] + dz;}
point next_pt(pts[1]); next_pt[d] += delta_d; if (n+1 < num_segs) {next_pt.z = zvals[n+2] + dz;}
vector3d const v_shared((pts[2] - pts[1])*((f^d) ? -1.0 : 1.0));
vector3d const fn(cross_product((pts[1] - pts[0]), v_shared));
if (dot_product((cpos - pts[0]), fn) > 0) { // BFC
vector3d const fn_prev(cross_product((pts[0] - prev_pt), v_shared)), fn_next(cross_product((next_pt - pts[1]), v_shared));
vector3d const vn_prev((fn + fn_prev).get_norm()), vn_next((fn + fn_next).get_norm()); // average of both face normals
vector3d const n[4] = {vn_prev, vn_next, vn_next, vn_prev};
qbd_bridge.add_quad_pts_vert_norms(pts, n, cw_main);
}
}
for (unsigned f = 0; f < 2; ++f) { // side surfaces
unsigned const i0(f ? 3 : 0), i1(f ? 2 : 1);
point pts2[4] = {pts[i0], pts[i1], pts[i1], pts[i0]};
pts2[2].z += thickness; pts2[3].z += thickness; // top surface
add_bridge_quad(pts2, cw_main, ((f^d) ? -1.0 : 1.0));
}
if (zval > cur_zpos) { // vertical cables
conn_pts[e] = 0.5*(pts[1] + pts[2]);
conn_pts[e].z += 0.5*thickness;
if (!shadow_only && dist_val < 0.1) { // use high detail vertical cylinders
s.set_cur_color(cables_color);
point const &p(conn_pts[e]);
draw_fast_cylinder(point(p.x, p.y, cur_zpos), point(p.x, p.y, zval), cable_thick, cable_thick, cable_ndiv, 0); // no ends
}
else { // use lower detail cubes; okay for shadow pass
cube_t c(conn_pts[e]);
c.z1() = cur_zpos;
c.z2() = zval;
c.expand_by(cable_thick);
draw_cube(qbd_bridge, c, cw_cables, 1); // skip_bottom=1
}
}
} // for e
if (zval > cur_zpos + 0.5*scale) { // add horizontal connectors if high enough
cube_t c(conn_pts[0], conn_pts[1]);
vector3d exp(zero_vector);
exp.z = 0.75*conn_thick;
exp[d] = conn_thick;
c.expand_by(exp);
draw_cube(qbd_bridge, c, cw_main, 0); // skip_bottom=0
}
cur_dval = next_dval;
cur_zpos += delta_z;
} // for s
// bottom surface
float tscale(0.0);
if (!shadow_only) {
qbd_bridge.draw_and_clear(); // flush before texture change
select_texture(get_texture_by_name("roads/asphalt.jpg"));
tscale = 1.0/scale; // scale texture to match road width
}
bool const dir(bridge.slope), invert_normals((d!=0) ^ dir);
float const dz_scale(bridge.dz()/(bridge.d[d][1] - bridge.d[d][0]));
cur_dval = bcube.d[d][0]; // reset to start
point pts[8];
for (unsigned n = 0; n <= sm_split_pos.size(); ++n) {
float const next_dval((n == sm_split_pos.size()) ? bcube.d[d][1] : sm_split_pos[n]);
cube_t bot_bc(bcube);
bot_bc.d[ d][0] = cur_dval; // one tile slice
bot_bc.d[ d][1] = next_dval;
bot_bc.d[!d][0] += 0.4*w_expand;
bot_bc.d[!d][1] -= 0.4*w_expand;
float extend_dz1(dz_scale*(bridge.d[d][0] - bot_bc.d[d][0])), extend_dz2(dz_scale*(bot_bc.d[d][1] - bridge.d[d][1]));
if (dir) {swap(extend_dz1, extend_dz2);}
float const z1(bridge.z1() - extend_dz1 - 0.25*ROAD_HEIGHT), z2(bridge.z2() + extend_dz2 - 0.25*ROAD_HEIGHT); // move slightly downward
point bot_center(bot_bc.get_cube_center());
bot_center.z = 0.5*(z1 + z2) - 0.5*wall_width;
if (!sm_split_pos.empty()) { // multiple tiles, must select a new shadow map set
query_pt[d] = cur_dval;
begin_tile(query_pt, 1); // will_emit_now=1
}
// add bottom road/bridge surface
set_cube_pts(bot_bc, z1-wall_width, z2-wall_width, z1, z2, (d != 0), dir, pts);
draw_cube(qbd_bridge, cw_concrete, bot_center, pts, 0, invert_normals, tscale); // skip_bottom=0
// add guardrails/walls
for (unsigned e = 0; e < 2; ++e) { // two sides
cube_t side_bc(bot_bc);
side_bc.d[!d][!e] = side_bc.d[!d][e] + (e ? -wall_width : wall_width);
point side_center(side_bc.get_cube_center());
side_center.z = 0.5*(z1 + z2) + 0.5*wall_height;
set_cube_pts(side_bc, z1, z2, z1+wall_height, z2+wall_height, (d != 0), dir, pts);
draw_cube(qbd_bridge, cw_concrete, side_center, pts, 1, invert_normals, tscale); // skip_bottom=1
}
qbd_bridge.draw_and_clear(); // flush
cur_dval = next_dval;
} // for n
}
void add_bridge_quad(point const pts[4], color_wrapper const &cw, float normal_scale) {
vector3d const normal(cross_product((pts[1] - pts[0]), (pts[3] - pts[0]))*normal_scale);
if (dot_product(((camera_pdu.pos - xlate) - pts[0]), normal) < 0) return; // BFC
qbd_bridge.add_quad_pts(pts, cw, normal.get_norm());
}
void draw_tunnel(tunnel_t const &tunnel, bool shadow_only) { // Note: called rarely, so doesn't need to be efficient
cube_t const bcube(tunnel.get_tunnel_bcube());
if (!check_cube_visible(bcube, 1.0, shadow_only)) return; // VFC/too far
ensure_shader_active(); // needed for use_smap=0 case
quad_batch_draw &qbd(qbd_bridge); // use same qbd as bridges
color_wrapper cw_concrete(LT_GRAY);
bool const d(tunnel.dim), invert_normals(d);
float const scale(1.0*city_params.road_width), wall_thick(TUNNEL_WALL_THICK*city_params.road_width), width(max(0.5*tunnel.get_width(), 2.0*(d ? DX_VAL : DY_VAL)));
float zf(tunnel.ends[0].z1()), zb(tunnel.ends[1].z1());
float const end_ext(2.0*(d ? DY_VAL : DX_VAL)), dz_ext(end_ext*(zb - zf)/tunnel.get_length());
zf -= dz_ext; zb += dz_ext; // adjust zvals for extension
cube_t cubes[4];
tunnel.calc_top_bot_side_cubes(cubes);
float const tile_sz(d ? MESH_Y_SIZE*DY_VAL : MESH_X_SIZE*DX_VAL), xy1(cubes[0].d[d][0]), xy2(cubes[0].d[d][1]), length(xy2 - xy1);
unsigned const num_segs(ceil(length/tile_sz));
float const dt(1.0/num_segs);
point pts[8];
float tscale(0.0);
if (!shadow_only) {
s.add_uniform_float("hemi_lighting_scale", 0.1); // mostly disable hemispherical lighting, which doesn't work for tunnel interiors
select_texture(get_texture_by_name("roads/asphalt.jpg"));
tscale = 1.0/scale; // scale texture to match road width
}
for (unsigned s = 0; s < num_segs; ++s) { // split into segments, one per tile shadow map
float const t1(s*dt), t2((s+1)*dt), tmid(0.5*(t1 + t2)); // in range [0.0, 1.0]
point center(tunnel.get_cube_center());
center[d] = xy1 + tmid*length; // halfway between the end points
begin_tile(center, 1);
for (unsigned i = 0; i < 4; ++i) { // add tunnel top, bottom, and sides
cube_t c(cubes[i]); // deep copy so we can modify it
c.d[d][0] = xy1 + t1*length;
c.d[d][1] = xy1 + t2*length;
float const dz(zb - zf), zft(zf + t1*dz), zbt(zf + t2*dz);
point center(c.get_cube_center());
center.z += 0.5*(zft + zbt);
// Note: could use draw_cylindrical_section() or draw_circle_normal() for cylindrical tunnel
set_cube_pts(c, c.z1()+zft, c.z1()+zbt, c.z2()+zft, c.z2()+zbt, d, 0, pts); // dir=0 here
draw_cube(qbd, cw_concrete, center, pts, (!shadow_only && i != 1), invert_normals, tscale); // skip_bottom=1 for all but the top cube unless shadowed
} // for i
qbd.draw_and_clear();
} // for s
if (!shadow_only) {
s.add_uniform_float("hemi_lighting_scale", 0.5); // set back to the default of 0.5
select_texture(get_texture_by_name("cblock2.jpg"));
tscale *= 4.0;
}
for (unsigned n = 0; n < 2; ++n) { // add tunnel facades
cube_t const &tend(tunnel.ends[n]);
cube_t c(tend);
c.z1() += tunnel.height - 0.5*wall_thick; // tunnel ceiling
c.z2() += tunnel.facade_height[n]; // high enough to cover the hole in the mesh
c.d[d][0] -= 0.9*end_ext; c.d[d][1] += 0.9*end_ext; // extend to cover the gaps in the mesh (both dirs) - slightly less than interior so that it sticks out
begin_tile(c.get_cube_center(), 1); // required for long tunnels where facade is in a different tile from the tunnel center
draw_cube(qbd, c, cw_concrete, 0, tscale); // skip_bottom=0
c.z1() = tunnel.ends[n].z1() - wall_thick; // extend to the bottom
c.d[!d][0] = tunnel.ends[n].d[!d][0] - width; c.d[!d][1] = tend.d[!d][0]; // left side
draw_cube(qbd, c, cw_concrete, 1, tscale); // skip_bottom=1
c.d[!d][1] = tunnel.ends[n].d[!d][1] + width; c.d[!d][0] = tend.d[!d][1]; // right side
draw_cube(qbd, c, cw_concrete, 0, tscale); // skip_bottom=0 in case there are overhangs due to steep cliffs
qbd.draw_and_clear();
} // for n
}
void draw_stoplights(vector<road_isec_t> const &isecs, bool shadow_only) {
for (auto i = isecs.begin(); i != isecs.end(); ++i) {i->draw_stoplights(qbd_sl, *this, shadow_only);}
}
}; // road_draw_state_t
struct bench_t : public sphere_t {
bool dim, dir;
cube_t bcube;
bench_t() : dim(0), dir(0) {}
bench_t(point const &pos_, float radius_, bool dim_, bool dir_) : sphere_t(pos_, radius_), dim(dim_), dir(dir_) {calc_bcube();}
void calc_bcube() {
bcube.set_from_point(pos);
bcube.expand_by(vector3d((dim ? 0.32 : 1.0), (dim ? 1.0 : 0.32), 0.0)*radius);
bcube.z2() += 0.85*radius; // set bench height
}
static void pre_draw(draw_state_t &dstate, bool shadow_only) {
if (!shadow_only) {select_texture(FENCE_TEX);} // normal map?
}
static void post_draw(draw_state_t &dstate, bool shadow_only) {}
void draw(draw_state_t &dstate, quad_batch_draw &qbd, bool shadow_only) const {
if (!dstate.check_cube_visible(bcube, 0.16, shadow_only)) return; // dist_scale=0.16
dstate.ensure_shader_active(); // needed for use_smap=0 case
dstate.begin_tile(pos, 1);
cube_t cubes[] = { // Note: taken from mapx/bench.txt
cube_t(-0.4, 0.0, -5.0, 5.0, 1.6, 5.0), // back (straight)
cube_t( 0.0, 4.0, -5.35, 5.35, 1.6, 2.0), // seat
cube_t( 0.3, 1.3, -5.3, -4.7, 0.0, 1.6), // legs
cube_t( 2.7, 3.7, -5.3, -4.7, 0.0, 1.6),
cube_t( 0.3, 1.3, 4.7, 5.3, 0.0, 1.6),
cube_t( 2.7, 3.7, 4.7, 5.3, 0.0, 1.6),
cube_t(-0.5, 3.8, -5.4, -4.5, 3.0, 3.2), // arms
cube_t(-0.5, 3.8, 4.5, 5.4, 3.0, 3.2),
cube_t( 0.8, 1.2, -5.1, -4.9, 2.0, 3.0), // arm supports
cube_t( 2.8, 3.2, -5.1, -4.9, 2.0, 3.0),
cube_t( 0.8, 1.2, 4.9, 5.1, 2.0, 3.0),
cube_t( 2.8, 3.2, 4.9, 5.1, 2.0, 3.0),
};
point const center(pos + dstate.xlate);
float const dist_val(shadow_only ? 0.0 : p2p_dist(camera_pdu.pos, center)/get_draw_tile_dist());
cube_t bc; // bench bbox
for (unsigned i = 0; i < 12; ++i) { // back still contributes to bbox
if (dir) {swap(cubes[i].d[0][0], cubes[i].d[0][1]); cubes[i].d[0][0] *= -1.0; cubes[i].d[0][1] *= -1.0;}
if (!dim) {swap(cubes[i].d[0][0], cubes[i].d[1][0]); swap(cubes[i].d[0][1], cubes[i].d[1][1]);}
if (i == 0) {bc = cubes[i];} else {bc.union_with_cube(cubes[i]);}
}
point const c1(bcube.get_cube_center()), c2(bc.get_cube_center());
vector3d const scale(bcube.get_dx()/bc.get_dx(), bcube.get_dy()/bc.get_dy(), bcube.get_dz()/bc.get_dz()); // scale to fit to target cube
color_wrapper const cw(WHITE);
unsigned const num(shadow_only ? 6U : max(1U, min(6U, unsigned(0.2/dist_val)))); // simple distance-based LOD, in pairs
for (unsigned i = 1; i < 2*num; ++i) {dstate.draw_cube(qbd, ((cubes[i] - c2)*scale + c1), cw, 1);} // skip back
point pts[4] = {point(-1.0, -5.0, 5.0), point(-1.0, 5.0, 5.0), point(0.2, 5.0, 1.6), point(0.2, -5.0, 1.6)}; // Note: back not drawn
point f[4], b[4];
for (unsigned i = 0; i < 4; ++i) {
if (dir) {pts[i].x *= -1.0;}
if (!dim) {swap(pts[i].x, pts[i].y);}
pts[i] = ((pts[i] - c2)*scale + c1);
}
vector3d const normal(get_poly_norm(pts, 1)), delta((0.2*scale.x)*normal); // thickness = 0.4
UNROLL_4X(f[i_] = pts[i_] + delta;);
qbd.add_quad_pts(f, WHITE, normal);
UNROLL_4X(b[i_] = pts[i_] - delta;);
qbd.add_quad_pts(b, WHITE, -normal);
for (unsigned i = 0; i < 4; ++i) { // draw sides
unsigned const j((i+1)&3); // next i
point const s[4] = {f[i], b[i], b[j], f[j]};
qbd.add_quad_pts(s, WHITE, get_poly_norm(s, 1));
}
qbd.draw_and_clear(); // draw with current smap
}
bool proc_sphere_coll(point &pos, point const &p_last, float radius, point const &xlate, vector3d *cnorm) const {
return sphere_cube_int_update_pos(pos, radius, (bcube + xlate), p_last, 1, 0, cnorm);
}
};
class city_obj_placer_t {
public: // road network needs access to parking lots for drawing
vector<parking_lot_t> parking_lots;
private:
vector<bench_t> benches;
unsigned num_spaces, filled_spaces;
bool gen_parking_lots_for_plot(cube_t plot, vector<car_t> &cars, unsigned city_id, vector<cube_t> &bcubes, rand_gen_t &rgen) {
vector3d const nom_car_size(city_params.get_nom_car_size()); // {length, width, height}
float const space_width(PARK_SPACE_WIDTH *nom_car_size.y); // add 50% extra space between cars
float const space_len (PARK_SPACE_LENGTH*nom_car_size.x); // space for car + gap for cars to drive through
float const pad_dist (1.0*nom_car_size.x); // one car length
plot.expand_by_xy(-pad_dist);
if (bcubes.empty()) return 0; // shouldn't happen, unless buildings are disabled; skip to avoid perf problems with an entire plot of parking lot
unsigned const first_corner(rgen.rand()&3); // 0-3
bool const car_dim(rgen.rand() & 1); // 0=cars face in X; 1=cars face in Y
bool const car_dir(rgen.rand() & 1);
float const xsz(car_dim ? space_width : space_len), ysz(car_dim ? space_len : space_width);
bool has_parking(0);
//cout << "max_row_sz: " << floor(plot.get_size()[!car_dim]/space_width) << ", max_num_rows: " << floor(plot.get_size()[car_dim]/space_len) << endl;
car_t car;
car.park();
car.cur_city = city_id;
car.cur_road_type = TYPE_PLOT;
for (unsigned c = 0; c < 4; ++c) {// generate 0-4 parking lots per plot, starting at the corners, in random order
unsigned const cix((first_corner + c) & 3), xdir(cix & 1), ydir(cix >> 1), wdir(car_dim ? xdir : ydir), rdir(car_dim ? ydir : xdir);
float const dx(xdir ? -xsz : xsz), dy(ydir ? -ysz : ysz), dw(car_dim ? dx : dy), dr(car_dim ? dy : dx); // delta-wdith and delta-row
point const corner_pos(plot.d[0][xdir], plot.d[1][ydir], (plot.z1() + 0.1*ROAD_HEIGHT)); // shift up slightly to avoid z-fighting
assert(dw != 0.0 && dr != 0.0);
parking_lot_t cand(cube_t(corner_pos, corner_pos), car_dim, car_dir, city_params.min_park_spaces, city_params.min_park_rows); // start as min size at the corner
cand.d[!car_dim][!wdir] += cand.row_sz*dw;
cand.d[ car_dim][!rdir] += cand.num_rows*dr;
if (!plot.contains_cube_xy(cand)) {continue;} // can't fit a min size parking lot in this plot, so skip it (shouldn't happen)
if (has_bcube_int_xy(cand, bcubes, pad_dist)) continue; // intersects a building - skip (can't fit min size parking lot)
cand.z2() += plot.get_dz(); // probably unnecessary
parking_lot_t park(cand);
// try to add more parking spaces in a row
for (; plot.contains_cube_xy(cand); ++cand.row_sz, cand.d[!car_dim][!wdir] += dw) {
if (has_bcube_int_xy(cand, bcubes, pad_dist)) break; // intersects a building - done
park = cand; // success: increase parking lot to this size
}
cand = park;
// try to add more rows of parking spaces
for (; plot.contains_cube_xy(cand); ++cand.num_rows, cand.d[car_dim][!rdir] += dr) {
if (has_bcube_int_xy(cand, bcubes, pad_dist)) break; // intersects a building - done
park = cand; // success: increase parking lot to this size
}
assert(park.row_sz >= city_params.min_park_spaces && park.num_rows >= city_params.min_park_rows);
assert(park.get_dx() > 0.0 && park.get_dy() > 0.0);
parking_lots.push_back(park);
//parking_lots.back().expand_by_xy(0.5*pad_dist); // re-add half the padding for drawing (breaks texture coord alignment)
bcubes.push_back(park); // add to list of blocker bcubes so that no later parking lots overlap this one
num_spaces += park.row_sz*park.num_rows;
// fill the parking lot with cars
vector3d car_sz(nom_car_size);
car.dim = car_dim;
car.dir = car_dir;
car.height = car_sz.z;
if (car.dim) {swap(car_sz.x, car_sz.y);}
point pos(corner_pos.x, corner_pos.y, (plot.z2() + 0.5*car_sz.z));
pos[ car_dim] += 0.5*dr + (car_dim ? 0.15 : -0.15)*fabs(dr); // offset for centerline, biased toward the front of the parking space
float const car_density(rgen.rand_uniform(city_params.min_park_density, city_params.max_park_density));
for (unsigned row = 0; row < park.num_rows; ++row) {
pos[!car_dim] = corner_pos[!car_dim] + 0.5*dw; // half offset for centerline
bool prev_was_bad(0);
for (unsigned col = 0; col < park.row_sz; ++col) {
if (prev_was_bad) {prev_was_bad = 0;} // previous car did a bad parking job, leave this space empty
else if (rgen.rand_float() < car_density) { // only half the spaces are filled on average
point cpos(pos);
cpos[ car_dim] += 0.05*dr*rgen.rand_uniform(-1.0, 1.0); // randomness of front amount
cpos[!car_dim] += 0.12*dw*rgen.rand_uniform(-1.0, 1.0); // randomness of side amount
if (col+1 != park.row_sz && (rgen.rand()&15) == 0) {// occasional bad parking job
cpos[!car_dim] += dw*rgen.rand_uniform(0.3, 0.35);
prev_was_bad = 1;
}
car.bcube.set_from_point(cpos);
car.bcube.expand_by(0.5*car_sz);
cars.push_back(car);
if ((rgen.rand()&7) == 0) {cars.back().dir ^= 1;} // pack backwards 1/8 of the time
++filled_spaces;
has_parking = 1;
}
pos[!car_dim] += dw;
} // for col
pos[car_dim] += dr;
} // for row
} // for c
return has_parking;
}
static bool check_pt_and_place_blocker(point const &pos, vector<cube_t> &blockers, float radius, float extra_spacing) {
cube_t bc(pos);
if (has_bcube_int_xy(bc, blockers, radius)) return 0; // intersects a building or parking lot - skip
bc.expand_by_xy(extra_spacing);
blockers.push_back(bc); // prevent trees from being too close to each other
return 1;
}
static bool try_place_obj(cube_t const &plot, vector<cube_t> &blockers, rand_gen_t &rgen, float radius, float extra_spacing, float num_tries, point &pos) {
for (unsigned t = 0; t < num_tries; ++t) {
pos = point(rgen.rand_uniform(plot.x1()+radius, plot.x2()-radius), rgen.rand_uniform(plot.y1()+radius, plot.y2()-radius), plot.z1());
if (check_pt_and_place_blocker(pos, blockers, radius, extra_spacing)) return 1; // success
} // for t
return 0;
}
static void place_trees_in_plot(cube_t const &plot, vector<cube_t> &blockers, rand_gen_t &rgen) {
if (city_params.max_trees_per_plot == 0) return;
float const radius(city_params.tree_spacing*city_params.get_nom_car_size().x); // in multiples of car length
vector3d const plot_sz(plot.get_size());
if (min(plot_sz.x, plot_sz.y) < 4.0*radius) return; // plot is too small for trees of this size
for (unsigned n = 0; n < city_params.max_trees_per_plot; ++n) {
point pos;
if (!try_place_obj(plot, blockers, rgen, radius, radius, 10, pos)) continue; // 10 tries per tree
int const ttype(rgen.rand()%100); // Note: okay to leave at -1; also, don't have to set to a valid tree type
tree_placer.add(pos, 0, ttype); // size is randomly selected by the tree generator using default values
// now that we're here, try to place more trees at this same distance from the road in a row
bool const dim(min((pos.x - plot.x1()), (plot.x2() - pos.x)) < min((pos.y - plot.y1()), (plot.y2() - pos.y)));
bool const dir((pos[dim] - plot.d[dim][0]) < (plot.d[dim][1] - pos[dim]));
float const step(2.5*radius*(dir ? 1.0 : -1.0)); // positive or negative (must be > 2x radius spacing)
for (; n < city_params.max_trees_per_plot; ++n) {
pos[dim] += step;
if (pos[dim] < plot.d[dim][0]+radius || pos[dim] > plot.d[dim][1]-radius) break; // outside place area
if (!check_pt_and_place_blocker(pos, blockers, radius, radius)) break; // placement failed
tree_placer.add(pos, 0, ttype); // use same tree type
} // for n
} // for n
}
void place_detail_objects(cube_t const &plot, vector<cube_t> &blockers, rand_gen_t &rgen) {
bench_t bench;
bench.radius = 0.3*city_params.get_nom_car_size().x;
for (unsigned n = 0; n < city_params.max_benches_per_plot; ++n) {
if (!try_place_obj(plot, blockers, rgen, bench.radius, bench.radius, 1, bench.pos)) continue; // 1 try
float dmin(0.0);
for (unsigned dim = 0; dim < 2; ++dim) {
for (unsigned dir = 0; dir < 2; ++dir) {
float const dist(fabs(bench.pos[dim] - plot.d[dim][dir])); // find closest distance to road (plot edge) and orient bench that way
if (dmin == 0.0 || dist < dmin) {bench.dim = !dim; bench.dir = !dir; dmin = dist;}
}
}
bench.calc_bcube();
benches.push_back(bench);
} // for n
}
template<typename T> void draw_objects(vector<T> const &objs, draw_state_t &dstate, bool shadow_only) const {
if (objs.empty()) return;
T::pre_draw(dstate, shadow_only);
quad_batch_draw qbd;
for (auto i = objs.begin(); i != objs.end(); ++i) {
if (dstate.check_sphere_visible(i->pos, i->radius)) {i->draw(dstate, qbd, shadow_only);}
}
qbd.draw();
T::post_draw(dstate, shadow_only);
}
public:
city_obj_placer_t() : num_spaces(0), filled_spaces(0) {}
void clear() {parking_lots.clear(); num_spaces = filled_spaces = 0;}
static bool has_bcube_int_xy(cube_t const &bcube, vector<cube_t> const &bcubes, float pad_dist=0.0) {
cube_t tc(bcube);
tc.expand_by(pad_dist);
for (auto c = bcubes.begin(); c != bcubes.end(); ++c) {
if (c->intersects_xy(tc)) return 1; // intersection
}
return 0;
}
void gen_parking_and_place_objects(vector<road_plot_t> &plots, vector<car_t> &cars, unsigned city_id, bool have_cars) { // Note: fills in plots.has_parking
timer_t timer("Gen Parking Lots and Place Objects");
vector<cube_t> bcubes; // reused across calls
rand_gen_t rgen, detail_rgen;
parking_lots.clear();
rgen.set_state(city_id, 123);
detail_rgen.set_state(3145739*(city_id+1), 1572869*(city_id+1));
clear();
if (city_params.max_trees_per_plot > 0) {tree_placer.begin_block();}
bool const add_parking_lots(have_cars && city_params.min_park_spaces > 0 && city_params.min_park_rows > 0);
for (auto i = plots.begin(); i != plots.end(); ++i) {
bcubes.clear();
get_building_bcubes(*i, bcubes);
if (add_parking_lots) {i->has_parking = gen_parking_lots_for_plot(*i, cars, city_id, bcubes, rgen);}
place_trees_in_plot (*i, bcubes, detail_rgen);
place_detail_objects(*i, bcubes, detail_rgen);
} // for i
cout << "parking lots: " << parking_lots.size() << ", spaces: " << num_spaces << ", filled: " << filled_spaces << ", benches: " << benches.size() << endl;
}
void draw_detail_objects(draw_state_t &dstate, bool shadow_only) const {
draw_objects(benches, dstate, shadow_only);
}
bool proc_sphere_coll(point &pos, point const &p_last, float radius, vector3d *cnorm) const {
vector3d const xlate(get_camera_coord_space_xlate());
for (auto i = benches.begin(); i != benches.end(); ++i) {
if (i->proc_sphere_coll(pos, p_last, radius, xlate, cnorm)) return 1;
}
return 0;
}
bool line_intersect(point const &p1, point const &p2, float &t) const { // Note: nothing to do for parking lots
bool ret(0);
for (auto i = benches.begin(); i != benches.end(); ++i) {ret |= check_line_clip_update_t(p1, p2, t, i->bcube);} // check bounding cube
return ret;
}
bool pt_in_parking_lot_xy(point const &pos) const {
for (auto p = parking_lots.begin(); p != parking_lots.end(); ++p) {
if (p->contains_pt_xy(pos)) return 1;
}
return 0;
}
bool cube_overlaps_parking_lot_xy(cube_t const &c) const {
for (auto p = parking_lots.begin(); p != parking_lots.end(); ++p) {if (p->intersects(c)) return 1;}
return 0;
}
bool get_color_at_xy(point const &pos, colorRGBA &color) const {
for (auto i = benches.begin(); i != benches.end(); ++i) {
if (i->bcube.contains_pt_xy(pos)) {color = texture_color(FENCE_TEX); return 1;}
}
return 0;
}
}; // city_obj_placer_t
class road_network_t : public streetlights_t {
vector<road_t> roads; // full overlapping roads, for collisions, etc.
vector<road_seg_t> segs; // non-overlapping road segments, for drawing with textures
vector<road_isec_t> isecs[3]; // for drawing with textures: {4-way, 3-way, 2-way}
vector<road_plot_t> plots; // plots of land that can hold buildings
vector<bridge_t> bridges; // bridges, part of global road network
vector<tunnel_t> tunnels; // tunnels, part of global road network
vector<road_t> tracks, track_segs; // railroad tracks (for global road network)
//vector<road_isec_t> track_turns; // for railroad tracks
city_obj_placer_t city_obj_placer;
cube_t bcube;
vector<road_t> segments; // reused temporary
set<unsigned> connected_to; // vector?
map<uint64_t, unsigned> tile_to_block_map;
map<unsigned, road_isec_t const *> cix_to_isec; // maps city_ix to intersection
unsigned city_id, cluster_id;
//string city_name; // future work
float tot_road_len;
mutable unsigned num_cars; // Note: not counting parked cars; mutable so that car_manager can update this
// use only for the global road network
struct city_id_pair_t {
unsigned id[2]; // lo, hi
city_id_pair_t(unsigned c1, unsigned c2) {id[0] = c1; id[1] = c2;}
};
vector<city_id_pair_t> road_to_city; // indexed by road ID
vector<vector<unsigned>> city_to_seg; // maps city_id to set of road segments connecting to that city
static uint64_t get_tile_id_for_cube(cube_t const &c) {return get_tile_id_containing_point_no_xyoff(c.get_cube_center());}
struct cmp_by_tile { // not the most efficient solution, but no memory overhead
bool operator()(cube_t const &a, cube_t const &b) const {return (get_tile_id_for_cube(a) < get_tile_id_for_cube(b));}
};
struct tile_block_t { // collection of road parts for a given tile
range_pair_t ranges[NUM_RD_TYPES]; // {plot, seg, isec2, isec3, isec4, park_lot, tracks}
quad_batch_draw quads[NUM_RD_TYPES];
cube_t bcube;
tile_block_t(cube_t const &bcube_) : bcube(bcube_) {}
};
vector<tile_block_t> tile_blocks;
template<typename T> void add_tile_blocks(vector<T> &v, map<uint64_t, unsigned> &tile_to_block_map, unsigned type_ix) {
assert(type_ix < NUM_RD_TYPES);
sort(v.begin(), v.end(), cmp_by_tile());
for (unsigned i = 0; i < v.size(); ++i) {
uint64_t const tile_id(get_tile_id_for_cube(v[i]));
auto it(tile_to_block_map.find(tile_id));
unsigned block_id(0);
if (it == tile_to_block_map.end()) { // not found, add new block
tile_to_block_map[tile_id] = block_id = tile_blocks.size();
tile_blocks.push_back(tile_block_t(v[i]));
}
else {block_id = it->second;}
assert(block_id < tile_blocks.size());
tile_blocks[block_id].ranges[type_ix].update(i);
tile_blocks[block_id].bcube.union_with_cube(v[i]);
} // for i
}
road_seg_t const &get_seg(unsigned seg_ix) const {
assert(seg_ix < segs.size());
return segs[seg_ix];
}
public:
road_network_t() : bcube(all_zeros), city_id(CONN_CITY_IX), cluster_id(0), tot_road_len(0.0), num_cars(0) {} // global road network ctor
road_network_t(cube_t const &bcube_, unsigned city_id_) : bcube(bcube_), city_id(city_id_), cluster_id(0), tot_road_len(0.0), num_cars(0) {
bcube.d[2][1] += ROAD_HEIGHT; // make it nonzero size
}
cube_t const &get_bcube() const {return bcube;}
void set_bcube(cube_t const &bcube_) {bcube = bcube_;}
unsigned num_roads() const {return roads.size();}
vector<road_t> const &get_roads() const {return roads;} // used for connecting roads between cities with 4-way intersections
bool empty() const {return roads.empty();}
bool has_tunnels() const {return !tunnels.empty();}
void set_cluster(unsigned id) {cluster_id = id;}
void register_connected_city(unsigned id) {connected_to.insert(id);}
set<unsigned> const &get_connected() const {return connected_to;}
bool is_connected_to(unsigned id) const {return (connected_to.find(id) != connected_to.end());}
float get_traffic_density() const {return ((tot_road_len == 0.0) ? 0.0 : num_cars/tot_road_len);} // cars per unit road
void register_car() const {++num_cars;} // Note: must be const; num_cars is mutable
void clear() {
roads.clear();
segs.clear();
plots.clear();
bridges.clear();
tunnels.clear();
tracks.clear();
track_segs.clear();
for (unsigned i = 0; i < 3; ++i) {isecs[i].clear();}
streetlights.clear();
city_obj_placer.clear();
tile_blocks.clear();
}
bool gen_road_grid(float road_width, float road_spacing) {
cube_t const &region(bcube); // use our bcube as the region to process
vector3d const size(region.get_size());
assert(size.x > 0.0 && size.y > 0.0);
//rand_gen_t rgen; rgen.set_state(int(123.0*region.x1()), int(456.0*region.y1())); road_spacing *= rgen.rand_uniform(0.8, 1.2); // add some random variation?
float const half_width(0.5*road_width), zval(region.z1() + ROAD_HEIGHT);
float const rx1(region.x1() + half_width), rx2(region.x2() - half_width), ry1(region.y1() + half_width), ry2(region.y2() - half_width); // shrink to include centerlines
float road_pitch_x(road_width + road_spacing), road_pitch_y(road_pitch_x);
int const num_x_roads((rx2 - rx1)/road_pitch_x), num_y_roads((ry2 - ry1)/road_pitch_y);
road_pitch_x = 0.9999*(rx2 - rx1)/num_x_roads; // auto-calculate, round down slightly to avoid FP error
road_pitch_y = 0.9999*(ry2 - ry1)/num_y_roads;
//cout << "road pitch: " << road_pitch_x/DX_VAL << " " << road_pitch_y/DY_VAL << " road width: " << road_width/DX_VAL << " " << road_width/DY_VAL << endl;
//spacing = int((road_width + road_spacing)/DX_VAL)*DX_VAL - road_width;
// create a grid, for now; crossing roads will overlap
for (float x = rx1; x < rx2; x += road_pitch_x) {
roads.emplace_back(point(x, region.y1(), zval), point(x, region.y2(), zval), road_width, true);
}
unsigned const num_x(roads.size());
for (float y = ry1; y < ry2; y += road_pitch_y) {
roads.emplace_back(point(region.x1(), y, zval), point(region.x2(), y, zval), road_width, false);
}
unsigned const num_r(roads.size()), num_y(num_r - num_x);
if (num_x <= 1 || num_y <= 1) {clear(); return 0;} // not enough space for roads
bcube.x1() = roads[0 ].x1(); // actual bcube x1 from first x road
bcube.x2() = roads[num_x-1].x2(); // actual bcube x2 from last x road
bcube.y1() = roads[num_x ].y1(); // actual bcube y1 from first y road
bcube.y2() = roads[num_r-1].y2(); // actual bcube y2 from last y road
// create road segments and intersections
segs .reserve(num_x*(num_y-1) + (num_x-1)*num_y + 4); // X + Y segments, allocate one extra per side for connectors
plots.reserve((num_x-1)*(num_y-1));
if (num_x > 2 && num_y > 2) {
isecs[0].reserve(4); // 2-way, always exactly 4 at each corner
isecs[1].reserve(2*((num_x-2) + (num_y-2)) + 4); // 3-way, allocate one extra per side for connectors
isecs[2].reserve((num_x-2)*(num_y-2) + 4); // 4-way, allocate one extra per side for connectors
}
for (unsigned x = 0; x < num_x; ++x) {
for (unsigned y = num_x; y < num_r; ++y) {
bool const FX(x == 0), FY(y == num_x), LX(x+1 == num_x), LY(y+1 == num_r);
cube_t const &rx(roads[x]), &ry(roads[y]);
unsigned const num_conn((!FX) + (!LX) + (!FY) + (!LY));
if (num_conn < 2) continue; // error?
uint8_t const conn(((!FX) << 0) | ((!LX) << 1) | ((!FY) << 2) | ((!LY) << 3)); // 1-15
isecs[num_conn - 2].emplace_back(cube_t(rx.x1(), rx.x2(), ry.y1(), ry.y2(), zval, zval), y, x, conn, false); // intersections
if (!LX) { // skip last y segment
cube_t const &rxn(roads[x+1]);
segs.emplace_back(cube_t(rx.x2(), rxn.x1(), ry.y1(), ry.y2(), zval, zval), y, false); // y-segments
}
if (!LY) { // skip last x segment
cube_t const &ryn(roads[y+1]);
segs.emplace_back(cube_t(rx.x1(), rx.x2(), ry.y2(), ryn.y1(), zval, zval), x, true); // x-segments
if (!LX) { // skip last y segment
cube_t const &rxn(roads[x+1]);
plots.push_back(cube_t(rx.x2(), rxn.x1(), ry.y2(), ryn.y1(), zval, zval)); // plots between roads
}
}
} // for y
} // for x
return 1;
}
void gen_railroad_tracks(float width, unsigned num, cube_t const &region, vector<cube_t> const &blockers, heightmap_query_t &hq) {
rand_gen_t rgen;
assert(region.dx() > 0.0 && region.dy() > 0.0);
if (region.dx() <= 2.0*width || region.dy() <= 2.0*width) return; // region too small (shouldn't happen)
vector<cube_t> dim_tracks[2]; // one in each dim, for collision detection with tracks going in the other dim
for (unsigned n = 0; n < num; ++n) {
for (unsigned tries = 0; tries < city_params.num_conn_tries; ++tries) {
bool const dim(rgen.rand_bool());
float const rv(rgen.rand_uniform(0.2, 0.8)); // use center area
float const pos(region.d[!dim][0]*(1.0 - rv) + region.d[!dim][1]*rv);
float const step_sz(city_params.conn_road_seg_len);
float const seg_end(region.d[dim][1]);
point p1, p2;
p1[!dim] = p2[!dim] = pos;
p1[dim] = region.d[dim][0]; p2[dim] = seg_end; // full segment for blockers check
p1.z = p2.z = region.z1();
cube_t tracks_bcube(p1, p2);
if (city_obj_placer.has_bcube_int_xy(tracks_bcube, blockers, width)) continue; // check cities
if (city_obj_placer.has_bcube_int_xy(tracks_bcube, dim_tracks[dim], 8.0*width)) continue; // check prev placed tracks in same dim
dim_tracks[dim].push_back(tracks_bcube); // add to dim_tracks, but not to blockers, since we want roads to cross tracks
tracks.emplace_back(p1, p2, width, dim, (p2.z < p1.z), n); // Note: zvals are at 0, but should be unused
p2[dim] = (p1[dim] + step_sz); // back to starting segment
while (p1[dim] < seg_end) { // split into per-tile segments
p1.z = hq.get_height_at(p1.x, p1.y) + ROAD_HEIGHT;
p2.z = hq.get_height_at(p2.x, p2.y) + ROAD_HEIGHT;
track_segs.emplace_back(p1, p2, width, dim, (p2.z < p1.z), n);
p1[dim] += step_sz;
p2[dim] = min((p1[dim] + step_sz), seg_end);
} // end while
// TODO: check for collisions with roads and handle them with intersections, bridges, or tunnels
// TODO: handle slopes that are too steep and shadow artifacts
break; // success
} // for tries
} // for n
for (unsigned pass = 0; pass < 2; ++pass) { // flatten mesh after placing all tracks: regular, decrease_only
for (auto i = track_segs.begin(); i != track_segs.end(); ++i) {hq.flatten_for_road(*i, city_params.road_border, 0, (pass == 1));}
}
cout << "track segments: " << track_segs.size() << endl;
}
void calc_bcube_from_roads() { // Note: ignores isecs, plots, and bridges, which should be bounded by roads
if (roads.empty()) return; // no roads (assumes also no tracks)
bcube = calc_cubes_bcube(roads);
for (auto t = tracks.begin(); t != tracks.end(); ++t) {bcube.union_with_cube(*t);}
}
private:
int find_conn_int_seg(cube_t const &c, bool dim, bool dir) const {
float const min_seg_len(1.0*city_params.road_width);
for (unsigned i = 0; i < segs.size(); ++i) {
road_seg_t const &s(segs[i]);
if (s.dim == dim) continue; // not perp dim
if (s.d[dim][dir] != bcube.d[dim][dir]) continue; // not on edge of road grid
if (s.d[!dim][1] < c.d[!dim][0] || s.d[!dim][0] > c.d[!dim][1]) continue; // no overlap/projection in other dim
// c contained in segment in other dim with enough padding (min road width) on each side
if (c.d[!dim][0] > s.d[!dim][0]+min_seg_len && c.d[!dim][1] < s.d[!dim][1]-min_seg_len) return i; // this is the one we want
return -1; // partial overlap in other dim, can't split, fail
} // for i
return -1; // not found
}
int find_3way_int_at(cube_t const &c, bool dim, bool dir) const {
float const cube_cent(0.5*(c.d[!dim][0] + c.d[!dim][1]));
//float dmin(0.0);
int ret(-1);
for (unsigned i = 0; i < isecs[1].size(); ++i) {
road_isec_t const &isec(isecs[1][i]);
if (isec.d[dim][dir] != bcube.d[dim][dir]) continue; // not on edge of road grid
if (isec.d[!dim][1] > cube_cent && isec.d[!dim][0] < cube_cent) return i; // this is the one we want (early terminate case)
//float const int_cent(0.5*(isec.d[!dim][0] + isec.d[!dim][1])), dist(fabs(int_cent - cube_cent));
//if (ret < 0 || dist < dmin) {ret = i; dmin = dist;} // update if closer
} // for i
return ret; // not found if ret is still at -1
}
template<typename T> static void do_road_align(vector<T> &v, float from, float to, bool dim) {
for (auto i = v.begin(); i != v.end(); ++i) {
for (unsigned d = 0; d < 2; ++d) { // low, high edge
if (i->d[dim][d] == from) {i->d[dim][d] = to;}
}
} // for i
}
bool align_isec3_to(unsigned int3_ix, cube_t const &c, bool dim) {
assert(int3_ix < isecs[1].size());
cube_t const src(isecs[1][int3_ix]); // deep copy so that it's not changed below
//cout << c.d[!dim][0] << " " << c.d[!dim][1] << " " << src.d[!dim][0] << " " << src.d[!dim][1] << " " << (c.d[!dim][0] - src.d[!dim][0]) << endl; // TESTING
if (c.d[!dim][0] == src.d[!dim][0]) return 0; // already aligned - done
for (unsigned d = 0; d < 2; ++d) { // low, high
do_road_align(roads, src.d[!dim][d], c.d[!dim][d], !dim);
do_road_align(segs, src.d[!dim][d], c.d[!dim][d], !dim);
do_road_align(plots, src.d[!dim][d], c.d[!dim][d], !dim);
for (unsigned i = 0; i < 3; ++i) {do_road_align(isecs[i], src.d[!dim][d], c.d[!dim][d], !dim);}
}
return 1;
}
void make_4way_int(unsigned int3_ix, bool dim, bool dir, unsigned conn_to_city, int road_ix) { // turn a 3-way intersection into a 4-way intersection for a connector road
assert(int3_ix < isecs[1].size());
road_isec_t &isec(isecs[1][int3_ix]); // bbox doesn't change, only conn changes
isec.make_4way(conn_to_city); // all connected
isec.rix_xy[2*dim + dir] = road_ix; // Note: should be negative (connector road)
isecs[2].push_back(isec); // add as 4-way intersection
isecs[1][int3_ix] = isecs[1].back(); // remove original 3-way intersection
isecs[1].pop_back();
}
struct road_ixs_t {
vector<unsigned> seg_ixs, isec_ixs[3][2]; // {2-way, 3-way, 4-way} x {X, Y}
};
template<typename T> int search_for_adj(vector<T> const &v, vector<unsigned> const &ixs, cube_t const &bcube, bool dim, bool dir) const {
for (auto i = ixs.begin(); i != ixs.end(); ++i) {
assert(*i < v.size());
cube_t const &c(v[*i]);
if (c.d[dim][!dir] != bcube.d[dim][dir]) continue; // no shared edge
if (c.d[!dim][0] != bcube.d[!dim][0] || c.d[!dim][1] != bcube.d[!dim][1]) continue; // no shared edge in other dim
return *i; // there can be only one
} // for i
return -1; // not found
}
vector<unsigned> const &get_segs_connecting_to_city(unsigned city) const {
assert(city < city_to_seg.size());
return city_to_seg[city];
}
public:
void calc_ix_values(vector<road_network_t> const &road_networks, road_network_t const &global_rn) {
// now that the segments and intersections are in order, we can fill in the IDs; first, create a mapping from road to segments and intersections
bool const is_global_rn(&global_rn == this);
assert(road_to_city.size() == (is_global_rn ? roads.size() : 0));
vector<road_ixs_t> by_ix(roads.size()); // maps road_ix to list of seg_ix values
vector<unsigned> all_ixs;
if (is_global_rn) {
unsigned num_cities(0);
for (auto r = road_to_city.begin(); r != road_to_city.end(); ++r) {
for (unsigned d = 0; d < 2; ++d) {if (r->id[d] != CONN_CITY_IX) {max_eq(num_cities, r->id[d]+1);}}
}
city_to_seg.resize(num_cities);
}
for (unsigned i = 0; i < segs.size(); ++i) {
unsigned const ix(segs[i].road_ix);
assert(ix < by_ix.size());
assert(roads[ix].dim == segs[i].dim);
by_ix[ix].seg_ixs.push_back(i);
}
for (unsigned n = 0; n < 3; ++n) { // {2-way, 3-way, 4-way}
for (unsigned i = 0; i < isecs[n].size(); ++i) {
road_isec_t const &isec(isecs[n][i]);
for (unsigned d = 0; d < 2; ++d) { // {x, y}
for (unsigned e = 0; e < 2; ++e) {
int ix(isec.rix_xy[2*d + e]);
if (ix < 0) { // global connector road
ix = decode_neg_ix(ix);
assert((unsigned)ix < global_rn.roads.size()); // connector road, nothing else to do here?
}
else if (e == 1 && ix == isec.rix_xy[2*d]) {} // same local road in both dirs, skip
else {
assert((unsigned)ix < roads.size());
assert(roads[ix].dim == (d != 0));
by_ix[ix].isec_ixs[n][d].push_back(i);
}
} // for e
} // for d
if (isec.conn_to_city >= 0) {cix_to_isec[isec.conn_to_city] = &isec;}
} // for i
} // for n
// next, connect segments and intersections together by index, using roads as an acceleration structure
for (unsigned i = 0; i < segs.size(); ++i) {
road_seg_t &seg(segs[i]);
road_ixs_t const &rix(by_ix[seg.road_ix]);
for (unsigned dir = 0; dir < 2; ++dir) { // dir
bool found(0);
int const seg_ix(search_for_adj(segs, rix.seg_ixs, seg, seg.dim, (dir != 0)));
if (seg_ix >= 0) {assert(seg_ix != (int)i); seg.conn_ix[dir] = seg_ix; seg.conn_type[dir] = TYPE_RSEG; found = 1;} // found segment
for (unsigned n = 0; n < 3; ++n) { // 2-way, 3-way, 4-way
int const isec_ix(search_for_adj(isecs[n], rix.isec_ixs[n][seg.dim], seg, seg.dim, (dir != 0)));
if (isec_ix >= 0) {assert(!found); seg.conn_ix[dir] = isec_ix; seg.conn_type[dir] = (TYPE_ISEC2 + n); found = 1;} // found intersection
}
if (is_global_rn && !found) { // connection to a city
assert(seg.road_ix < road_to_city.size());
unsigned const city(road_to_city[seg.road_ix].id[dir]);
assert(city != CONN_CITY_IX); // internal segments should be connected and not get here
assert(city < road_networks.size());
assert(city < city_to_seg.size());
city_to_seg[city].push_back(i); // add segment ID
road_network_t const &rn(road_networks[city]);
bool found2(0);
for (unsigned n = 1; n < 3; ++n) { // search 3-way and 4-way intersections
all_ixs.resize(rn.isecs[n].size());
for (unsigned m = 0; m < all_ixs.size(); ++m) {all_ixs[m] = m;} // all sequential index values
int const isec_ix(rn.search_for_adj(rn.isecs[n], all_ixs, seg, seg.dim, (dir != 0)));
if (isec_ix < 0) continue; // not be found
seg.conn_ix [dir] = isec_ix;
seg.conn_type[dir] = TYPE_ISEC2 + n; // always connects to a 3-way or 4-way intersection within the city
found2 = 1;
break;
} // for n
assert(found2); // must be found
continue;
}
assert(found);
} // for dir
} // for i
for (unsigned n = 0; n < 3; ++n) { // 2-way, 3-way, 4-way
for (unsigned i = 0; i < isecs[n].size(); ++i) {
road_isec_t &isec(isecs[n][i]);
for (unsigned d = 0; d < 4; ++d) { // {-x, +x, -y, +y}
if (!(isec.conn & (1<<d))) continue; // no connection in this position
unsigned const dim(d>>1), dir(d&1);
int const ix(isec.rix_xy[d]);
if (ix < 0) { // global connector road
vector<unsigned> const &seg_ids(global_rn.get_segs_connecting_to_city(city_id));
assert(!seg_ids.empty());
int const seg_ix(global_rn.search_for_adj(global_rn.segs, seg_ids, isec, (dim != 0), (dir != 0))); // global conn segment
assert(seg_ix >= 0); // must be found
if (seg_ix >= 0) {isec.conn_ix[d] = encode_neg_ix(seg_ix);}
}
else { // local segment
int const seg_ix(search_for_adj(segs, by_ix[ix].seg_ixs, isec, (dim != 0), (dir != 0))); // always connects to a road segment
assert(seg_ix >= 0); // must be found
if (seg_ix >= 0) {isec.conn_ix[d] = seg_ix;}
}
} // for d
} // for i
} // for n
for (auto r = roads.begin(); r != roads.end(); ++r) {tot_road_len += r->get_length();} // calculate tot_road_len
}
bool check_valid_conn_intersection(cube_t const &c, bool dim, bool dir, bool is_4_way) const {
return (is_4_way ? (find_3way_int_at(c, dim, dir) >= 0) : (find_conn_int_seg(c, dim, dir) >= 0));
}
void insert_conn_intersection(cube_t const &c, bool dim, bool dir, unsigned grn_rix, unsigned dest_city_id, bool is_4_way) { // Note: dim is the dimension of the connector road
assert(dest_city_id != city_id); // not connected to self
if (is_4_way) {
int const int3_ix(find_3way_int_at(c, dim, dir));
assert(int3_ix >= 0); // must be found
align_isec3_to(int3_ix, c, dim);
make_4way_int(int3_ix, dim, dir, dest_city_id, encode_neg_ix(grn_rix));
}
else {
int const seg_id(find_conn_int_seg(c, dim, dir));
assert(seg_id >= 0 && (unsigned)seg_id < segs.size());
segs.push_back(segs[seg_id]); // clone the segment first
road_seg_t &seg(segs[seg_id]);
assert(seg.road_ix < roads.size() && roads[seg.road_ix].dim != dim); // sanity check
seg .d[!dim][1] = c.d[!dim][0]; // low part
segs.back().d[!dim][0] = c.d[!dim][1]; // high part
cube_t ibc(seg); // intersection bcube
ibc.d[!dim][0] = c.d[!dim][0]; // copy width from c
ibc.d[!dim][1] = c.d[!dim][1];
uint8_t const conns[4] = {7, 11, 13, 14};
int const other_rix(encode_neg_ix(grn_rix)); // make negative
isecs[1].emplace_back(ibc, (dim ? seg.road_ix : (int)other_rix), (dim ? other_rix : (int)seg.road_ix), conns[2*(!dim) + dir], true, dest_city_id); // 3-way
}
}
float create_connector_road(cube_t const &bcube1, cube_t const &bcube2, vector<cube_t> &blockers, road_network_t *rn1, road_network_t *rn2, unsigned city1, unsigned city2,
unsigned dest_city_id1, unsigned dest_city_id2, heightmap_query_t &hq, float road_width, float conn_pos, bool dim, bool check_only, bool is_4_way1, bool is_4_way2)
{
bool const dir(bcube1.d[dim][0] < bcube2.d[dim][0]);
if (dir == 0) {swap(city1, city2);} // make {lo, hi}
point p1, p2;
p1.z = bcube1.d[2][1];
p2.z = bcube2.d[2][1];
p1[!dim] = p2[!dim] = conn_pos;
p1[ dim] = bcube1.d[dim][ dir];
p2[ dim] = bcube2.d[dim][!dir];
bool const slope((p1.z < p2.z) ^ dir);
road_t const road(p1, p2, road_width, dim, slope, roads.size());
float const road_len(road.get_length()), delta_z(road.get_dz()), max_slope(city_params.max_road_slope);
assert(road_len > 0.0 && delta_z >= 0.0);
if (delta_z/road_len > max_slope) {assert(check_only); return -1.0;} // slope is too high (split segments will have even higher slopes)
unsigned const x1(hq.get_x_pos(road.x1())), y1(hq.get_y_pos(road.y1())), x2(hq.get_x_pos(road.x2())), y2(hq.get_y_pos(road.y2()));
if (check_only) { // only need to do these checks in this case
if (rn1 && !rn1->check_valid_conn_intersection(road, dim, dir, is_4_way1)) return -1.0; // invalid, don't make any changes
if (rn2 && !rn2->check_valid_conn_intersection(road, dim, !dir, is_4_way2)) return -1.0;
for (auto b = blockers.begin(); b != blockers.end(); ++b) {
if ((rn1 && b->contains_cube(bcube1)) || (rn2 && b->contains_cube(bcube2))) continue; // skip current cities
// create an intersection if blocker is a road, and happens to be the same elevation?
if (b->intersects_xy(road)) return -1.0; // bad intersection, fail
}
if (hq.any_underwater(x1, y1, x2+1, y2+1)) return -1.0; // underwater (Note: bounds check is done here)
}
if (!check_only) { // create intersections and add blocker
unsigned const grn_rix(roads.size()); // may be wrong end of connector, but doesn't matter?
if (rn1) {rn1->insert_conn_intersection(road, dim, dir, grn_rix, dest_city_id2, is_4_way1);}
if (rn2) {rn2->insert_conn_intersection(road, dim, !dir, grn_rix, dest_city_id1, is_4_way2);}
float const blocker_padding(max(city_params.road_spacing, 2.0f*city_params.road_border*max(DX_VAL, DY_VAL)));
blockers.push_back(road);
blockers.back().expand_by(blocker_padding); // add extra padding
}
if (road_len <= city_params.conn_road_seg_len) { // simple single road segment case
if (!check_only) {
roads.push_back(road);
road_to_city.emplace_back(city1, city2);
} // Note: no bridges here, but could add them
return hq.flatten_sloped_region(x1, y1, x2, y2, road.d[2][slope]-ROAD_HEIGHT, road.d[2][!slope]-ROAD_HEIGHT, dim, city_params.road_border, 0, 0, check_only);
}
unsigned const num_segs(ceil(road_len/city_params.conn_road_seg_len));
assert(num_segs > 0 && num_segs < 1000); // sanity check
float const seg_len(road_len/num_segs);
assert(seg_len <= city_params.conn_road_seg_len);
road_t rs(road); // keep d[!dim][0], d[!dim][1], dim, and road_ix
rs.z1() = road.d[2][slope];
segments.clear();
float tot_dz(0.0);
bool last_was_bridge(0), last_was_tunnel(0);
vector<flatten_op_t> replay_fops;
for (unsigned n = 0; n < num_segs; ++n) {
rs.d[dim][1] = ((n+1 == num_segs) ? road.d[dim][1] : (rs.d[dim][0] + seg_len)); // make sure it ends exactly at the correct location
point pos;
pos[ dim] = rs.d[dim][1];
pos[!dim] = conn_pos;
rs.z2() = hq.get_height_at(pos.x, pos.y) + ROAD_HEIGHT; // terrain height at end of segment
rs.slope = (rs.z2() < rs.z1());
if (fabs(rs.get_slope_val()) > max_slope) { // slope is too high, clamp z2 to max allowed value
if (n+1 == num_segs) {
// Note: the height of the first/last segment may change slightly after placing the bend,
// which can make this slope check fail when check_only=0 while it passed when check_only=1;
// returning here will create a disconnected road segment and fail an assert later, so instead we allow the high slope
if (check_only) return -1.0;
}
else {rs.z2() = rs.z1() + seg_len*max_slope*SIGN(rs.get_dz());}
}
segments.push_back(rs);
rs.d[dim][0] = rs.d[dim][1]; rs.z1() = rs.z2(); // shift segment end point
} // for n
for (auto s = segments.begin(); s != segments.end(); ++s) {
if (s->z2() < s->z1()) {swap(s->z2(), s->z1());} // swap zvals if needed
assert(s->is_normalized());
bridge_t bridge(*s);
tunnel_t tunnel(*s);
tot_dz += hq.flatten_for_road(*s, city_params.road_border, check_only, 0, (last_was_bridge ? nullptr : &bridge), (last_was_tunnel ? nullptr : &tunnel));
replay_fops.push_back(hq.last_flatten_op);
if (!check_only) {
roads.push_back(*s);
road_to_city.emplace_back(city1, city2); // Note: city index is specified even for internal (non-terminal) roads
if (bridge.make_bridge) {bridges.push_back(bridge);}
if (tunnel.enabled()) {tunnels.push_back(tunnel);}
}
last_was_bridge = bridge.make_bridge; // Note: conservative; used to prevent two consecutive bridges with no (or not enough) mesh in between
last_was_tunnel = tunnel.enabled(); // same thing for tunnels
} // for s
if (!check_only) { // post-flatten pass to fix up dirt at road joints - doesn't help much
for (auto f = replay_fops.begin(); f != replay_fops.end(); ++f) { // replay the same series of operations; Note that bridge and tunnel segments have been cached
hq.flatten_sloped_region(f->x1, f->y1, f->x2, f->y2, f->z1, f->z2, f->dim, f->border, f->skip_six, f->skip_eix, 0, 1);
}
}
return tot_dz; // success
}
void create_connector_bend(cube_t const &int_bcube, bool dx, bool dy, unsigned road_ix_x, unsigned road_ix_y) {
uint8_t const conns[4] = {6, 5, 10, 9};
isecs[0].emplace_back(int_bcube, road_ix_x, road_ix_y, conns[2*dy + dx], true);
//blockers.push_back(int_bcube); // ???
}
void split_connector_roads(float road_spacing) {
// Note: here we use segs, maybe 2-way isecs for bends, but not plots
for (auto r = roads.begin(); r != roads.end(); ++r) {
bool const d(r->dim), slope(r->slope);
float const len(r->get_length());
unsigned const rix(r->road_ix); // not (r - roads.begin())
if (len <= road_spacing) {segs.emplace_back(*r, rix); continue;} // single segment road
assert(len > 0.0);
unsigned const num_segs(ceil(len/road_spacing));
float const seg_len(len/num_segs), z1(r->d[2][slope]), z2(r->d[2][!slope]); // use fixed-length segments
assert(seg_len <= road_spacing);
cube_t c(*r); // start by copying the road's bcube
for (unsigned n = 0; n < num_segs; ++n) {
c.d[d][1] = ((n+1 == num_segs) ? r->d[d][1] : (c.d[d][0] + seg_len)); // make sure it ends exactly at the correct location
for (unsigned e = 0; e < 2; ++e) {c.d[2][e] = z1 + (z2 - z1)*((c.d[d][e] - r->d[d][0])/len);} // interpolate road height across segments
if (c.z2() < c.z1()) {swap(c.z2(), c.z1());} // swap zvals if needed
assert(c.is_normalized());
segs.emplace_back(c, rix, d, r->slope);
c.d[d][0] = c.d[d][1]; // shift segment end point
} // for n
} // for r
}
void finalize_bridges_and_tunnels() {
for (auto b = bridges.begin(); b != bridges.end(); ++b) {b->add_streetlights();}
for (auto b = tunnels.begin(); b != tunnels.end(); ++b) {b->add_streetlights();}
}
void gen_tile_blocks() {
tile_blocks.clear(); // should already be empty?
tile_to_block_map.clear();
add_tile_blocks(segs, tile_to_block_map, TYPE_RSEG);
add_tile_blocks(plots, tile_to_block_map, TYPE_PLOT);
add_tile_blocks(track_segs, tile_to_block_map, TYPE_TRACKS);
for (unsigned i = 0; i < 3; ++i) {add_tile_blocks(isecs[i], tile_to_block_map, (TYPE_ISEC2 + i));}
//cout << "tile_to_block_map: " << tile_to_block_map.size() << ", tile_blocks: " << tile_blocks.size() << endl;
}
void gen_parking_lots_and_place_objects(vector<car_t> &cars, bool have_cars) {
city_obj_placer.gen_parking_and_place_objects(plots, cars, city_id, have_cars);
add_tile_blocks(city_obj_placer.parking_lots, tile_to_block_map, TYPE_PARK_LOT); // need to do this later, after gen_tile_blocks()
tile_to_block_map.clear(); // no longer needed
}
void add_streetlights() {
streetlights.clear();
streetlights.reserve(4*plots.size()); // one on each side of each plot
float const b(-0.015), a(1.0 - b); // spacing from light pos to plot edge
for (auto i = plots.begin(); i != plots.end(); ++i) {
streetlights.emplace_back(point((a*i->x1() + b*i->x2()), (0.75*i->y1() + 0.25*i->y2()), i->z2()), -plus_x); // left edge one quarter up
streetlights.emplace_back(point((a*i->x2() + b*i->x1()), (0.25*i->y1() + 0.75*i->y2()), i->z2()), plus_x); // right edge three quarters up
streetlights.emplace_back(point((0.25*i->x1() + 0.75*i->x2()), (a*i->y1() + b*i->y2()), i->z2()), -plus_y); // bottom edge three quarters right
streetlights.emplace_back(point((0.75*i->x1() + 0.25*i->x2()), (a*i->y2() + b*i->y1()), i->z2()), plus_y); // top edge one quarter right
}
}
void get_road_bcubes(vector<cube_t> &bcubes) const {
get_all_bcubes(roads, bcubes);
get_all_bcubes(tracks, bcubes);
}
void get_plot_bcubes(vector<cube_t> &bcubes) const { // Note: z-values of cubes indicate building height ranges
if (plots.empty()) return; // connector road city
unsigned const start(bcubes.size());
get_all_bcubes(plots, bcubes);
vector3d const city_radius(0.5*bcube.get_size());
point const city_center(bcube.get_cube_center());
for (auto i = bcubes.begin()+start; i != bcubes.end(); ++i) { // set zvals to control building height range, higher in city center
point const center(i->get_cube_center());
float const dx(fabs(center.x - city_center.x)/city_radius.x); // 0 at city center, 1 at city perimeter
float const dy(fabs(center.y - city_center.y)/city_radius.y); // 0 at city center, 1 at city perimeter
float const hval(1.0 - max(dx*dx, dy*dy)); // square to give higher weight to larger height ranges
i->z1() = max(0.0, (hval - 0.25)); // bottom of height range
i->z2() = min(1.0, (hval + 0.25)); // bottom of height range
} // for i
}
bool check_road_sphere_coll(point const &pos, float radius, bool include_intersections, bool xy_only, bool exclude_bridges_and_tunnels) const {
if (roads.empty()) return 0;
point const query_pos(pos - get_camera_coord_space_xlate());
if (!check_bcube_sphere_coll(bcube, query_pos, radius, xy_only)) return 0;
if (check_bcubes_sphere_coll(roads, query_pos, radius, xy_only)) { // collision with a road
if (!exclude_bridges_and_tunnels ||
!(check_bcubes_sphere_coll(bridges, query_pos, radius, xy_only) ||
check_bcubes_sphere_coll(tunnels, query_pos, radius, xy_only))) return 1; // ignore collisions with bridges and tunnels
}
if (include_intersections) { // used for global road network
for (unsigned i = 0; i < 3; ++i) { // {2-way, 3-way, 4-way}
if (check_bcubes_sphere_coll(isecs[i], query_pos, radius, xy_only)) return 1;
}
}
if (check_bcubes_sphere_coll(tracks, query_pos, radius, xy_only)) return 1; // collision with a track
return 0;
}
bool proc_sphere_coll(point &pos, point const &p_last, float radius, float prev_frame_zval, vector3d *cnorm) const {
vector3d const xlate(get_camera_coord_space_xlate());
float const dist(p2p_dist(pos, p_last));
if (!sphere_cube_intersect_xy(pos, (radius + dist), (bcube + xlate))) return 0;
if (!plots.empty()) {max_eq(pos.z, (bcube.z1() + radius));} // make sure the sphere is above the city road/plot surface
for (unsigned n = 1; n < 3; ++n) { // intersections with stoplights (3-way, 4-way)
for (auto i = isecs[n].begin(); i != isecs[n].end(); ++i) {
if (i->proc_sphere_coll(pos, p_last, radius, xlate, dist, cnorm)) return 1;
}
}
for (auto i = bridges.begin(); i != bridges.end(); ++i) {
if (i->proc_sphere_coll(pos, p_last, radius, prev_frame_zval, xlate, cnorm)) return 1;
}
for (auto i = tunnels.begin(); i != tunnels.end(); ++i) {
if (i->proc_sphere_coll(pos, p_last, radius, prev_frame_zval, xlate, cnorm)) return 1;
}
if ((pos.z - xlate.z - radius < bcube.z2()) + (streetlight_ns::get_streetlight_height())) { // below the level of the streetlights
if (proc_streetlight_sphere_coll(pos, radius, xlate, cnorm)) return 1;
}
if (city_obj_placer.proc_sphere_coll(pos, p_last, radius, cnorm)) return 1;
return 0;
}
bool line_intersect(point const &p1, point const &p2, float &t) const { // Note: xlate has already been applied
cube_t c(bcube); // deep copy
c.z2() += stoplight_ns::stoplight_max_height();
if (!c.line_intersects(p1, p2)) return 0;
bool ret(0);
for (unsigned n = 1; n < 3; ++n) { // intersections with stoplights (3-way, 4-way)
for (auto i = isecs[n].begin(); i != isecs[n].end(); ++i) {ret |= i->line_intersect(p1, p2, t);}
}
for (auto i = bridges.begin(); i != bridges.end(); ++i) {ret |= i->line_intersect(p1, p2, t);}
for (auto i = tunnels.begin(); i != tunnels.end(); ++i) {ret |= i->line_intersect(p1, p2, t);}
ret |= line_intersect_streetlights(p1, p2, t);
ret |= city_obj_placer.line_intersect(p1, p2, t);
return ret;
}
bool check_mesh_disable(point const &pos, float radius) const {
if (tunnels.empty()) return 0;
point const query_pos(pos - get_camera_coord_space_xlate());
cube_t query_region; query_region.set_from_sphere(query_pos, radius); // actually a cube, not a sphere
for (auto i = tunnels.begin(); i != tunnels.end(); ++i) {
if (i->check_mesh_disable(query_region)) return 1;
}
return 0;
}
bool tile_contains_tunnel(cube_t const &bcube) const {
for (auto i = tunnels.begin(); i != tunnels.end(); ++i) {
if (i->intersects_xy(bcube)) return 1;
}
return 0;
}
bool point_in_tunnel(point const &pos) const {
for (auto i = tunnels.begin(); i != tunnels.end(); ++i) {
if (i->contains_pt(pos)) return 1; // Note: checks z-val
}
return 0;
}
int get_color_at_xy(point const &pos, colorRGBA &color) const { // Note: query results are mutually exclusive since there's no overlap, so can early terminate on true
if (!bcube.contains_pt_xy(pos)) return 0;
for (auto i = bridges.begin(); i != bridges.end(); ++i) {
if (i->contains_pt_xy_exp(pos, 1.0*city_params.road_width)) {color = WHITE; return INT_ROAD;}
}
for (auto i = tunnels.begin(); i != tunnels.end(); ++i) {
if (i->contains_pt_xy(pos)) {color = BROWN; return INT_ROAD;}
}
for (auto i = roads.begin(); i != roads.end(); ++i) { // use an acceleration structure?
if (i->contains_pt_xy(pos)) {color = GRAY; return INT_ROAD;}
}
for (auto i = tracks.begin(); i != tracks.end(); ++i) {
if (i->contains_pt_xy(pos)) {color = LT_BROWN; return INT_ROAD;} // counts as road intersection (for now)
}
if (plots.empty()) { // connector road
for (auto i = isecs[0].begin(); i != isecs[0].end(); ++i) { // 2-way intersections
if (i->contains_pt_xy(pos)) {color = GRAY; return INT_ROAD;}
}
}
if (city_obj_placer.pt_in_parking_lot_xy(pos)) {color = DK_GRAY; return INT_PARKING;}
if (city_obj_placer.get_color_at_xy(pos, color)) {return INT_PLOT;} // hit a detail object, but still in a plot
if (!plots.empty()) {color = colorRGBA(0.65, 0.65, 0.65, 1.0); return INT_PLOT;} // inside a city and not over a road - must be over a plot
return INT_NONE;
}
bool cube_overlaps_road_xy(cube_t const &c) const {
for (auto i = roads.begin(); i != roads.end(); ++i) {if (i->intersects(c)) return 1;}
return 0;
}
bool cube_overlaps_parking_lot_xy(cube_t const &c) const {return city_obj_placer.cube_overlaps_parking_lot_xy(c);}
void draw(road_draw_state_t &dstate, bool shadow_only, bool is_connector_road) {
if (empty()) return;
if (!dstate.check_cube_visible(bcube, 1.0, shadow_only)) return; // VFC/too far
if (shadow_only) {
if (!is_connector_road) { // connector road has no stoplights to cast shadows
for (auto b = tile_blocks.begin(); b != tile_blocks.end(); ++b) {
if (!dstate.check_cube_visible(b->bcube, 1.0, shadow_only)) continue; // VFC/too far
for (unsigned i = 1; i < 3; ++i) {dstate.draw_stoplights(isecs[i], 1);} // intersections with stoplights (3-way, 4-way)
}
}
}
else {
for (auto b = tile_blocks.begin(); b != tile_blocks.end(); ++b) {
if (!dstate.check_cube_visible(b->bcube)) continue; // VFC/too far
dstate.begin_tile(b->bcube.get_cube_center());
dstate.draw_road_region(segs, b->ranges[TYPE_RSEG ], b->quads[TYPE_RSEG ], TYPE_RSEG ); // road segments
dstate.draw_road_region(plots, b->ranges[TYPE_PLOT ], b->quads[TYPE_PLOT ], TYPE_PLOT ); // plots
dstate.draw_road_region(track_segs, b->ranges[TYPE_TRACKS], b->quads[TYPE_TRACKS], TYPE_TRACKS); // railroad tracks
dstate.draw_road_region(city_obj_placer.parking_lots, b->ranges[TYPE_PARK_LOT], b->quads[TYPE_PARK_LOT], TYPE_PARK_LOT); // parking lots
bool const draw_stoplights(dstate.check_cube_visible(b->bcube, 0.16)); // use smaller dist_scale
for (unsigned i = 0; i < 3; ++i) { // intersections (2-way, 3-way, 4-way)
dstate.draw_road_region(isecs[i], b->ranges[TYPE_ISEC2 + i], b->quads[TYPE_ISEC2 + i], (TYPE_ISEC2 + i));
if (draw_stoplights && i > 0) {dstate.draw_stoplights(isecs[i], 0);}
}
} // for b
}
draw_streetlights(dstate.s, dstate.xlate, shadow_only, 0);
// draw bridges and tunnels; only in connector road network; bridgesand tunnels are sparse/uncommon, so don't need to be batched by blocks
for (auto b = bridges.begin(); b != bridges.end(); ++b) {
dstate.draw_bridge(*b, shadow_only);
b->draw_streetlights(dstate.s, dstate.xlate, shadow_only, 0);
}
for (auto t = tunnels.begin(); t != tunnels.end(); ++t) {
dstate.draw_tunnel(*t, shadow_only);
t->draw_streetlights(dstate.s, dstate.xlate, shadow_only, 1); // always_on=1
}
city_obj_placer.draw_detail_objects(dstate, shadow_only);
}
void add_city_lights(vector3d const &xlate, cube_t &lights_bcube) const { // for now, the only light sources added by the road network are city block streetlights
add_streetlight_dlights(xlate, lights_bcube, 0);
for (auto b = bridges.begin(); b != bridges.end(); ++b) {b->add_streetlight_dlights(xlate, lights_bcube, 0);}
for (auto t = tunnels.begin(); t != tunnels.end(); ++t) {t->add_streetlight_dlights(xlate, lights_bcube, 1);} // always_on=1
}
// cars
static float get_car_lane_offset() {return CAR_LANE_OFFSET*city_params.road_width;}
static bool add_car_to_rns(car_t &car, rand_gen_t &rgen, vector<road_network_t> const &road_networks) {
unsigned const city(rgen.rand()%road_networks.size());
car.cur_city = city;
return road_networks[city].add_car(car, rgen);
}
bool add_car(car_t &car, rand_gen_t &rgen) const {
if (segs.empty()) return 0; // no segments to place car on
vector3d const nom_car_size(city_params.get_nom_car_size());
for (unsigned n = 0; n < 10; ++n) { // make 10 tries
unsigned const seg_ix(rgen.rand()%segs.size());
road_seg_t const &seg(segs[seg_ix]); // chose a random segment
car.dim = seg.dim;
car.dir = rgen.rand_bool();
car.max_speed = rgen.rand_uniform(0.66, 1.0); // add some speed variation
car.cur_road = seg.road_ix;
car.cur_seg = seg_ix;
car.cur_road_type = TYPE_RSEG;
vector3d car_sz(nom_car_size); // {length, width, height} // Note: car models should all be the same size
car.height = car_sz.z;
point pos;
float val1(seg.d[seg.dim][0] + 0.6*car_sz.x), val2(seg.d[seg.dim][1] - 0.6*car_sz.x);
if (val1 >= val2) continue; // failed, try again (connector road junction?)
pos[!seg.dim] = 0.5*(seg.d[!seg.dim][0] + seg.d[!seg.dim][1]); // center of road
pos[!seg.dim] += ((car.dir ^ car.dim) ? -1.0 : 1.0)*get_car_lane_offset(); // place in right lane
pos[ seg.dim] = rgen.rand_uniform(val1, val2); // place at random pos in segment
pos.z = seg.z2() + 0.5*car_sz.z; // place above road surface
if (seg.dim) {swap(car_sz.x, car_sz.y);}
car.bcube.set_from_point(pos);
car.bcube.expand_by(0.5*car_sz);
assert(get_road_bcube_for_car(car).contains_cube_xy(car.bcube)); // sanity check
return 1; // success
} // for n
return 0; // failed
}
void find_car_next_seg(car_t &car, vector<road_network_t> const &road_networks, road_network_t const &global_rn) const {
if (car.cur_road_type == TYPE_RSEG) {
road_seg_t const &seg(get_car_seg(car));
car.cur_road_type = seg.conn_type[car.dir];