Update; refactor of initial code
This commit is contained in:
Submodule modules/voxel updated: fcca722d52...6cd143d392
@@ -18,6 +18,7 @@ Characters_::Characters_() : query(memnew(DetourNavigationQuery)),
|
||||
crowd(NULL)
|
||||
{
|
||||
smm = NULL;
|
||||
no_navmesh = true;
|
||||
}
|
||||
AnimationTree *Characters_::get_animation_tree(const Node *npc) const
|
||||
{
|
||||
@@ -173,6 +174,8 @@ void Characters_::rotate_to_agent(Spatial *obj)
|
||||
}
|
||||
void Characters_::speed_to_agent(Spatial *obj)
|
||||
{
|
||||
if (!crowd)
|
||||
return;
|
||||
float delta = get_physics_process_delta_time();
|
||||
float cur_speed = get_walk_speed(obj);
|
||||
float new_speed;
|
||||
@@ -269,6 +272,8 @@ void Characters_::speed_to_agent(Spatial *obj)
|
||||
}
|
||||
bool Characters_::has_arrived(Object *obj)
|
||||
{
|
||||
if (!crowd)
|
||||
return false;
|
||||
Spatial *sp = Object::cast_to<Spatial>(obj);
|
||||
if (!obj->has_meta("agent_id"))
|
||||
return false;
|
||||
@@ -287,6 +292,8 @@ bool Characters_::has_arrived(Object *obj)
|
||||
}
|
||||
void Characters_::update_arrived(Object *obj)
|
||||
{
|
||||
if (!crowd)
|
||||
return;
|
||||
Spatial *sp = Object::cast_to<Spatial>(obj);
|
||||
int agent_id = obj->get_meta("agent_id");
|
||||
if (obj->has_meta("climb"))
|
||||
@@ -331,6 +338,7 @@ void Characters_::character_physics(Object *obj)
|
||||
|
||||
orientation = obj->get_meta("orientation");
|
||||
root_motion = animtree->get_root_motion_transform();
|
||||
root_motion.origin = root_motion_mod.xform(root_motion.origin);
|
||||
orientation *= root_motion;
|
||||
h_velocity = orientation.origin / delta;
|
||||
velocity = h_velocity;
|
||||
@@ -350,7 +358,7 @@ void Characters_::character_physics(Object *obj)
|
||||
velocity = kb->move_and_slide(velocity, Vector3(0.0f, 1.0f, 0.0f), true, 4, 0.785f, false);
|
||||
}
|
||||
orientation.origin = Vector3();
|
||||
orientation = orientation.orthonormalized();
|
||||
orientation.orthonormalize();
|
||||
obj->set_meta("orientation", orientation);
|
||||
Spatial *sp = Object::cast_to<Spatial>(obj);
|
||||
if (sp) {
|
||||
@@ -513,7 +521,8 @@ void Characters_::walkto_agent_node(Node *ch, const Node *target)
|
||||
}
|
||||
void Characters_::walkto_agent(Node *ch, const Vector3 &target)
|
||||
{
|
||||
assert(crowd);
|
||||
if (!crowd)
|
||||
return;
|
||||
if (ch->has_meta("_target")) {
|
||||
Vector3 otarget = ch->get_meta("_target");
|
||||
if (otarget == target)
|
||||
@@ -542,7 +551,8 @@ void Characters_::_notification(int p_what)
|
||||
debug->set_color(Color(1, 0, 0, 1));
|
||||
}
|
||||
for (e = char_node_list.front(); e; e = e->next()) {
|
||||
debug->set_color(Color(1, 0, 0, 1));
|
||||
if (debug)
|
||||
debug->set_color(Color(1, 0, 0, 1));
|
||||
Node *ch = e->get();
|
||||
if (!ch->has_meta("animation_tree"))
|
||||
continue;
|
||||
@@ -595,9 +605,10 @@ void Characters_::_notification(int p_what)
|
||||
continue;
|
||||
if (!ch->has_meta("orientation"))
|
||||
continue;
|
||||
printf("running physics for %p\n", ch);
|
||||
character_physics(ch);
|
||||
Spatial *sp = Object::cast_to<Spatial>(ch);
|
||||
Vector3 direction = -sp->get_global_transform().basis[2];
|
||||
Vector3 direction = sp->get_global_transform().xform(Vector3(0, 0, -1));
|
||||
ch->set_meta("direction", direction);
|
||||
}
|
||||
#if 0
|
||||
@@ -630,9 +641,20 @@ void Characters_::_bind_methods()
|
||||
ClassDB::bind_method(D_METHOD("walkto_agent", "ch", "target"), &Characters_::walkto_agent);
|
||||
ClassDB::bind_method(D_METHOD("walkto_agent_node", "ch", "target"), &Characters_::walkto_agent_node);
|
||||
ClassDB::bind_method(D_METHOD("character_physics", "obj"), &Characters_::character_physics);
|
||||
ClassDB::bind_method(D_METHOD("set_root_motion_mod", "xform"), &Characters_::set_root_motion_mod);
|
||||
ClassDB::bind_method(D_METHOD("get_root_motion_mod"), &Characters_::get_root_motion_mod);
|
||||
ADD_SIGNAL(MethodInfo("arrived", PropertyInfo(Variant::OBJECT, "obj"), PropertyInfo(Variant::VECTOR3, "where")));
|
||||
}
|
||||
|
||||
void Characters_::set_root_motion_mod(const Transform &xform)
|
||||
{
|
||||
root_motion_mod = xform;
|
||||
}
|
||||
Transform Characters_::get_root_motion_mod() const
|
||||
{
|
||||
return root_motion_mod;
|
||||
}
|
||||
|
||||
void Characters_::process_frozen_character(Node *npc, const Vector3 &tposition)
|
||||
{
|
||||
float delta = npc->get_process_delta_time();
|
||||
@@ -691,11 +713,14 @@ void Characters_::process_character(Node *node, bool frozen)
|
||||
if (!paths.has(id)) {
|
||||
Vector<Vector3> points;
|
||||
Vector<int> flags;
|
||||
Vector3 start = query->nearest_point(position, Vector3(1, 1, 1), filter);
|
||||
Vector3 end = query->nearest_point(target, Vector3(1, 1, 1), filter);
|
||||
query->find_path_array(start, end, Vector3(1, 1, 1), filter, points, flags);
|
||||
assert(points.size() > 0);
|
||||
paths[id] = points;
|
||||
if (!no_navmesh) {
|
||||
Vector3 start = query->nearest_point(position, Vector3(1, 1, 1), filter);
|
||||
Vector3 end = query->nearest_point(target, Vector3(1, 1, 1), filter);
|
||||
query->find_path_array(start, end, Vector3(1, 1, 1), filter, points, flags);
|
||||
assert(points.size() > 0);
|
||||
paths[id] = points;
|
||||
} else
|
||||
paths[id] = Vector<Vector3>();
|
||||
}
|
||||
if (debug)
|
||||
for (i = 0; i < paths[id].size() - 1; i++) {
|
||||
@@ -753,10 +778,16 @@ void Characters_::walkto_node(const Node *ch, const Node *target)
|
||||
|
||||
void Characters_::set_navmesh(Ref<DetourNavigationMesh> mesh, const Transform &xform)
|
||||
{
|
||||
if (mesh.is_null()) {
|
||||
no_navmesh = true;
|
||||
initialized = true;
|
||||
return;
|
||||
}
|
||||
query->init(mesh, xform);
|
||||
filter.instance();
|
||||
debug = memnew(ImmediateGeometry);
|
||||
add_child(debug);
|
||||
no_navmesh = false;
|
||||
initialized = true;
|
||||
Ref<SpatialMaterial> mat;
|
||||
mat.instance();
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
Object *get_crowd_();
|
||||
bool has_arrived(Object *obj);
|
||||
void agent_walk_stop(Object *obj);
|
||||
void set_root_motion_mod(const Transform &xform);
|
||||
Transform get_root_motion_mod() const;
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
@@ -56,5 +58,7 @@ protected:
|
||||
ImmediateGeometry *debug;
|
||||
DetourCrowdManager *crowd;
|
||||
float arrive_precision;
|
||||
Transform root_motion_mod;
|
||||
bool no_navmesh;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <core/os/file_access.h>
|
||||
#include <core/io/json.h>
|
||||
#include <core/math/geometry.h>
|
||||
#include <core/resource.h>
|
||||
#include <core/variant_parser.h>
|
||||
#include <scene/2d/canvas_item.h>
|
||||
#include <modules/voronoi/voronoi.h>
|
||||
#include <modules/opensimplex/open_simplex_noise.h>
|
||||
@@ -11,11 +14,6 @@ RoadGrid::RoadGrid()
|
||||
{
|
||||
grid_width = 16;
|
||||
grid_height = 16;
|
||||
class_sizes[SITE_EMPTY] = 10000;
|
||||
class_sizes[SITE_TOWN] = 100000;
|
||||
class_sizes[SITE_FARM] = 500000;
|
||||
class_sizes[SITE_FOREST] = 1000000;
|
||||
class_sizes[SITE_UNASSIGNED] = 2000000;
|
||||
}
|
||||
|
||||
RoadGrid::~RoadGrid()
|
||||
@@ -23,7 +21,7 @@ RoadGrid::~RoadGrid()
|
||||
}
|
||||
|
||||
/* TODO: constants, configuration */
|
||||
Dictionary RoadGrid::build_diagram(int npatches, int center_count, int center_step, int spread, int dim)
|
||||
Dictionary RoadDiagram::build_diagram(Ref<RandomNumberGenerator> rnd, int npatches, int center_count, int center_step, int spread, int dim)
|
||||
{
|
||||
printf("build_diagram %d %d %d %d %d\n", npatches, center_count, center_step, spread, dim);
|
||||
Vector<Vector2i> centers;
|
||||
@@ -72,6 +70,22 @@ Dictionary RoadGrid::build_diagram(int npatches, int center_count, int center_st
|
||||
Dictionary diagram = Voronoi::get_singleton()->generate_diagram(cpoints, 11);
|
||||
return diagram;
|
||||
}
|
||||
const List<struct cluster> &RoadDiagram::get_clusters() const
|
||||
{
|
||||
return clusters;
|
||||
}
|
||||
const Vector<struct map_site> &RoadDiagram::get_map_sites() const
|
||||
{
|
||||
return map_sites;
|
||||
}
|
||||
const Vector<Vector2> &RoadDiagram::get_diagram_vertices() const
|
||||
{
|
||||
return diagram_vertices;
|
||||
}
|
||||
const Vector<struct half_edge *> &RoadDiagram::get_map_hedges() const
|
||||
{
|
||||
return map_hedges;
|
||||
}
|
||||
bool RoadGrid::segment_intersects_rect(const Vector2 &a, const Vector2 &b, const Rect2 &rect)
|
||||
{
|
||||
real_t min = 0, max = 1;
|
||||
@@ -165,7 +179,7 @@ void RoadGrid::draw_debug(Node *drawable, int size_x, int size_y) const
|
||||
}
|
||||
}
|
||||
|
||||
void RoadGrid::index_site(struct map_site *site)
|
||||
void RoadDiagram::index_site(struct map_site *site)
|
||||
{
|
||||
int i;
|
||||
site->vertices_ind.resize(site->vertices.size());
|
||||
@@ -203,14 +217,21 @@ void RoadGrid::index_site(struct map_site *site)
|
||||
he.b = idx2;
|
||||
he.site = site->index;
|
||||
/* use length to decide */
|
||||
he.depth = 6.0f;
|
||||
he.length = diagram_vertices[idx1].distance_to(diagram_vertices[idx2]);
|
||||
if (he.length < 50.0f)
|
||||
he.depth = 3.0f;
|
||||
else if (he.length < 100.0f)
|
||||
he.depth = 6.0f;
|
||||
else if (he.length < 200.0f)
|
||||
he.depth = 12.0f;
|
||||
else
|
||||
he.depth = 24.0f;
|
||||
site->hedges.write[count++] = he;
|
||||
}
|
||||
site->hedges.resize(count);
|
||||
}
|
||||
|
||||
void RoadGrid::process_diagram(const Dictionary &diagram)
|
||||
void RoadDiagram::process_diagram(const Dictionary &diagram)
|
||||
{
|
||||
const Array &sites = diagram["sites"];
|
||||
int i, j;
|
||||
@@ -237,7 +258,7 @@ void RoadGrid::process_diagram(const Dictionary &diagram)
|
||||
site.pos = site_data["pos"];
|
||||
site.polygon = site_data["polygon"];
|
||||
site.vertices = site_data["vertices"];
|
||||
site.site_type = SITE_UNASSIGNED;
|
||||
site.site_type = map_site::SITE_UNASSIGNED;
|
||||
site.cluster = -1;
|
||||
index_site(&site);
|
||||
hedge_count += site.hedges.size();
|
||||
@@ -253,7 +274,6 @@ void RoadGrid::process_diagram(const Dictionary &diagram)
|
||||
/* bad bad constness */
|
||||
struct half_edge *hedge = &map_sites.write[i].hedges.write[j];
|
||||
map_hedges.write[hedge_idx] = hedge;
|
||||
add_hedge_to_grid(hedge);
|
||||
hedge_idx++;
|
||||
}
|
||||
}
|
||||
@@ -261,49 +281,48 @@ void RoadGrid::process_diagram(const Dictionary &diagram)
|
||||
classify_sites();
|
||||
printf("processing done, sites count: %d\n", map_sites.size());
|
||||
}
|
||||
RoadDiagram::RoadDiagram()
|
||||
{
|
||||
class_sizes[map_site::SITE_EMPTY] = 10000;
|
||||
class_sizes[map_site::SITE_TOWN] = 100000;
|
||||
class_sizes[map_site::SITE_FARM] = 500000;
|
||||
class_sizes[map_site::SITE_FOREST] = 1000000;
|
||||
class_sizes[map_site::SITE_UNASSIGNED] = 2000000;
|
||||
}
|
||||
|
||||
void RoadDiagram::build(Ref<RandomNumberGenerator> rnd,
|
||||
int npatches, int center_count, int center_step,
|
||||
int spread, int dim)
|
||||
{
|
||||
Dictionary diagram = build_diagram(rnd, npatches, center_count, center_step,
|
||||
spread, dim);
|
||||
process_diagram(diagram);
|
||||
}
|
||||
void RoadGrid::build(Ref<Curve> curve, Ref<FastNoiseLite> noise)
|
||||
{
|
||||
int i, j;
|
||||
RoadDiagram rd;
|
||||
rnd.instance();
|
||||
rnd->randomize();
|
||||
printf("build_diagram\n");
|
||||
// Dictionary diagram = build_diagram(8, 2 + (rnd->randi() % 2), 100, 100, 50);
|
||||
Dictionary diagram = build_diagram(8, 2, 100, 100, 500);
|
||||
rd.build(rnd, 8, 2, 100, 100, 500);
|
||||
printf("build_diagram done\n");
|
||||
printf("process_diagram\n");
|
||||
process_diagram(diagram);
|
||||
printf("process_diagram done\n");
|
||||
const List<struct cluster> &cl = rd.get_clusters();
|
||||
const List<struct cluster>::Element *e = cl.front();
|
||||
for (e = cl.front(); e; e = e->next())
|
||||
clusters.push_back(e->get());
|
||||
map_sites.append_array(rd.get_map_sites());
|
||||
diagram_vertices.append_array(rd.get_diagram_vertices());
|
||||
map_hedges.append_array(rd.get_map_hedges());
|
||||
for (i = 0; i < map_hedges.size(); i++) {
|
||||
struct half_edge *hedge = map_hedges.write[i];
|
||||
add_hedge_to_grid(hedge);
|
||||
}
|
||||
printf("%d %d\n", curve.is_valid(), noise.is_valid());
|
||||
assert(curve.is_valid() && noise.is_valid());
|
||||
int i, j;
|
||||
if (curve.is_valid() && noise.is_valid()) {
|
||||
printf("building 3rd dimention\n");
|
||||
diagram_vertex_heights.resize(diagram_vertices.size());
|
||||
for (i = 0; i < diagram_vertices.size(); i++) {
|
||||
const Vector2 &v = diagram_vertices[i];
|
||||
float n = noise->get_noise_2d(v.x, v.y);
|
||||
float t = (n + 1.0f) * 0.5f;
|
||||
float d = MAX(1.0f, curve->interpolate_baked(t));
|
||||
d = CLAMP(d, 1.0f, 30.0f);
|
||||
diagram_vertex_heights.write[i] = d;
|
||||
}
|
||||
for (j = 0; j < 3; j++) {
|
||||
for (i = 0; i < map_hedges.size(); i++) {
|
||||
int x1 = map_hedges[i]->a;
|
||||
int x2 = map_hedges[i]->b;
|
||||
float xd = map_hedges[i]->length;
|
||||
float dh = fabsf(diagram_vertex_heights[x2] - diagram_vertex_heights[x1]);
|
||||
if (fabsf(dh / xd) > 0.01f)
|
||||
diagram_vertex_heights.write[x2] = diagram_vertex_heights[x1] + dh / fabsf(dh) * 0.01f * xd;
|
||||
}
|
||||
#if 0
|
||||
for (i = 0; i < diagram_vertices.size(); i++)
|
||||
diagram_vertex_heights.write[i] = Math::stepify(diagram_vertex_heights.write[i], 4.0f);
|
||||
for (i = 0; i < diagram_vertices.size(); i++)
|
||||
diagram_vertex_heights.write[i] = 2.0;
|
||||
#endif
|
||||
}
|
||||
generate_3d_vertices();
|
||||
generate_3d_vertices(curve, noise);
|
||||
for (i = 0; i < map_sites.size(); i++) {
|
||||
float max_height = -10000.0f;
|
||||
float min_height = 10000.0f;
|
||||
@@ -319,6 +338,7 @@ void RoadGrid::build(Ref<Curve> curve, Ref<FastNoiseLite> noise)
|
||||
generate_building_positions();
|
||||
printf("building 3rd dimention done\n");
|
||||
}
|
||||
keep_seed = rnd->get_state();
|
||||
}
|
||||
|
||||
Vector2 RoadGrid::get_influence(int x, int y, float radius) const
|
||||
@@ -345,13 +365,13 @@ Vector2 RoadGrid::get_influence(int x, int y, float radius) const
|
||||
Vector2 seg[] = {a, b};
|
||||
Vector2 pt = Geometry::get_closest_point_to_segment_2d(p, seg);
|
||||
float d = pt.distance_squared_to(p);
|
||||
if (d < radius * radius) {
|
||||
if (d < radius * radius + he->depth * he->depth) {
|
||||
Vector2 ret;
|
||||
ret.x = 1.0f;
|
||||
assert(diagram_vertex_heights.size() > he->a);
|
||||
assert(diagram_vertex_heights.size() > he->b);
|
||||
float h1 = diagram_vertex_heights[he->a];
|
||||
float h2 = diagram_vertex_heights[he->b];
|
||||
assert(vertices.size() > he->a);
|
||||
assert(vertices.size() > he->b);
|
||||
float h1 = vertices[he->a].y;
|
||||
float h2 = vertices[he->b].y;
|
||||
float l = he->length;
|
||||
assert(l > 0.0f);
|
||||
float m1 = pt.distance_to(a) / l;
|
||||
@@ -409,6 +429,8 @@ void RoadGrid::setup_vshapes()
|
||||
v.area.expand_to(vertices[b1] + Vector3(0, 1, 0));
|
||||
v.area.expand_to(vertices[b2] + Vector3(0, -1, 0));
|
||||
v.instance = -1;
|
||||
v.depth1 = map_hedges[v.e1]->depth;
|
||||
v.depth2 = map_hedges[v.e2]->depth;
|
||||
Vector3 p1 = vertices[map_hedges[v.e1]->a];
|
||||
Vector3 p2 = vertices[map_hedges[v.e1]->b];
|
||||
Vector3 p3 = vertices[map_hedges[v.e2]->b];
|
||||
@@ -492,9 +514,29 @@ void RoadGrid::sort_angle(Vector<int> &sort_data)
|
||||
sorter.sort(sort_data.ptrw(), sort_data.size());
|
||||
}
|
||||
|
||||
void RoadGrid::generate_3d_vertices()
|
||||
void RoadGrid::generate_3d_vertices(Ref<Curve> curve, Ref<FastNoiseLite> noise)
|
||||
{
|
||||
int i;
|
||||
int i, j;
|
||||
Vector<float> diagram_vertex_heights;
|
||||
diagram_vertex_heights.resize(diagram_vertices.size());
|
||||
for (i = 0; i < diagram_vertices.size(); i++) {
|
||||
const Vector2 &v = diagram_vertices[i];
|
||||
float n = noise->get_noise_2d(v.x, v.y);
|
||||
float t = (n + 1.0f) * 0.5f;
|
||||
float d = MAX(1.0f, curve->interpolate_baked(t));
|
||||
d = CLAMP(d, 1.0f, 30.0f);
|
||||
diagram_vertex_heights.write[i] = d;
|
||||
}
|
||||
for (j = 0; j < 3; j++) {
|
||||
for (i = 0; i < map_hedges.size(); i++) {
|
||||
int x1 = map_hedges[i]->a;
|
||||
int x2 = map_hedges[i]->b;
|
||||
float xd = map_hedges[i]->length;
|
||||
float dh = fabsf(diagram_vertex_heights[x2] - diagram_vertex_heights[x1]);
|
||||
if (fabsf(dh / xd) > 0.01f)
|
||||
diagram_vertex_heights.write[x2] = diagram_vertex_heights[x1] + dh / fabsf(dh) * 0.01f * xd;
|
||||
}
|
||||
}
|
||||
vertices.resize(get_diagram_vertex_count());
|
||||
for (i = 0; i < vertices.size(); i++) {
|
||||
vertices.write()[i].x = diagram_vertices[i].x;
|
||||
@@ -525,3 +567,117 @@ int RoadGrid::get_site_from_point(int x, int z)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static String var2str(const Variant &data)
|
||||
{
|
||||
String s;
|
||||
VariantWriter::write_to_string(data, s);
|
||||
return s;
|
||||
}
|
||||
template <class T>
|
||||
static Array avar2str(const Vector<T> &data)
|
||||
{
|
||||
Array pdata;
|
||||
int i;
|
||||
pdata.resize(data.size());
|
||||
for (i = 0; i < data.size(); i++)
|
||||
pdata[i] = var2str(data[i]);
|
||||
return pdata;
|
||||
}
|
||||
static Dictionary half_edge2dict(const struct half_edge *he)
|
||||
{
|
||||
Dictionary ret;
|
||||
ret["a"] = he->a;
|
||||
ret["b"] = he->b;
|
||||
ret["site"] = he->site;
|
||||
ret["depth"] = he->depth;
|
||||
ret["length"] = he->length;
|
||||
return ret;
|
||||
}
|
||||
static Array rect2i_array(const Rect2i &rect)
|
||||
{
|
||||
Array tmp;
|
||||
tmp.resize(4);
|
||||
tmp[0] = rect.position[0];
|
||||
tmp[1] = rect.position[1];
|
||||
tmp[2] = rect.size[0];
|
||||
tmp[3] = rect.size[1];
|
||||
return tmp;
|
||||
}
|
||||
static Dictionary map_site2dict(const struct map_site *ms)
|
||||
{
|
||||
int i;
|
||||
Dictionary ret;
|
||||
Array hedges;
|
||||
ret["index"] = ms->index;
|
||||
ret["pos"] = var2str(ms->pos);
|
||||
ret["vertices"] = avar2str(ms->vertices);
|
||||
ret["polygon"] = avar2str(ms->polygon);
|
||||
ret["vertices_ind"] = avar2str(ms->vertices_ind);
|
||||
ret["polygon_ind"] = avar2str(ms->polygon_ind);
|
||||
ret["site_type"] = ms->site_type;
|
||||
ret["cluster"] = ms->cluster;
|
||||
hedges.resize(ms->hedges.size());
|
||||
for (i = 0; i < ms->hedges.size(); i++)
|
||||
hedges[i] = half_edge2dict(&ms->hedges[i]);
|
||||
ret["hedges"] = hedges;
|
||||
ret["rect"] = rect2i_array(ms->rect);
|
||||
ret["avg_height"] = ms->avg_height;
|
||||
return ret;
|
||||
}
|
||||
static Dictionary vshape2dict(const struct RoadGrid::vshape *v)
|
||||
{
|
||||
Dictionary ret;
|
||||
ret["area"] = var2str(v->area);
|
||||
ret["instance"] = v->instance;
|
||||
ret["e1"] = v->e1;
|
||||
ret["e2"] = v->e2;
|
||||
ret["site"] = v->site;
|
||||
ret["p1"] = var2str(v->p1);
|
||||
ret["p2"] = var2str(v->p2);
|
||||
ret["p3"] = var2str(v->p3);
|
||||
ret["depth1"] = v->depth1;
|
||||
ret["depth2"] = v->depth2;
|
||||
return ret;
|
||||
}
|
||||
void RoadGrid::save_json(const String &path)
|
||||
{
|
||||
int i;
|
||||
String vars;
|
||||
FileAccess *f;
|
||||
Dictionary to_json;
|
||||
/* Clusters */
|
||||
Array save_clusters;
|
||||
/* save clusters */
|
||||
save_clusters.resize(clusters.size());
|
||||
for (i = 0; i < clusters.size(); i++) {
|
||||
Dictionary cluster;
|
||||
cluster["pos_x"] = clusters[i].c.x;
|
||||
cluster["pos_y"] = clusters[i].c.y;
|
||||
cluster["radius"] = clusters[i].r;
|
||||
save_clusters[i] = cluster;
|
||||
}
|
||||
to_json["clusters"] = save_clusters;
|
||||
to_json["bounds"] = var2str(bounds);
|
||||
to_json["diagram_vertices"] = avar2str(diagram_vertices);
|
||||
Array msites, mvshapes;
|
||||
msites.resize(map_sites.size());
|
||||
for (i = 0; i < map_sites.size(); i++)
|
||||
msites[i] = map_site2dict(&map_sites[i]);
|
||||
to_json["map_sites"] = msites;
|
||||
to_json["vertices"] = var2str(vertices);
|
||||
mvshapes.resize(vshapes.size());
|
||||
for (i = 0; i < vshapes.size(); i++) {
|
||||
const struct vshape *v = &(vshapes.read()[i]);
|
||||
mvshapes[i] = vshape2dict(v);
|
||||
}
|
||||
to_json["vshapes"] = mvshapes;
|
||||
|
||||
String json = JSON::print(to_json, "\t");
|
||||
f = FileAccess::open(path, FileAccess::WRITE);
|
||||
if (f) {
|
||||
f->store_string(json);
|
||||
f->close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,25 +32,15 @@ class CanvasItem;
|
||||
* - Implement 3D positions and geometry generation
|
||||
*/
|
||||
|
||||
class RoadGrid: public Reference {
|
||||
GDCLASS(RoadGrid, Object)
|
||||
protected:
|
||||
Ref<RandomNumberGenerator> rnd;
|
||||
struct cluster {
|
||||
Vector2i c;
|
||||
float r;
|
||||
};
|
||||
List<struct cluster> clusters;
|
||||
Dictionary build_diagram(int npatches, int center_count, int center_step,
|
||||
int spread, int dim);
|
||||
HashMap<int, int> class_sizes;
|
||||
struct half_edge;
|
||||
Rect2 bounds;
|
||||
void set_class_size(int cl, int sz)
|
||||
{
|
||||
class_sizes[cl] = sz;
|
||||
}
|
||||
static void _bind_methods();
|
||||
struct cluster {
|
||||
Vector2i c;
|
||||
float r;
|
||||
};
|
||||
struct graphedge {
|
||||
int a, b;
|
||||
int edge;
|
||||
};
|
||||
struct map_site {
|
||||
enum {
|
||||
SITE_UNASSIGNED = 0,
|
||||
SITE_FOREST,
|
||||
@@ -59,36 +49,33 @@ protected:
|
||||
SITE_EMPTY,
|
||||
SITE_MAX
|
||||
};
|
||||
struct graphedge {
|
||||
int a, b;
|
||||
int edge;
|
||||
};
|
||||
struct half_edge {
|
||||
int a, b;
|
||||
int site;
|
||||
float depth;
|
||||
float length;
|
||||
};
|
||||
struct map_site {
|
||||
int index;
|
||||
Vector2 pos;
|
||||
Vector<struct graphedge> graphedges;
|
||||
Vector<Vector2> vertices;
|
||||
Vector<Vector2> polygon;
|
||||
Vector<int> vertices_ind;
|
||||
Vector<int> polygon_ind;
|
||||
int site_type;
|
||||
int cluster;
|
||||
Vector<struct half_edge> hedges;
|
||||
Vector2 building_positions;
|
||||
Rect2i rect;
|
||||
float avg_height;
|
||||
};
|
||||
void index_site(struct map_site *site);
|
||||
Vector<Vector2> diagram_vertices;
|
||||
Vector<float> diagram_vertex_heights;
|
||||
int index;
|
||||
Vector2 pos;
|
||||
Vector<struct graphedge> graphedges;
|
||||
Vector<Vector2> vertices;
|
||||
Vector<Vector2> polygon;
|
||||
Vector<int> vertices_ind;
|
||||
Vector<int> polygon_ind;
|
||||
int site_type;
|
||||
int cluster;
|
||||
Vector<struct half_edge> hedges;
|
||||
Vector2 building_positions;
|
||||
Rect2i rect;
|
||||
float avg_height;
|
||||
};
|
||||
struct half_edge {
|
||||
int a, b;
|
||||
int site;
|
||||
float depth;
|
||||
float length;
|
||||
};
|
||||
class RoadDiagram {
|
||||
protected:
|
||||
List<struct cluster> clusters;
|
||||
Vector<struct map_site> map_sites;
|
||||
Vector<Vector2> diagram_vertices;
|
||||
Vector<struct half_edge *> map_hedges;
|
||||
HashMap<int, int> class_sizes;
|
||||
void classify_sites()
|
||||
{
|
||||
int i, j;
|
||||
@@ -103,7 +90,7 @@ protected:
|
||||
}
|
||||
}
|
||||
int cl_area = (int)(r.get_area() + 1.0f);
|
||||
for (i = 0; i < SITE_MAX; i++) {
|
||||
for (i = 0; i < map_site::SITE_MAX; i++) {
|
||||
if (class_sizes.has(i))
|
||||
if (cl_area <= class_sizes[i]) {
|
||||
map_sites.write[j].site_type = i;
|
||||
@@ -113,9 +100,52 @@ protected:
|
||||
/* for now the starting town is at 0 */
|
||||
printf("area: %d class: %d\n", cl_area, map_sites[j].site_type);
|
||||
}
|
||||
map_sites.write[0].site_type = SITE_TOWN;
|
||||
map_sites.write[0].site_type = map_site::SITE_TOWN;
|
||||
}
|
||||
void index_site(struct map_site *site);
|
||||
Dictionary build_diagram(Ref<RandomNumberGenerator> rnd,
|
||||
int npatches, int center_count, int center_step,
|
||||
int spread, int dim);
|
||||
void process_diagram(const Dictionary &diagram);
|
||||
public:
|
||||
RoadDiagram();
|
||||
void set_class_size(int cl, int sz)
|
||||
{
|
||||
class_sizes[cl] = sz;
|
||||
}
|
||||
const List<struct cluster> &get_clusters() const;
|
||||
const Vector<struct map_site> &get_map_sites() const;
|
||||
const Vector<Vector2> &get_diagram_vertices() const;
|
||||
const Vector<struct half_edge *> &get_map_hedges() const;
|
||||
void build(Ref<RandomNumberGenerator> rnd,
|
||||
int npatches, int center_count, int center_step,
|
||||
int spread, int dim);
|
||||
};
|
||||
|
||||
class RoadGrid: public Reference {
|
||||
GDCLASS(RoadGrid, Object)
|
||||
protected:
|
||||
Ref<RandomNumberGenerator> rnd;
|
||||
int keep_seed;
|
||||
List<struct cluster> clusters;
|
||||
protected:
|
||||
Rect2 bounds;
|
||||
static void _bind_methods();
|
||||
public:
|
||||
struct vshape {
|
||||
AABB area;
|
||||
int instance;
|
||||
int e1, e2;
|
||||
int site;
|
||||
Vector3 p1, p2, p3;
|
||||
float depth1, depth2;
|
||||
};
|
||||
protected:
|
||||
Vector<Vector2> diagram_vertices;
|
||||
Vector<struct map_site> map_sites;
|
||||
Vector<struct half_edge *> map_hedges;
|
||||
PoolVector<Vector3> vertices;
|
||||
PoolVector<struct vshape> vshapes;
|
||||
bool segment_intersects_rect(const Vector2 &a, const Vector2 &b, const Rect2 &rect);
|
||||
inline bool segment_in_grid_rect(const Vector2 &a, const Vector2 &b, int x, int y)
|
||||
{
|
||||
@@ -170,15 +200,15 @@ protected:
|
||||
items.write[items.size() - 1] = hedge;
|
||||
hedge_grid[x][y] = items;
|
||||
}
|
||||
inline void insert_hedge_to_grid_cell(int x, int y, struct half_edge *hedge)
|
||||
{
|
||||
static int count = 0;
|
||||
count++;
|
||||
set(x, y, hedge);
|
||||
printf("count: %d\n", count);
|
||||
}
|
||||
};
|
||||
class hg hedge_grid;
|
||||
inline void insert_hedge_to_grid_cell(int x, int y, struct half_edge *hedge)
|
||||
{
|
||||
static int count = 0;
|
||||
count++;
|
||||
hedge_grid.set(x, y, hedge);
|
||||
printf("count: %d\n", count);
|
||||
}
|
||||
inline void add_hedge_to_grid(struct half_edge *hedge)
|
||||
{
|
||||
Vector2 a = diagram_vertices[hedge->a];
|
||||
@@ -201,7 +231,7 @@ protected:
|
||||
int py = rgrid.position.y + y;
|
||||
Rect2 xr = get_grid_rect(px, py).grow(16.0f);
|
||||
if (segment_intersects_rect(a, b, xr))
|
||||
insert_hedge_to_grid_cell(px, py, hedge);
|
||||
hedge_grid.insert_hedge_to_grid_cell(px, py, hedge);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,17 +244,6 @@ protected:
|
||||
return (int)(y / grid_height);
|
||||
}
|
||||
float grid_width, grid_height;
|
||||
PoolVector<Vector3> vertices;
|
||||
public:
|
||||
struct vshape {
|
||||
AABB area;
|
||||
int instance;
|
||||
int e1, e2;
|
||||
int site;
|
||||
Vector3 p1, p2, p3;
|
||||
};
|
||||
protected:
|
||||
PoolVector<struct vshape> vshapes;
|
||||
void sort_angle(Vector<int> &sort_data);
|
||||
public:
|
||||
void build(Ref<Curve> curve, Ref<FastNoiseLite> noise);
|
||||
@@ -252,17 +271,17 @@ public:
|
||||
{
|
||||
return map_hedges.size();
|
||||
}
|
||||
void generate_3d_vertices();
|
||||
void generate_3d_vertices(Ref<Curve> curve, Ref<FastNoiseLite> noise);
|
||||
void generate_building_positions();
|
||||
void generate_site_building_positions(const struct map_site *site);
|
||||
int get_site_from_point(int x, int z);
|
||||
inline bool site_is_town(int site)
|
||||
{
|
||||
return map_sites[site].site_type == SITE_TOWN;
|
||||
return map_sites[site].site_type == map_site::SITE_TOWN;
|
||||
}
|
||||
inline bool site_is_farm(int site)
|
||||
{
|
||||
return map_sites[site].site_type == SITE_FARM;
|
||||
return map_sites[site].site_type == map_site::SITE_FARM;
|
||||
}
|
||||
inline float get_site_avg_height(int site)
|
||||
{
|
||||
@@ -277,6 +296,8 @@ public:
|
||||
{
|
||||
if (polygon_cache_2d.has(site))
|
||||
return polygon_cache_2d[site];
|
||||
if (map_sites.size() <= site)
|
||||
return PoolVector<Vector2>();
|
||||
int count = 0, i;
|
||||
int psize = map_sites[site].polygon_ind.size();
|
||||
PoolVector<Vector2> polygon;
|
||||
@@ -298,6 +319,8 @@ public:
|
||||
{
|
||||
if (polygon_cache_3d.has(site))
|
||||
return polygon_cache_3d[site];
|
||||
if (map_sites.size() <= site)
|
||||
return PoolVector<Vector3>();
|
||||
int count = 0, i;
|
||||
int psize = map_sites[site].polygon_ind.size();
|
||||
PoolVector<Vector3> polygon;
|
||||
@@ -329,5 +352,6 @@ public:
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
void save_json(const String &path);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -172,17 +172,20 @@ Ref<Curve3D> Roads::build_curve(Vector3 p1, Vector3 p2, Vector3 p3, float total_
|
||||
return curve3;
|
||||
}
|
||||
|
||||
Array Roads::curve_mesh(PoolVector<Vector3> points, float width, int flags, float sidewalk_sidth)
|
||||
Array Roads::curve_mesh(PoolVector<Vector3> points, float width1, float width2, int flags, float sidewalk_width)
|
||||
{
|
||||
float tx = 0.0f, total_width = 0.0f, t = 0.0f, l;
|
||||
float tx = 0.0f, total_width1 = 0.0f, total_width2 = 0.0f, t = 0.0f, l;
|
||||
int i;
|
||||
Array ret;
|
||||
all_offsets();
|
||||
PoolVector<String> parts_list = build_item_list(width, flags, sidewalk_sidth);
|
||||
for (i = 0; i < parts_list.size(); i++)
|
||||
total_width += offsets[parts_list[i]].x;
|
||||
assert(total_width >= 3.0f);
|
||||
Ref<Curve3D> curve3 = build_curve(points[0], points[1], points[2], total_width);
|
||||
PoolVector<String> parts_list1 = build_item_list(width1, flags, sidewalk_width);
|
||||
for (i = 0; i < parts_list1.size(); i++)
|
||||
total_width1 += offsets[parts_list1[i]].x;
|
||||
PoolVector<String> parts_list2 = build_item_list(width2, flags, sidewalk_width);
|
||||
for (i = 0; i < parts_list2.size(); i++)
|
||||
total_width2 += offsets[parts_list2[i]].x;
|
||||
assert(total_width1 >= 3.0f && total_width2 >= 3.0f);
|
||||
Ref<Curve3D> curve3 = build_curve(points[0], points[1], points[2], total_width2);
|
||||
l = curve3->get_baked_length();
|
||||
assert(l > 0.0f);
|
||||
PoolVector<Vector3> new_verts, new_normals;
|
||||
@@ -193,9 +196,9 @@ Array Roads::curve_mesh(PoolVector<Vector3> points, float width, int flags, floa
|
||||
while (t <= l) {
|
||||
tx = 0.0f;
|
||||
int part = 0;
|
||||
while (tx < total_width) {
|
||||
while (tx < total_width2) {
|
||||
int k;
|
||||
Array data = mesh_data[parts_list[part]];
|
||||
Array data = mesh_data[parts_list2[part]];
|
||||
int b = new_verts.size();
|
||||
PoolVector<Vector3> verts = data[Mesh::ARRAY_VERTEX];
|
||||
PoolVector<Vector3> normals = data[Mesh::ARRAY_NORMAL];
|
||||
@@ -226,7 +229,7 @@ Array Roads::curve_mesh(PoolVector<Vector3> points, float width, int flags, floa
|
||||
Vector3 n = xform.xform(normals[k]);
|
||||
if (right < 0.15f &&
|
||||
((flags & FLAGS_INTERSECTION) != 0) &&
|
||||
nvert.distance_squared_to(points[1]) < total_width * total_width * 2.5f) {
|
||||
nvert.distance_squared_to(points[1]) < total_width2 * total_width2 * 2.5f) {
|
||||
new_verts.write()[b + k] = points[1];
|
||||
new_normals.write()[b + k] = Vector3(0.0f, 1.0f, 0.0f);
|
||||
} else {
|
||||
@@ -240,9 +243,9 @@ Array Roads::curve_mesh(PoolVector<Vector3> points, float width, int flags, floa
|
||||
new_index.resize(idx + index.size());
|
||||
for (k = 0; k < index.size(); k++)
|
||||
new_index.write()[idx + k] = index[k] + b;
|
||||
tx += offsets[parts_list[part]].x;
|
||||
tx += offsets[parts_list2[part]].x;
|
||||
part += 1;
|
||||
if (part >= parts_list.size())
|
||||
if (part >= parts_list2.size())
|
||||
break;
|
||||
}
|
||||
t += 2.0f;
|
||||
@@ -258,6 +261,7 @@ Array Roads::curve_mesh(PoolVector<Vector3> points, float width, int flags, floa
|
||||
static Ref<ConcavePolygonShape> create_concave_polygon_shape(Vector<Array> surfaces) {
|
||||
PoolVector<Vector3> face_points;
|
||||
int face_points_size = 0;
|
||||
assert(surfaces.size() > 0);
|
||||
|
||||
//find the correct size for face_points
|
||||
for (int i = 0; i < surfaces.size(); i++) {
|
||||
@@ -268,12 +272,14 @@ static Ref<ConcavePolygonShape> create_concave_polygon_shape(Vector<Array> surfa
|
||||
}
|
||||
// If the surface is not empty then it must have an expected amount of data arrays
|
||||
ERR_CONTINUE(surface_arrays.size() != Mesh::ARRAY_MAX);
|
||||
assert(surface_arrays.size() == Mesh::ARRAY_MAX);
|
||||
PoolVector<int> indices = surface_arrays[Mesh::ARRAY_INDEX];
|
||||
face_points_size += indices.size();
|
||||
}
|
||||
face_points.resize(face_points_size);
|
||||
|
||||
if (face_points_size < 3) {
|
||||
assert(0);
|
||||
return Ref<ConcavePolygonShape>();
|
||||
}
|
||||
|
||||
@@ -287,6 +293,10 @@ static Ref<ConcavePolygonShape> create_concave_polygon_shape(Vector<Array> surfa
|
||||
PoolVector<Vector3> positions = surface_arrays[Mesh::ARRAY_VERTEX];
|
||||
PoolVector<int> indices = surface_arrays[Mesh::ARRAY_INDEX];
|
||||
|
||||
assert(positions.size() >= 3);
|
||||
assert(indices.size() >= 3);
|
||||
assert(indices.size() % 3 == 0);
|
||||
|
||||
ERR_FAIL_COND_V(positions.size() < 3, Ref<ConcavePolygonShape>());
|
||||
ERR_FAIL_COND_V(indices.size() < 3, Ref<ConcavePolygonShape>());
|
||||
ERR_FAIL_COND_V(indices.size() % 3 != 0, Ref<ConcavePolygonShape>());
|
||||
@@ -312,7 +322,7 @@ static Ref<ConcavePolygonShape> create_concave_polygon_shape(Vector<Array> surfa
|
||||
}
|
||||
|
||||
int Roads::make_vmesh(Node *root, Ref<Material> mat, Ref<ArrayMesh> mesh, MeshInstance *xmi, Vector3 p1, Vector3 p2,
|
||||
Vector3 p3, float width, int flags, float sidewalk_width)
|
||||
Vector3 p3, float width1, float width2, int flags, float sidewalk_width)
|
||||
{
|
||||
Vector3 m1 = p1 - p2;
|
||||
Vector3 m2 = Vector3();
|
||||
@@ -329,7 +339,7 @@ int Roads::make_vmesh(Node *root, Ref<Material> mat, Ref<ArrayMesh> mesh, MeshIn
|
||||
points.resize(3);
|
||||
for (i = 0; i < 3; i++)
|
||||
points.write()[i] = pts[i].snapped(Vector3(4.0f, 0.1f, 4.0f));
|
||||
Array rdata = curve_mesh(points, width, flags, sidewalk_width);
|
||||
Array rdata = curve_mesh(points, width1, width2, flags, sidewalk_width);
|
||||
Ref<ArrayMesh> mdata = mesh;
|
||||
assert(mdata.is_valid());
|
||||
mdata->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, rdata);
|
||||
@@ -352,9 +362,25 @@ void Roads::add_scene_element(Node *root, Node *xnode, const Vector3 &p2, Ref<Co
|
||||
Transform xform(Basis(), p2);
|
||||
assert(xmi->get_mesh().is_valid() && xmi->get_mesh()->get_surface_count() > 0);
|
||||
xmi->set_global_transform(xform);
|
||||
printf("trimesh collision\n");
|
||||
#if 0
|
||||
Node *c = xmi->create_trimesh_collision();
|
||||
assert(c);
|
||||
add_child(c);
|
||||
#if 0
|
||||
CollisionShape *cs = memnew(CollisionShape);
|
||||
cs->set_shape(shape);
|
||||
body->add_child(cs);
|
||||
#endif
|
||||
#endif
|
||||
StaticBody *sb = memnew(StaticBody);
|
||||
CollisionShape *cs = memnew(CollisionShape);
|
||||
assert(sb);
|
||||
assert(cs);
|
||||
sb->add_child(cs);
|
||||
assert(!shape.is_null());
|
||||
cs->set_shape(shape);
|
||||
xmi->call_deferred("add_child", sb);
|
||||
}
|
||||
|
||||
void Roads::process_vshapes()
|
||||
@@ -368,7 +394,6 @@ void Roads::process_vshapes()
|
||||
int i;
|
||||
List<int> active_vshapes;
|
||||
const PoolVector<struct RoadGrid::vshape> &vshapes = rg->get_vshapes();
|
||||
printf("camera %f %f %f\n", cam_xform.origin.x, cam_xform.origin.y, cam_xform.origin.z);
|
||||
for (i = 0; i < vshapes.size(); i++) {
|
||||
if (active_vshapes.size() > 32)
|
||||
break;
|
||||
@@ -387,7 +412,6 @@ void Roads::process_vshapes()
|
||||
active_vshapes.push_back(i);
|
||||
#endif
|
||||
}
|
||||
printf("active vshapes %d\n", active_vshapes.size());
|
||||
List<int>::Element *e;
|
||||
for (e = active_vshapes.front(); e; e = e->next()) {
|
||||
i = e->get();
|
||||
@@ -438,8 +462,10 @@ void Roads::generate_threaded(void *p_userdata)
|
||||
Vector3 p1 = vshapes[data->vshape].p1;
|
||||
Vector3 p2 = vshapes[data->vshape].p2;
|
||||
Vector3 p3 = vshapes[data->vshape].p3;
|
||||
float width1 = vshapes[data->vshape].depth1;
|
||||
float width2 = vshapes[data->vshape].depth2;
|
||||
obj->mutex.unlock();
|
||||
int instance = obj->make_vmesh(obj, data->mat, data->mesh, data->xmi, p1, p2, p3, data->width, data->flags, data->sidewalk_width);
|
||||
int instance = obj->make_vmesh(obj, data->mat, data->mesh, data->xmi, p1, p2, p3, width1, width2, data->flags, data->sidewalk_width);
|
||||
assert(instance >= 0);
|
||||
obj->mutex.lock();
|
||||
rg->get_vshapes().write()[data->vshape].instance = instance;
|
||||
@@ -484,6 +510,7 @@ void RoadsData::_bind_methods()
|
||||
ClassDB::bind_method(D_METHOD("get_site_polygon_3d", "site"), &RoadsData::get_site_polygon_3d);
|
||||
ClassDB::bind_method(D_METHOD("get_here_sites", "site"), &RoadsData::get_here_sites);
|
||||
ClassDB::bind_method(D_METHOD("get_site_avg_height", "site"), &RoadsData::get_site_avg_height);
|
||||
ClassDB::bind_method(D_METHOD("save_json", "path"), &RoadsData::save_json);
|
||||
}
|
||||
void RoadsData::set_noise(Ref<FastNoiseLite> noise)
|
||||
{
|
||||
|
||||
@@ -70,9 +70,9 @@ public:
|
||||
void all_offsets();
|
||||
PoolVector<String> build_item_list(float width, int flags, float sidewalk_width) const;
|
||||
Ref<Curve3D> build_curve(Vector3 p1, Vector3 p2, Vector3 p3, float total_width) const;
|
||||
Array curve_mesh(PoolVector<Vector3> points, float width, int flags, float sidewalk_width);
|
||||
Array curve_mesh(PoolVector<Vector3> points, float width1, float width2, int flags, float sidewalk_width);
|
||||
int make_vmesh(Node *root, Ref<Material> mat, Ref<ArrayMesh> mesh, MeshInstance *xmi, Vector3 p1, Vector3 p2,
|
||||
Vector3 p3, float width, int flags, float sidewalk_width);
|
||||
Vector3 p3, float width1, float width2, int flags, float sidewalk_width);
|
||||
void process_vshapes();
|
||||
void add_scene_element(Node *root, Node *xnode, const Vector3 &p2, Ref<ConcavePolygonShape> shape);
|
||||
};
|
||||
@@ -104,5 +104,9 @@ public:
|
||||
{
|
||||
return rg->get_site_avg_height(site);
|
||||
}
|
||||
inline void save_json(const String &path)
|
||||
{
|
||||
rg->save_json(path);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user