Files
streaming_world/src/modules/stream/buildings_editor.cpp

568 lines
16 KiB
C++

#undef NDEBUG
#include <cassert>
#include <core/variant.h>
#include <core/os/time.h>
#include <scene/gui/option_button.h>
#include <scene/main/viewport.h>
#include <scene/3d/camera.h>
#include <modules/voxel/terrain/voxel_lod_terrain.h>
#include <modules/imgmapper/voxel_generator_imgmapper.h>
#include "from_string.h"
#include "editor_event.h"
#include "world_editor.h"
#include "buildings_data.h"
#include "buildings_editor.h"
class HandleChangeBuildingType : public Object {
GDCLASS(HandleChangeBuildingType, Object)
BuildingsEditor *editor;
void change_building_type(int index)
{
OptionButton *building_type =
editor->get_as_node<OptionButton>("%building_type");
const String &item = building_type->get_item_text(index);
int bmode = editor->get_buildings_editor_mode();
if (bmode == 0 || bmode == 1 ||
bmode == 2) /* select, move, rotate */
editor->change_building_type(item);
/* do not change building types in create mode (3) */
}
public:
HandleChangeBuildingType(BuildingsEditor *editor)
: Object()
, editor(editor)
{
OptionButton *building_type =
editor->get_as_node<OptionButton>("%building_type");
building_type->connect("item_selected", this,
"change_building_type");
}
virtual ~HandleChangeBuildingType()
{
OptionButton *building_type =
editor->get_as_node<OptionButton>("%building_type");
if (building_type->is_connected("item_selected", this,
"change_building_type"))
building_type->disconnect("item_selected", this,
"change_building_type");
}
protected:
static void _bind_methods()
{
ClassDB::bind_method(
D_METHOD("change_building_type", "index"),
&HandleChangeBuildingType::change_building_type);
}
};
class HandleButton : public Object {
GDCLASS(HandleButton, Object)
BuildingsEditor *editor;
String button_path;
String event_string;
Array event_args;
Button *get_button()
{
Button *button = editor->get_as_node<Button>(button_path);
assert(button);
return button;
}
void button_handler()
{
editor->emit("button:" + event_string, event_args);
}
public:
HandleButton(BuildingsEditor *editor, const String &button_path,
const String &event_string,
const Array &event_args = Array())
: Object()
, editor(editor)
, button_path(button_path)
, event_string(event_string)
, event_args(event_args)
{
if (!get_button()->is_connected("pressed", this,
"button_handler"))
get_button()->connect("pressed", this,
"button_handler");
}
virtual ~HandleButton()
{
if (get_button()->is_connected("pressed", this,
"button_handler"))
get_button()->disconnect("pressed", this,
"button_handler");
}
static void _bind_methods()
{
ClassDB::bind_method(D_METHOD("button_handler"),
&HandleButton::button_handler);
}
};
class HandleDeleteButton : public Object {
GDCLASS(HandleDeleteButton, Object)
BuildingsEditor *editor;
public:
HandleDeleteButton(BuildingsEditor *editor)
: Object()
, editor(editor)
{
Button *delete_button = editor->get_as_node<Button>(
"%buildings_delete_building");
assert(delete_button);
/* FIXME */
if (!delete_button->is_connected("pressed", this,
"delete_building_handler"))
delete_button->connect("pressed", this,
"delete_building_handler");
}
~HandleDeleteButton()
{
Button *delete_button = editor->get_as_node<Button>(
"%buildings_delete_building");
if (delete_button->is_connected("pressed", this,
"delete_building_handler"))
delete_button->disconnect("pressed", this,
"delete_building_handler");
}
void delete_building_handler()
{
editor->delete_building_handler();
}
static void _bind_methods()
{
ClassDB::bind_method(
D_METHOD("delete_building_handler"),
&HandleDeleteButton::delete_building_handler);
}
};
static std::vector<Object *> ui_handlers;
BuildingsEditor::BuildingsEditor(WorldEditor *editor)
: editor(editor)
, selected_building(-1)
, active(false)
{
}
void BuildingsEditor::exit()
{
if (active)
deactivate();
}
void BuildingsEditor::activate()
{
assert(!active);
ConfigFile config;
Error result = config.load("res://config/stream.conf");
ERR_FAIL_COND_MSG(result != OK, "Failed to load config");
print_line("BuildingsEditor ACTIVE");
Array args;
ui_handlers.push_back(memnew(HandleButton(
this, "%buildings_create_building", "create_building", args)));
ui_handlers.push_back(memnew(HandleChangeBuildingType(this)));
ui_handlers.push_back(memnew(HandleDeleteButton(this)));
int i;
for (i = 0; i < (int)ui_handlers.size(); i++)
assert(ui_handlers[i]);
/* TODO: make separate function for this */
EditorEvent::get_singleton()->event.add_listener(
this, &BuildingsEditor::event_handler);
Dictionary buildings_data =
config.get_value("buildings", "building_data");
OptionButton *building_type =
get_as_node<OptionButton>("%building_type");
building_type->clear();
List<Variant> keys;
buildings_data.get_key_list(&keys);
List<Variant>::Element *e = keys.front();
while (e) {
const String &key = e->get();
print_line("EE::" + key);
building_type->add_item(key);
e = e->next();
}
active = true;
}
void BuildingsEditor::deactivate()
{
assert(active);
EditorEvent::get_singleton()->event.remove_listener(
this, &BuildingsEditor::event_handler);
print_line("BuildingsEditor DEACTIVE");
int i;
for (i = 0; i < (int)ui_handlers.size(); i++)
memdelete(ui_handlers[i]);
ui_handlers.clear();
active = false;
}
void BuildingsEditor::event_handler(const String &event, const Array &args)
{
assert(active);
print_line("E::" + event);
if (event == "mouse_drag")
mouse_drag(args[0]);
else if (event == "mouse_press")
mouse_press(args[0]);
else if (event == "button:create_building")
handle_create_building();
else if (event == "result:get_closest_building") {
select_building(args[0], args[3], args[4]);
if (get_buildings_editor_mode() == 2 /* rotate */) {
/* move rot cursor too */
get_as_node<Spatial>("%building_rot_cursor")
->set_global_transform(selected_building_xform);
}
} else if (event == "result:get_building_types") {
/* TODO: replace with direct data access */
}
print_line("buildings::" + event);
}
void BuildingsEditor::handle_create_building()
{
print_line("create_building");
int bmode = get_buildings_editor_mode();
assert(bmode == 3);
OptionButton *building_type =
get_as_node<OptionButton>("%building_type");
int index = building_type->get_selected();
if (index >= 0) {
const String &item = building_type->get_item_text(index);
Array args;
Transform xform = get_as_node<Spatial>("%building_cursor")
->get_global_transform();
Dictionary building_data;
/* FIXME: calculate AABBs as in previous editor */
String building_xform = to_string<Transform>(xform);
building_data["id"] = item;
building_data["key"] = String::num_uint64(
String(item + building_xform +
itos(Time::get_singleton()->get_ticks_usec()))
.hash64(),
16);
building_data["xform"] = building_xform;
args.push_back(building_data);
editor->editor_command("create_building", args);
}
}
template <class T>
inline void BuildingsEditor::mode_visibility(int mode, const String &path)
{
T *obj = get_as_node<T>(path);
if (get_buildings_editor_mode() == mode) {
if (!obj->is_visible())
obj->show();
} else {
if (obj->is_visible())
obj->hide();
}
}
void BuildingsEditor::update(float delta)
{
#ifdef _VERBOSE_DEBUG
if (active)
print_line("update: " + String::num(delta) + " " +
itos(editor->get_current_mode()) + " " +
itos(editor->get_camera_mode()));
#endif
if (!active)
activate();
int mode = get_buildings_editor_mode();
if (mode == 2) {
Spatial *rot_cursor =
get_as_node<Spatial>("%building_rot_cursor");
if (!rot_cursor->is_visible())
rot_cursor->set_global_transform(
selected_building_xform);
}
mode_visibility<Button>(0, "%buildings_delete_building");
mode_visibility<Spatial>(2, "%building_rot_cursor");
mode_visibility<Button>(3, "%buildings_create_building");
}
void BuildingsEditor::mouse_drag(const Vector2 &position)
{
if (editor->get_current_mode() != WorldEditor::MODE_BUILDINGS) {
print_verbose("bad editor mode: " +
itos(editor->get_current_mode()));
return;
}
print_line("in mouse_drag");
Camera *camera = editor->get_viewport()->get_camera();
Vector3 start = camera->project_ray_origin(position);
Vector3 normal = camera->project_ray_normal(position);
Vector3 end = start + normal * camera->get_zfar();
PhysicsDirectSpaceState *space_state =
editor->get_world()->get_direct_space_state();
PhysicsDirectSpaceState::RayResult result;
Set<RID> excludes;
bool r = space_state->intersect_ray(start, end, result, excludes,
1 << 15, false, true, false);
if (r && result.rid != RID()) {
Vector3 proj = result.position;
int mode = get_buildings_editor_mode();
switch (mode) {
case 1: {
/* move */
print_verbose("move: " + (proj.operator String()));
Vector3 newpos(Math::stepify(proj.x, 2.0f), proj.y,
Math::stepify(proj.z, 2.0f));
Ref<VoxelGeneratorImgMapper> gen =
get_as_node<VoxelLodTerrain>("%VoxelLodTerrain")
->get_generator();
float h = gen->get_height_full(newpos);
newpos.y = h;
Array args;
args.push_back(selected_building);
args.push_back(Transform(selected_building_xform.basis,
newpos));
editor->editor_command("update_building_transform",
args);
Spatial *cursor =
get_as_node<Spatial>("%building_cursor");
Transform orig_pos = cursor->get_global_transform();
orig_pos.origin = newpos;
cursor->set_global_transform(orig_pos);
selected_building_xform = Transform(
selected_building_xform.basis, newpos);
} break;
case 2: {
/* rotate */
print_verbose("rotate: " + (proj.operator String()));
Vector3 m = proj;
m.y = selected_building_xform.origin.y;
Transform xform = selected_building_xform.looking_at(
m, Vector3(0.0f, 1.0f, 0.0f));
Array args;
args.push_back(selected_building);
args.push_back(xform);
editor->editor_command("update_building_transform",
args);
selected_building_xform = xform;
get_as_node<Spatial>("%building_rot_cursor")
->set_global_transform(xform);
} break;
case 3: { /* create */
print_verbose("create: " + (proj.operator String()));
Vector3 newpos(Math::stepify(proj.x, 2.0f), proj.y,
Math::stepify(proj.z, 2.0f));
Ref<VoxelGeneratorImgMapper> gen =
get_as_node<VoxelLodTerrain>("%VoxelLodTerrain")
->get_generator();
float h = gen->get_height_full(newpos);
newpos.y = h;
Transform xform(Basis(), newpos);
get_as_node<Spatial>("%building_cursor")
->set_global_transform(xform);
} break;
}
}
}
void BuildingsEditor::mouse_press(const Vector2 &position)
{
if (editor->get_current_mode() != WorldEditor::MODE_BUILDINGS) {
print_verbose("bad editor mode: " +
itos(editor->get_current_mode()));
return;
}
print_line("in mouse_press");
Camera *camera = editor->get_viewport()->get_camera();
Vector3 start = camera->project_ray_origin(position);
Vector3 normal = camera->project_ray_normal(position);
Vector3 end = start + normal * camera->get_zfar();
PhysicsDirectSpaceState *space_state =
editor->get_world()->get_direct_space_state();
PhysicsDirectSpaceState::RayResult result;
Set<RID> excludes;
bool r = space_state->intersect_ray(start, end, result, excludes,
1 << 15, false, true, false);
if (r && result.rid != RID()) {
Vector3 proj = result.position;
int mode = get_buildings_editor_mode();
switch (mode) {
case 0: {
/* select */
Array args;
Transform xform(Basis(), proj);
args.push_back(xform);
editor->editor_command("get_closest_building", args);
} break;
case 1: {
/* move */
editor->editor_command("checkpoint", Array());
} break;
case 2: {
/* TODO: deduplicate */
/* rotate */
editor->editor_command("checkpoint", Array());
print_verbose("rotate: " + (proj.operator String()));
Vector3 m = proj;
m.y = selected_building_xform.origin.y;
Transform xform = selected_building_xform.looking_at(
m, Vector3(0.0f, 1.0f, 0.0f));
Array args;
args.push_back(selected_building);
args.push_back(xform);
editor->editor_command("update_building_transform",
args);
selected_building_xform = xform;
get_as_node<Spatial>("%building_rot_cursor")
->set_global_transform(xform);
} break;
/* TODO: deduplicate */
case 3: { /* create */
print_verbose("create: " + (proj.operator String()));
Vector3 newpos(Math::stepify(proj.x, 2.0f), proj.y,
Math::stepify(proj.z, 2.0f));
Ref<VoxelGeneratorImgMapper> gen =
get_as_node<VoxelLodTerrain>("%VoxelLodTerrain")
->get_generator();
float h = gen->get_height_full(newpos);
newpos.y = h;
Transform xform(Basis(), newpos);
get_as_node<Spatial>("%building_cursor")->show();
get_as_node<Spatial>("%building_cursor")
->set_global_transform(xform);
} break;
}
}
}
int BuildingsEditor::get_buildings_editor_mode() const
{
const OptionButton *buildings_edit_mode =
get_as_node<OptionButton>("%buildings_edit_mode");
int selected = buildings_edit_mode->get_selected();
return selected;
}
BuildingsEditor::~BuildingsEditor()
{
if (active)
deactivate();
}
int BuildingsEditor::get_selected_building() const
{
return selected_building;
}
Transform BuildingsEditor::get_selected_building_xform() const
{
return selected_building_xform;
}
void BuildingsEditor::delete_building_handler()
{
Array args, args2;
args.push_back(selected_building);
editor->editor_command("remove_building", args);
args2.push_back(selected_building_xform);
editor->editor_command("get_closest_building", args2);
}
void BuildingsEditor::change_building_type(const String &type_name)
{
Array args;
int bmode = get_buildings_editor_mode();
assert(bmode == 0 || bmode == 1 ||
bmode == 2); /* select, move, rotate */
args.push_back(selected_building);
args.push_back(type_name);
editor->editor_command("change_building_type", args);
}
void BuildingsEditor::emit(const String &event_string, const Array &event_args)
{
EditorEvent::get_singleton()->event.emit(event_string, event_args);
}
void BuildingsEditor::remove_buildings_by_prefix(const String &prefix)
{
Array args;
args.push_back(prefix);
editor->editor_command("remove_buildings_by_prefix", args);
}
void BuildingsEditor::select_building(const Transform &xform, int id,
const String &mid)
{
int i;
selected_building_xform = xform;
selected_building = id;
print_line("selected id: " + itos(id));
OptionButton *building_type = Object::cast_to<OptionButton>(
editor->get_node(NodePath("%building_type")));
assert(building_type);
bool ok = false;
for (i = 0; i < building_type->get_item_count(); i++) {
const String &item = building_type->get_item_text(i);
if (item == mid) {
building_type->select(i);
ok = true;
break;
}
}
if (ok) {
Button *delete_button =
get_as_node<Button>("%buildings_delete_building");
delete_button->show();
}
Spatial *cursor = get_as_node<Spatial>("%building_cursor");
/* Show cursor and change cursor position */
if (!cursor->is_visible())
cursor->show();
Transform orig_xform = cursor->get_global_transform();
orig_xform.origin = xform.origin;
cursor->set_global_transform(orig_xform);
}
void BuildingsEditor::editor_command(const String &command, const Array &args)
{
if (command == "select_building") {
select_building(args[0], args[1], args[2]);
}
}
template <class T> T *BuildingsEditor::get_as_node(const String &path)
{
Node *node = scene()->get_node(NodePath(path));
assert(node);
T *ret = Object::cast_to<T>(node);
assert(ret);
return ret;
}
template <class T>
const T *BuildingsEditor::get_as_node(const String &path) const
{
const Node *node = scene()->get_node(NodePath(path));
assert(node);
const T *ret = Object::cast_to<T>(node);
assert(ret);
return ret;
}
Node *BuildingsEditor::scene()
{
return editor->get_tree()->get_current_scene();
}
const Node *BuildingsEditor::scene() const
{
const Node *ret = editor->get_tree()->get_current_scene();
return ret;
}