214 lines
5.1 KiB
C++
214 lines
5.1 KiB
C++
#include <cmath>
|
|
#include <core/image.h>
|
|
#include <core/math/random_number_generator.h>
|
|
#include "world_map_data.h"
|
|
|
|
static WorldMapData *g_world_map_data = NULL;
|
|
WorldMapData *WorldMapData::get_singleton()
|
|
{
|
|
return g_world_map_data;
|
|
}
|
|
void WorldMapData::create_singleton()
|
|
{
|
|
g_world_map_data = memnew(WorldMapData);
|
|
}
|
|
void WorldMapData::destroy_singleton()
|
|
{
|
|
memdelete(g_world_map_data);
|
|
g_world_map_data = NULL;
|
|
}
|
|
void WorldMapData::_bind_methods()
|
|
{
|
|
}
|
|
void WorldMapData::add_circle(int x, int y, float radius)
|
|
{
|
|
struct area a;
|
|
int grid_x_min, grid_x_max, grid_x, grid_y_min, grid_y_max, grid_y;
|
|
int i;
|
|
Vector2i grid_pos;
|
|
a.x = x;
|
|
a.y = y;
|
|
a.radius = radius;
|
|
if (last_area >= areas.size())
|
|
areas.resize(areas.size() + alloc_count);
|
|
areas.write[last_area] = a;
|
|
grid_x_min = floorf(((float)x - radius) / (float)grid_size);
|
|
grid_x_max = ceilf(((float)x + radius) / (float)grid_size);
|
|
grid_y_min = floorf(((float)y - radius) / (float)grid_size);
|
|
grid_y_max = ceilf(((float)y + radius) / (float)grid_size);
|
|
for (grid_y = grid_y_min; grid_y < grid_y_max + 1; grid_y++)
|
|
for (grid_x = grid_x_min; grid_x < grid_x_max + 1; grid_x++) {
|
|
grid_pos.x = grid_x;
|
|
grid_pos.y = grid_y;
|
|
if (!grid.has(grid_pos))
|
|
grid[grid_pos] = List<int>();
|
|
if (grid[grid_pos].find(last_area) == NULL) {
|
|
int oid = get_sorted_index(grid[grid_pos], radius);
|
|
int xsize = grid[grid_pos].size();
|
|
if (oid == 0)
|
|
grid[grid_pos].push_front(last_area);
|
|
else if (oid >= xsize)
|
|
grid[grid_pos].push_back(last_area);
|
|
else {
|
|
i = 0;
|
|
List<int>::Element *e = grid[grid_pos].front();
|
|
while (i != oid && e) {
|
|
e = e->next();
|
|
i++;
|
|
}
|
|
if (e)
|
|
grid[grid_pos].insert_before(e, last_area);
|
|
}
|
|
}
|
|
}
|
|
last_area++;
|
|
}
|
|
|
|
List<int> WorldMapData::get_circles_for_pos(int x, int y)
|
|
{
|
|
Vector2i grid_pos;
|
|
grid_pos.x = x / grid_size;
|
|
grid_pos.y = y / grid_size;
|
|
if (grid.has(grid_pos))
|
|
return grid[grid_pos];
|
|
else
|
|
return List<int>();
|
|
}
|
|
|
|
bool WorldMapData::in_circle(int id, int x, int y)
|
|
{
|
|
if (id >= areas.size())
|
|
return false;
|
|
struct area a = areas[id];
|
|
int xx = x - a.x;
|
|
int yy = y - a.y;
|
|
int rsq = xx * xx + yy * yy;
|
|
if ((float)rsq < a.radius * a.radius)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
int WorldMapData::get_sorted_index(const List<int> &items, float radius) const
|
|
{
|
|
int low = 0;
|
|
int high = items.size();
|
|
while (low < high) {
|
|
int mid = (low + high) >> 1;
|
|
if (areas[items[mid]].radius < radius)
|
|
low = mid + 1;
|
|
else
|
|
high = mid;
|
|
}
|
|
return low;
|
|
}
|
|
|
|
void WorldMapData::clear()
|
|
{
|
|
last_area = 0;
|
|
alloc_count = 1000;
|
|
areas.resize(alloc_count);
|
|
grid.clear();
|
|
}
|
|
|
|
WorldMapData::WorldMapData()
|
|
{
|
|
grid_size = 100;
|
|
last_area = 0;
|
|
alloc_count = 1000;
|
|
areas.resize(alloc_count);
|
|
#ifdef WORLD_MAP_TESTS
|
|
tests_run = false;
|
|
#endif
|
|
world_x = 2048;
|
|
world_y = 2048;
|
|
seed = 1038;
|
|
}
|
|
WorldMapData::~WorldMapData()
|
|
{
|
|
}
|
|
void WorldMapData::build_point_clusters_iteration(Vector2i seed_point, int bound, int count)
|
|
{
|
|
Ref<RandomNumberGenerator> rnd;
|
|
rnd.instance();
|
|
rnd->set_seed(seed);
|
|
int mind = MIN(bound / count / 4, 1);
|
|
int rmax = bound / mind;
|
|
if (rmax == 0)
|
|
return;
|
|
float radius = (float)bound / (float)count;
|
|
float radiussq = radius * radius;
|
|
List<Vector2i> points;
|
|
int n = 0;
|
|
int max_steps = 10000;
|
|
while (n < count && max_steps-- > 0) {
|
|
int x = seed_point.x + rnd->randi() % rmax;
|
|
int y = seed_point.y + rnd->randi() % rmax;
|
|
if (x >= world_x || y >= world_y)
|
|
continue;
|
|
bool good = true;
|
|
List<Vector2i>::Element *e = points.front();
|
|
while (e) {
|
|
Vector2i p = e->get();
|
|
int dx = x - p.x;
|
|
int dy = y - p.y;
|
|
if (dx * dx + dy * dy < (int)radiussq) {
|
|
good = false;
|
|
break;
|
|
}
|
|
e = e->next();
|
|
}
|
|
if (good) {
|
|
add_circle(x, y, radius);
|
|
points.push_back(Vector2i(x, y));
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
void WorldMapData::save_debug_image()
|
|
{
|
|
int i, j;
|
|
Ref<Image> image;
|
|
HashMap<int, Color> colors;
|
|
image.instance();
|
|
image->create(2048, 2048, false, Image::FORMAT_RGB8);
|
|
image->lock();
|
|
Color newcolor(0.1f, 0.2f, 1.0f);
|
|
printf("world %d %d\n", world_x, world_y);
|
|
for (i = 0; i < image->get_width(); i++)
|
|
for (j = 0; j < image->get_height(); j++) {
|
|
List<int> data = get_circles_for_pos(i * world_x / 2048, j * world_y / 2048);
|
|
while (data.size() > 0) {
|
|
int id = data.front()->get();
|
|
if (in_circle(id, i * world_x / 2048, j * world_y / 2048)) {
|
|
if (!colors.has(id)) {
|
|
colors[id] = newcolor;
|
|
newcolor.r = (newcolor.r + fmodf(1321.27f * (float)i, 13.0f) / 13.0f) / 1.9f + 0.1f;
|
|
newcolor.g = (newcolor.r + newcolor.g + fmodf(5321.23f * (float)i, 10.0f) / 10.0f) / 2.9f + 0.1f;
|
|
newcolor.b = (newcolor.r + newcolor.g + newcolor.b + fmodf(1121.13f * (float)i, 11.0f) / 11.0f) / 3.9f + 0.1f;
|
|
}
|
|
image->set_pixel(i, j, colors[id]);
|
|
break;
|
|
}
|
|
data.pop_front();
|
|
}
|
|
}
|
|
image->unlock();
|
|
image->save_png("world_map_test.png");
|
|
}
|
|
#ifdef WORLD_MAP_TESTS
|
|
void WorldMapData::unit_test()
|
|
{
|
|
int i;
|
|
add_circle(100, 100, 50.0f);
|
|
add_circle(200, 200, 100.0f);
|
|
add_circle(100, 300, 30.0f);
|
|
add_circle(300, 100, 30.0f);
|
|
for (i = 0; i < 1000; i++) {
|
|
int x = (i * 100) % 1000;
|
|
int y = (i * 100) / 1000;
|
|
add_circle(x, y, 90.0f);
|
|
}
|
|
save_debug_image();
|
|
}
|
|
#endif
|