582 lines
16 KiB
C++
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();
|
|
}
|