Files
streaming_world/src/modules/stream/stream.cpp
2025-04-01 12:09:43 +03:00

582 lines
16 KiB
C++

#undef NDEBUG
#include <cassert>
#include <core/set.h>
#include <core/io/json.h>
#include <core/os/time.h>
#include <scene/main/viewport.h>
#include <scene/resources/packed_scene.h>
#include <scene/resources/material.h>
#include <scene/3d/mesh_instance.h>
#include <main/performance.h>
#include <modules/voxel/terrain/voxel_viewer.h>
#include <modules/voxel/terrain/voxel_lod_terrain.h>
#include "from_string.h"
#include "road_processing.h"
#include "road_debug.h"
#include "buildings_data.h"
#include "base_data.h"
#include "npc/npc.h"
#include "stream.h"
#define data() (BuildingsData::get_singleton())
void StreamWorld::create_tilemap()
{
tiles.clear();
data()->for_each_building(
[this](const String &key, void *hdata) {
const struct BuildingsData::building &b =
data()->get_building(key);
int tile_x = b.xform.origin.x / tile_size;
int tile_z = b.xform.origin.z / tile_size;
std::tuple<int, int> tkey =
std::make_tuple(tile_x, tile_z);
if (tiles.find(tkey) != tiles.end())
tiles[tkey].push_back(key);
else {
std::vector<String> data = { key };
tiles[tkey] = data;
}
},
nullptr);
print_verbose("Tile count: " + itos(tiles.size()));
}
void StreamWorld::update_view()
{
int i, j;
ERR_FAIL_COND_MSG(!initialized,
"The Stream object is incorrectly initialized");
if (!viewer || !terrain) {
VoxelViewer *v = nullptr;
VoxelLodTerrain *t = nullptr;
Node *scene = current_scene;
ERR_FAIL_COND_MSG(!scene, "No current scene");
List<Node *> queue;
queue.push_back(scene);
while (!queue.empty()) {
Node *item = queue.front()->get();
ERR_FAIL_COND_MSG(!item, "Something really fucked up");
VoxelViewer *tv = Object::cast_to<VoxelViewer>(item);
VoxelLodTerrain *tt =
Object::cast_to<VoxelLodTerrain>(item);
if (tv)
v = tv;
else if (tt)
t = tt;
if (v && t)
break;
int c = item->get_child_count();
for (i = 0; i < c; i++)
queue.push_back(item->get_child(i));
queue.pop_front();
}
ERR_FAIL_COND_MSG(!v, "VoxelViewer was not found");
ERR_FAIL_COND_MSG(!t, "VoxelLodTerrain was not found");
viewer = v;
terrain = t;
viewer->connect("tree_exiting", this, "viewer_dead");
terrain->connect("tree_exiting", this, "terrain_dead");
current_x = world_extent + 1;
current_z = world_extent + 1;
}
eye = viewer->get_global_transform().origin;
int tile_x = int(eye.x / tile_size);
int tile_z = int(eye.z / tile_size);
if (current_x != tile_x || current_z != tile_z) {
print_verbose("tile: " + itos(tile_x) + " " + itos(tile_z));
for (i = tile_z - view_distance; i < tile_z + view_distance + 1;
i++)
for (j = tile_x - view_distance;
j < tile_x + view_distance + 1; j++) {
std::tuple<int, int> key =
std::make_tuple(j, i);
if (tiles.find(key) != tiles.end()) {
if (loaded_tiles.find(key) ==
loaded_tiles.end()) {
print_verbose(
"load tile: " +
itos(j) + " " +
itos(i) + " = " +
itos(tiles[key].size()));
load_tile(j, i);
loaded_tiles[key] = tiles[key];
}
}
}
auto it = loaded_tiles.begin();
int erase_distance = view_distance + 1;
int ed2 = erase_distance * erase_distance;
while (it != loaded_tiles.end()) {
std::tuple<int, int> lkey = it->first;
int kx = std::get<0>(lkey);
int kz = std::get<1>(lkey);
int lx = kx - tile_x;
int lz = kz - tile_z;
if (lx * lx > ed2 || lz * lz > ed2) {
print_verbose("erase tile: " + itos(kx) + " " +
itos(kz));
erase_tile(kx, kz);
it = loaded_tiles.erase(it);
} else
it++;
}
current_x = tile_x;
current_z = tile_z;
}
}
void StreamWorld::viewer_dead()
{
print_error("viewer dead");
viewer = nullptr;
set_process(false);
}
void StreamWorld::terrain_dead()
{
print_verbose("terrain dead");
terrain = nullptr;
set_process(false);
}
void StreamWorld::load_tile(int tx, int ty)
{
int i;
std::tuple<int, int> key = std::make_tuple(tx, ty);
const std::vector<String> &items = tiles[key];
for (i = 0; i < (int)items.size(); i++) {
print_verbose("load item: " + itos(i) + ": key: " + (items[i]) +
": " /* + data()->get_building(items[i]).id */);
const String &bkey = items[i];
load_building(bkey);
}
}
void StreamWorld::erase_tile(int tx, int ty)
{
int i;
std::tuple<int, int> key = std::make_tuple(tx, ty);
const std::vector<String> &items = tiles[key];
for (i = 0; i < (int)items.size(); i++) {
print_verbose("unload item: " + itos(i) + ": key: " + items[i] +
": " /* + data()->get_building(items[i]).id */);
const String &bkey = items[i];
unload_building(bkey);
}
}
void StreamWorld::load_building(const String &key)
{
request_item(0, key);
}
void StreamWorld::unload_building(const String &key)
{
request_item(1, key);
}
void StreamWorld::request_item(int type, const String &bkey)
{
bool debug = false;
/* bkey can contain "::"'s so need to replae them with underscores */
String ekey = bkey;
if (bkey.begins_with("road__")) {
print_verbose("loading building: " + ekey);
debug = true;
}
String id = data()->get_building(ekey).id;
if (debug)
print_verbose("building id: " + id);
if (id == "empty")
return;
switch (type) {
case 0:
if (debug)
print_verbose("add to scene id: " + id);
if (!data()->has_scene(id))
data()->create_scene_data(id, ekey);
else
data()->add_scene_item(id, ekey);
if (debug)
print_verbose("added to scene id: " + id);
break;
case 1: {
print_verbose("Removing key:" + ekey);
if (data()->has_scene(id)) {
data()->remove_scene_item(id, ekey);
}
} break;
}
}
void StreamWorld::update_items()
{
List<String> keys;
data()->get_scene_keys_list(&keys);
List<String>::Element *e = keys.front();
while (e) {
const String &key = e->get();
if (!data()->scene_get_packed_scene(key).is_valid()) {
e = e->next();
continue;
}
const List<String> &items = data()->scene_get_items(key);
const List<String>::Element *be = items.front();
while (be) {
const String &bkey = be->get();
if (data()->has_scene_item(key, bkey) &&
data()->get_scene_item_node(key, bkey)) {
be = be->next();
continue;
}
Node *psc =
data()->scene_get_packed_scene(key)->instance();
Spatial *psp = Object::cast_to<Spatial>(psc);
// data()->item_nodes_set_node(bkey, psc);
data()->set_scene_item_node(key, bkey, psc);
current_scene->call_deferred("add_child", psp);
psp->call_deferred("set_global_transform",
data()->get_building(bkey).xform);
be = be->next();
}
e = e->next();
}
}
void StreamWorld::remove_building(const String &key)
{
// TODO: implement
unload_building(key);
data()->destroy_building(key);
assert(!data()->has_building(key));
update_items();
}
VoxelLodTerrain *StreamWorld::get_terrain()
{
return terrain;
}
void StreamWorld::run_command(const String &command,
const Vector<Variant> &args)
{
if (command == "get_closest_building") {
if (args.size() == 0) {
print_error("bad command: not enough args: " + command);
return;
}
const Transform &xform = args[0];
String key = data()->get_closest_building(xform);
Array ret_data;
ret_data.resize(5);
ret_data[0] = data()->get_building(key).xform;
ret_data[1] = xform.origin.distance_squared_to(
data()->get_building(key).xform.origin);
ret_data[2] = key;
ret_data[3] = key;
ret_data[4] = data()->get_building(key).id;
emit_signal("command_result", command, ret_data);
} else if (command == "update_building_transform") {
if (args.size() == 0) {
print_error("bad command: not enough args: " + command);
return;
}
String key = args[0];
data()->update_building_transform(key, args[1]);
Node *node = data()->item_nodes_get_node(key);
Spatial *bnode = Object::cast_to<Spatial>(node);
if (bnode)
bnode->set_global_transform(args[1]);
} else if (command == "checkpoint")
data()->checkpoint();
else if (command == "undo")
data()->undo();
else if (command == "buildings_save") {
String buildings_path =
config.get_value("buildings", "buildings_path");
data()->save_buildings_json(buildings_path);
} else if (command == "get_building_types") {
Dictionary buildings_data =
config.get_value("buildings", "building_data");
Array ret_data;
ret_data.push_back(buildings_data);
emit_signal("command_result", command, ret_data);
} else if (command == "change_building_type") {
if (args.size() == 0) {
print_error("bad command: not enough args: " + command);
return;
}
String key = args[0];
String new_type = args[1];
Dictionary buildings_data =
config.get_value("buildings", "building_data");
if (!data()->building_data.has(new_type)) {
print_error("unknown building type: " + new_type);
return;
}
String old_type = data()->get_building(key).id;
unload_building(key);
data()->get_building(key).id = new_type;
load_building(key);
update_items();
print_verbose("changed building: key:" + key +
" from: " + old_type + " to: " + new_type);
} else if (command == "create_building") {
if (args.size() == 0) {
print_error("bad command: not enough args: " + command);
return;
}
const Dictionary &building_dict = args[0];
struct BuildingsData::building b;
// TODO: check that key is valid
print_verbose("DICT: " + (JSON::print(building_dict, "\t")));
BuildingsData::building::from_dict(&b, building_dict);
data()->create_building(b);
assert(b.key.length() > 0);
load_building(b.key);
} else if (command == "remove_building") {
if (args.size() == 0) {
print_error("bad command: not enough args: " + command);
return;
}
const String &key = args[0];
remove_building(key);
} else if (command == "remove_buildings_by_prefix") {
if (args.size() == 0) {
print_error("bad command: not enough args: " + command);
return;
}
std::vector<String> erased_keys;
erased_keys.reserve(data()->get_building_count());
String prefix = args[0];
int i;
data()->for_each_building(
[&erased_keys, prefix](const String &key, void *data) {
if (data()->get_building(key).id.begins_with(
prefix)) {
erased_keys.push_back(key);
}
},
nullptr);
print_verbose("delete buildings: " + itos(erased_keys.size()) +
" prefix: " + prefix);
for (i = erased_keys.size() - 1; i >= 0; i--) {
unload_building(erased_keys[i]);
data()->destroy_building(erased_keys[i]);
assert(!data()->has_building(erased_keys[i]));
}
update_items();
} else if (command == "remove_generated_stuff") {
remove_generated_stuff();
update_items();
} else if (command == "rebuild_roads") {
std::vector<String> erased_keys;
erased_keys.reserve(data()->get_building_count());
String prefix = "road__";
int i;
data()->for_each_building(
[&erased_keys, prefix](const String &key, void *data) {
if (key.begins_with(prefix)) {
erased_keys.push_back(key);
}
},
nullptr);
print_verbose("delete buildings: " + itos(erased_keys.size()) +
" prefix: " + prefix);
for (i = erased_keys.size() - 1; i >= 0; i--) {
unload_building(erased_keys[i]);
data()->destroy_building(erased_keys[i]);
assert(!data()->has_building(erased_keys[i]));
}
/* FIXME */
if (args.size() > 0)
RoadProcessing::road_setup(this, args[0]);
else
RoadProcessing::road_setup(this, 0);
create_tilemap();
update_view();
data()->scene_update();
update_items();
#if 0
data()->for_each_building(
[this](const String &key, void *mdata) {
print_line("Building key: " + key);
// FIXME do not load building every time
if (key.begins_with("road__"))
load_building(key);
},
nullptr);
#endif
print_verbose("road_rebuild done");
} else if (command == "remove_road_meshes") {
RoadProcessing::remove_road_meshes(this);
print_verbose("remove_road_meshes done");
} else if (command == "nudge_generator") {
auto gen = terrain->get_generator();
terrain->set_generator(Ref<VoxelGenerator>());
terrain->set_generator(gen);
update_items();
RoadProcessing::road_setup(this, 0);
create_tilemap();
update_view();
update_items();
assert(false);
} else
print_error("No command " + command);
}
void StreamWorld::_notification(int which)
{
switch (which) {
case NOTIFICATION_ENTER_WORLD:
break;
case NOTIFICATION_EXIT_WORLD:
break;
case NOTIFICATION_ENTER_TREE:
if (Engine::get_singleton()->is_editor_hint())
break;
if (initialized) {
if (Engine::get_singleton()->is_editor_hint())
current_scene = Object::cast_to<Node>(this);
else {
current_scene = get_tree()->get_current_scene();
if (current_scene)
current_scene = get_tree()->get_root();
}
ERR_FAIL_COND_MSG(!current_scene, "No current scene");
RoadProcessing::road_setup(this, 0);
#if 0
data()->for_each_building(
[this](const String &key, void *mdata) {
print_line("Building key: " + key);
// FIXME do not load building every time
if (key.begins_with("road__")) {
load_building(key);
assert(false);
}
},
nullptr);
assert(false);
#endif
create_tilemap();
set_process(true);
if (Engine::get_singleton()->is_editor_hint())
break;
update_view();
data()->scene_update();
update_items();
update_view();
assert(terrain);
assert(viewer);
}
break;
case NOTIFICATION_EXIT_TREE:
frame_count = 0;
break;
case NOTIFICATION_PROCESS: {
if (frame_count % 60 == 0) {
float fmon = Performance::get_singleton()->get_monitor(
Performance::RENDER_DRAW_CALLS_IN_FRAME);
print_verbose("Draw calls: " + String::num(fmon));
}
frame_count++;
if (Engine::get_singleton()->is_editor_hint())
break;
update_view();
data()->scene_update();
update_items();
} break;
}
}
void StreamWorld::_bind_methods()
{
ClassDB::bind_method(D_METHOD("terrain_dead"),
&StreamWorld::terrain_dead);
ClassDB::bind_method(D_METHOD("viewer_dead"),
&StreamWorld::viewer_dead);
ClassDB::bind_method(D_METHOD("run_command", "command", "args"),
&StreamWorld::run_command);
ADD_SIGNAL(MethodInfo("command_result",
PropertyInfo(Variant::STRING, "result_name"),
PropertyInfo(Variant::ARRAY, "args")));
}
StreamWorld::StreamWorld()
: Spatial()
, viewer(nullptr)
, terrain(nullptr)
, current_scene(nullptr)
, world_extent(0)
, tile_size(0)
, view_distance(0)
, initialized(false)
, frame_count(0)
{
Error result = config.load("res://config/stream.conf");
ERR_FAIL_COND_MSG(result != OK, "Failed to load config");
RoadProcessing::load_data();
world_extent = config.get_value("world", "world_extent");
tile_size = config.get_value("world", "tile_size");
ERR_FAIL_COND_MSG(tile_size <= 0 || world_extent <= 0 ||
world_extent <= tile_size,
"Failed to configure world");
create_tilemap();
tile_map_t::iterator map_it = tiles.begin();
while (map_it != tiles.end()) {
std::tuple<int, int> key = map_it->first;
std::vector<String> &tile_buildings = map_it->second;
print_verbose("x: " + itos(std::get<0>(key)) +
" y: " + itos(std::get<1>(key)) + " " +
itos(tile_buildings.size()));
map_it++;
}
view_distance = config.get_value("world", "view_distance");
NPC *npc = NPC::get_singleton();
if (!npc) {
assert(false);
}
initialized = true;
}
void StreamWorld::cleanup()
{
RoadProcessing::cleanup();
}
StreamWorld::~StreamWorld()
{
RoadProcessing::cleanup();
}
void StreamWorld::remove_generated_stuff()
{
std::vector<String> erased_keys;
erased_keys.reserve(data()->get_building_count());
int i;
data()->for_each_building(
[&erased_keys](const String &key, void *mdata) {
if (data()->get_building(key).generated)
erased_keys.push_back(key);
},
nullptr);
for (i = erased_keys.size() - 1; i >= 0; i--) {
const String &key = erased_keys[i];
unload_building(key);
data()->destroy_building(key);
assert(!data()->has_building(key));
}
update_items();
}
void StreamWorld::undo()
{
data()->for_each_building([this](const String &key,
void *mdata) { unload_building(key); },
nullptr);
data()->undo();
update_view();
update_items();
}