1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-15 01:24:45 +02:00

First working version

This commit is contained in:
Tomasz Zieliński
2024-02-02 14:27:32 +01:00
parent 9f7c986621
commit 178f960533
3 changed files with 142 additions and 160 deletions

View File

@ -20,6 +20,7 @@
#include "RmgMap.h" #include "RmgMap.h"
#include "Zone.h" #include "Zone.h"
#include "Functions.h" #include "Functions.h"
#include "PenroseTiling.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -524,7 +525,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const
std::vector<float> prescaler = { 0, 0 }; std::vector<float> prescaler = { 0, 0 };
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
prescaler[i] = std::sqrt((width * height) / (totalSize[i] * 3.14f)); prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT));
mapSize = static_cast<float>(sqrt(width * height)); mapSize = static_cast<float>(sqrt(width * height));
for(const auto & zone : zones) for(const auto & zone : zones)
{ {
@ -802,6 +803,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
float CZonePlacer::metric (const int3 &A, const int3 &B) const float CZonePlacer::metric (const int3 &A, const int3 &B) const
{ {
/*
float dx = abs(A.x - B.x) * scaleX; float dx = abs(A.x - B.x) * scaleX;
float dy = abs(A.y - B.y) * scaleY; float dy = abs(A.y - B.y) * scaleY;
@ -811,9 +813,14 @@ float CZonePlacer::metric (const int3 &A, const int3 &B) const
3. Nonlinear mess for fuzzy edges 3. Nonlinear mess for fuzzy edges
*/ */
/*
return dx * dx + dy * dy + return dx * dx + dy * dy +
5 * std::sin(dx * dy / 10) + 5 * std::sin(dx * dy / 10) +
25 * std::sin (std::sqrt(A.x * B.x) * (A.y - B.y) / 100 * (scaleX * scaleY)); 25 * std::sin (std::sqrt(A.x * B.x) * (A.y - B.y) / 100 * (scaleX * scaleY));
*/
return A.dist2dSQ(B);
} }
void CZonePlacer::assignZones(CRandomGenerator * rand) void CZonePlacer::assignZones(CRandomGenerator * rand)
@ -845,7 +852,13 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize(); return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize();
}; };
auto moveZoneToCenterOfMass = [](const std::shared_ptr<Zone> & zone) -> void auto simpleCompareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool
{
//bigger zones have smaller distance
return lhs.second < rhs.second;
};
auto moveZoneToCenterOfMass = [width, height](const std::shared_ptr<Zone> & zone) -> void
{ {
int3 total(0, 0, 0); int3 total(0, 0, 0);
auto tiles = zone->area().getTiles(); auto tiles = zone->area().getTiles();
@ -855,17 +868,17 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
} }
int size = static_cast<int>(tiles.size()); int size = static_cast<int>(tiles.size());
assert(size); assert(size);
zone->setPos(int3(total.x / size, total.y / size, total.z / size)); auto newPos = int3(total.x / size, total.y / size, total.z / size);
zone->setPos(newPos);
zone->setCenter(float3(float(newPos.x) / width, float(newPos.y) / height, newPos.z));
}; };
int levels = map.levels(); int levels = map.levels();
/* // Find current center of mass for each zone. Move zone to that center to balance zones sizes
1. Create Voronoi diagram
2. find current center of mass for each zone. Move zone to that center to balance zones sizes
*/
int3 pos; int3 pos;
for(pos.z = 0; pos.z < levels; pos.z++) for(pos.z = 0; pos.z < levels; pos.z++)
{ {
for(pos.x = 0; pos.x < width; pos.x++) for(pos.x = 0; pos.x < width; pos.x++)
@ -890,14 +903,41 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
if(zone.second->area().empty()) if(zone.second->area().empty())
throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size"); throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size");
// FIXME: Is 2. correct and doesn't break balance?
moveZoneToCenterOfMass(zone.second); moveZoneToCenterOfMass(zone.second);
} }
//assign actual tiles to each zone using nonlinear norm for fine edges
for(const auto & zone : zones) for(const auto & zone : zones)
zone.second->clearTiles(); //now populate them again zone.second->clearTiles(); //now populate them again
// Assign zones to closest Penrose vertex
PenroseTiling penrose;
auto vertices = penrose.generatePenroseTiling(zones.size(), rand);
std::map<std::shared_ptr<Zone>, std::set<int3>> vertexMapping;
for (const auto & vertex : vertices)
{
distances.clear();
for(const auto & zone : zones)
{
distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), 0)));
}
auto closestZone = boost::min_element(distances, compareByDistance)->first;
vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, closestZone->getPos().z)); //Closest vertex belongs to zone
}
for (const auto & p : vertexMapping)
{
for (const auto vertex : p.second)
{
logGlobal->info("Zone %2d is assigned to vertex %s", p.first->getId(), vertex.toString());
}
logGlobal->info("Zone %2d has total of %d vertices", p.first->getId(), p.second.size());
}
//Assign actual tiles to each zone using nonlinear norm for fine edges
for (pos.z = 0; pos.z < levels; pos.z++) for (pos.z = 0; pos.z < levels; pos.z++)
{ {
for (pos.x = 0; pos.x < width; pos.x++) for (pos.x = 0; pos.x < width; pos.x++)
@ -905,22 +945,32 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
for (pos.y = 0; pos.y < height; pos.y++) for (pos.y = 0; pos.y < height; pos.y++)
{ {
distances.clear(); distances.clear();
for(const auto & zone : zones) for(const auto & zoneVertex : vertexMapping)
{ {
if (zone.second->getPos().z == pos.z) // FIXME: Find closest vertex, not closest zone
distances.emplace_back(zone.second, metric(pos, zone.second->getPos())); auto zone = zoneVertex.first;
else for (const auto & vertex : zoneVertex.second)
distances.emplace_back(zone.second, std::numeric_limits<float>::max()); {
if (zone->getCenter().z == pos.z)
distances.emplace_back(zone, metric(pos, vertex));
else
distances.emplace_back(zone, std::numeric_limits<float>::max());
}
} }
auto zone = boost::min_element(distances, compareByDistance)->first; //closest tile belongs to zone
zone->area().add(pos); auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; //closest tile belongs to zone
map.setZoneID(pos, zone->getId()); closestZone->area().add(pos);
map.setZoneID(pos, closestZone->getId());
} }
} }
} }
//set position (town position) to center of mass of irregular zone //set position (town position) to center of mass of irregular zone
for(const auto & zone : zones) for(const auto & zone : zones)
{ {
if(zone.second->area().empty())
throw rmgException("Empty zone after Penrose tiling");
moveZoneToCenterOfMass(zone.second); moveZoneToCenterOfMass(zone.second);
//TODO: similiar for islands //TODO: similiar for islands

View File

@ -1,28 +1,20 @@
/* /*
* © 2020 Michael Percival <m@michaelpercival.xyz> * PenroseTiling.cpp, part of VCMI engine
* See LICENSE file for copyright and license details. *
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/ */
// Adapted from https://github.com/mpizzzle/penrose // Adapted from https://github.com/mpizzzle/penrose by Michael Percival
//https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/geometry/reference/adapted/boost_polygon/point_data.html
//#include <GL/glew.h>
//#include <GLFW/glfw3.h>
//#include <glm/glm.hpp>
//#include <glm/gtx/rotate_vector.hpp>
#include "StdInc.h" #include "StdInc.h"
#include "PenroseTiling.h" #include "PenroseTiling.h"
//#include "shader.hpp"
//#include "png_writer.hpp"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
//static const std::string file_name = "penrose.png";
Point2D Point2D::operator * (float scale) const Point2D Point2D::operator * (float scale) const
{ {
@ -34,11 +26,35 @@ Point2D Point2D::operator + (const Point2D& other) const
return Point2D(x() + other.x(), y() + other.y()); return Point2D(x() + other.x(), y() + other.y());
} }
bool Point2D::operator < (const Point2D& other) const
{
if (x() < other.x())
{
return true;
}
else
{
return y() < other.y();
}
}
Triangle::Triangle(bool t_123, const TIndices & inds): Triangle::Triangle(bool t_123, const TIndices & inds):
tiling(t_123), tiling(t_123),
indices(inds) indices(inds)
{} {}
Triangle::~Triangle()
{
for (auto * triangle : subTriangles)
{
if (triangle)
{
delete triangle;
triangle = nullptr;
}
}
}
Point2D Point2D::rotated(float radians) const Point2D Point2D::rotated(float radians) const
{ {
float cosAngle = cos(radians); float cosAngle = cos(radians);
@ -51,21 +67,6 @@ Point2D Point2D::rotated(float radians) const
return Point2D(newX, newY); return Point2D(newX, newY);
} }
/*
Point2D rotatePoint(const Point2D& point, double radians, const Point2D& origin = Point2D(0, 0))
{
// Define a rotate_transformer: the first template argument `2` stands for 2D,
// `float` is the coordinate type, and 2 is input and output dimension
strategy::transform::rotate_transformer<boost::geometry::radian, float, 2, 2> rot(radians);
Point2D rotatedPoint;
rot.apply(point, rotatedPoint);
//transform(point, rotatedPoint, rot);
return rotatedPoint;
}
*/
void PenroseTiling::split(Triangle& p, std::vector<Point2D>& points, void PenroseTiling::split(Triangle& p, std::vector<Point2D>& points,
std::array<std::vector<uint32_t>, 5>& indices, uint32_t depth) std::array<std::vector<uint32_t>, 5>& indices, uint32_t depth)
{ {
@ -81,21 +82,20 @@ void PenroseTiling::split(Triangle& p, std::vector<Point2D>& points,
points.push_back(Point2D((points[i[0]] * (1.0f - PHI) ) + (points[i[2]]) * PHI)); points.push_back(Point2D((points[i[0]] * (1.0f - PHI) ) + (points[i[2]]) * PHI));
points.push_back(Point2D((points[i[p2]] * (1.0f - PHI)) + (points[i[!p2]] * PHI))); points.push_back(Point2D((points[i[p2]] * (1.0f - PHI)) + (points[i[!p2]] * PHI)));
Triangle t1(p2, TIndices({ i[(!p2) + 1], p2 ? i[2] : s, p2 ? s : i[1] })); auto * t1 = new Triangle(p2, TIndices({ i[(!p2) + 1], p2 ? i[2] : s, p2 ? s : i[1] }));
Triangle t2(true, TIndices({ p2 ? i[1] : s, s + 1, p2 ? s : i[1] })); auto * t2 = new Triangle(true, TIndices({ p2 ? i[1] : s, s + 1, p2 ? s : i[1] }));
Triangle t3(false, TIndices({ s, s + 1, i[0] })); auto * t3 = new Triangle(false, TIndices({ s, s + 1, i[0] }));
// FIXME: Make sure these are not destroyed when we leave the scope p.subTriangles = { t1, t2, t3 };
p.subTriangles = { &t1, &t2, &t3 };
} }
else else
{ {
points.push_back(Point2D((points[i[p2 * 2]] * (1.0f - PHI)) + (points[i[!p2]]) * PHI)); points.push_back(Point2D((points[i[p2 * 2]] * (1.0f - PHI)) + (points[i[!p2]]) * PHI));
Triangle t1(true, TIndices({ i[2], s, i[1] })); auto * t1 = new Triangle(true, TIndices({ i[2], s, i[1] }));
Triangle t2(false, TIndices({ i[(!p2) + 1], s, i[0] })); auto * t2 = new Triangle(false, TIndices({ i[(!p2) + 1], s, i[0] }));
p.subTriangles = { &t1, &t2 }; p.subTriangles = { t1, t2 };
} }
for (auto& t : p.subTriangles) for (auto& t : p.subTriangles)
@ -122,23 +122,14 @@ void PenroseTiling::split(Triangle& p, std::vector<Point2D>& points,
return; return;
} }
// TODO: Return something std::set<Point2D> PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * rand)
void PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * rand)
{ {
float scale = 100.f / (numZones + 20); //TODO: Use it to initialize the large tile float scale = 100.f / (numZones + 20);
float polyAngle = (2 * PI_CONSTANT) / POLY;
//static std::default_random_engine e(std::random_device{}()); float randomAngle = rand->nextDouble(0, 2 * PI_CONSTANT);
static std::uniform_real_distribution<> d(0, 1);
/* std::vector<Point2D> points = { Point2D(0.0f, 0.0f), Point2D(0.0f, 1.0f).rotated(randomAngle) };
std::vector<glm::vec3> colours = { glm::vec3(d(e), d(e), d(e)), glm::vec3(d(e), d(e), d(e)),
glm::vec3(d(e), d(e), d(e)), glm::vec3(d(e), d(e), d(e)),
glm::vec3(d(e), d(e), d(e)), glm::vec3(d(e), d(e), d(e)) };
*/
float polyAngle = 360.0f / POLY;
std::vector<Point2D> points = { Point2D(0.0f, 0.0f), Point2D(0.0f, 1.0f) };
std::array<std::vector<uint32_t>, 5> indices; std::array<std::vector<uint32_t>, 5> indices;
for (uint32_t i = 1; i < POLY; ++i) for (uint32_t i = 1; i < POLY; ++i)
@ -147,12 +138,21 @@ void PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * ra
points.push_back(next); points.push_back(next);
} }
// TODO: Scale to unit square
for (auto& p : points) for (auto& p : points)
{ {
p.x(p.x() * scale * BASE_SIZE); p.x(p.x() * scale * BASE_SIZE);
} }
// Scale square to window size
/*
for (auto& p : points)
{
p.x = (p.x / window_w) * window_h;
}
*/
std::set<Point2D> finalPoints;
for (uint32_t i = 0; i < POLY; i++) for (uint32_t i = 0; i < POLY; i++)
{ {
std::array<uint32_t, 2> p = { (i % (POLY + 1)) + 1, ((i + 1) % POLY) + 1 }; std::array<uint32_t, 2> p = { (i % (POLY + 1)) + 1, ((i + 1) % POLY) + 1 };
@ -162,93 +162,22 @@ void PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * ra
split(t, points, indices, DEPTH); split(t, points, indices, DEPTH);
} }
// TODO: Return collection of triangles
// TODO: Get center point of the triangle
// Do not draw anything
/* /*
if(!glfwInit()) //No difference for the number of points
for (auto& p : points)
{ {
return -1; p = p + Point2D(0.5f, 0.5f); // Center in a square (0,1)
} }
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(window_w, window_h, "penrose", NULL, NULL);
if(window == NULL) {
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glewExperimental=true;
if (glewInit() != GLEW_OK) {
return -1;
}
glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
uint32_t VAOs[5], VBO, EBOs[5];
glGenVertexArrays(5, VAOs);
glGenBuffers(1, &VBO);
glGenBuffers(5, EBOs);
glLineWidth(line_w);
for (uint32_t i = 0; i < indices.size(); ++i) {
glBindVertexArray(VAOs[i]);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, points.size() * 4 * 2, &points[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBOs[i]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices[i].size() * 4, &indices[i][0], GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
}
uint32_t programID = Shader::loadShaders("vertex.vert", "fragment.frag");
GLint paint = glGetUniformLocation(programID, "paint");
while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose(window) == 0 && paint != -1) {
glViewport(-1.0 * (window_w / scale) * ((0.5 * scale) - 0.5), -1.0 * (window_h / scale) * ((0.5 * scale) - 0.5), window_w, window_h);
glClearColor(colours.back().x, colours.back().y, colours.back().z, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(programID);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
for (uint32_t i = 0; i < indices.size(); ++i) {
glPolygonMode(GL_FRONT_AND_BACK, i < indices.size() - 1 ? GL_FILL : GL_LINE);
glUniform3fv(paint, 1, &colours[i][0]);
glBindVertexArray(VAOs[i]);
glDrawElements(i < indices.size() - 1 ? GL_TRIANGLES : GL_LINES, indices[i].size(), GL_UNSIGNED_INT, 0);
}
glfwSwapBuffers(window);
glfwPollEvents();
}
int frame_w, frame_h;
glfwGetFramebufferSize(window, &frame_w, &frame_h);
png_bytep* row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * frame_h);
for (int y = 0; y < frame_h; ++y) {
row_pointers[y] = (png_byte*) malloc((4 * sizeof(png_byte)) * frame_w);
glReadPixels(0, y, frame_w, 1, GL_RGBA, GL_UNSIGNED_BYTE, row_pointers[y]);
}
PngWriter::write_png_file(file_name, frame_w, frame_h, row_pointers);
return 0;
*/ */
vstd::copy_if(points, vstd::set_inserter(finalPoints), [](const Point2D point)
{
return vstd::isbetween(point.x(), 0.f, 1.0f) && vstd::isbetween(point.y(), 0.f, 1.0f);
});
logGlobal->info("Number of points within unit square: %d", finalPoints.size());
return finalPoints;
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -21,6 +21,8 @@ VCMI_LIB_NAMESPACE_BEGIN
using namespace boost::geometry; using namespace boost::geometry;
typedef std::array<uint32_t, 3> TIndices; typedef std::array<uint32_t, 3> TIndices;
const float PI_CONSTANT = 3.141592f;
class Point2D : public model::d2::point_xy<float> class Point2D : public model::d2::point_xy<float>
{ {
public: public:
@ -29,6 +31,8 @@ public:
Point2D operator * (float scale) const; Point2D operator * (float scale) const;
Point2D operator + (const Point2D& other) const; Point2D operator + (const Point2D& other) const;
Point2D rotated(float radians) const; Point2D rotated(float radians) const;
bool operator < (const Point2D& other) const;
}; };
Point2D rotatePoint(const Point2D& point, double radians, const Point2D& origin); Point2D rotatePoint(const Point2D& point, double radians, const Point2D& origin);
@ -36,6 +40,8 @@ Point2D rotatePoint(const Point2D& point, double radians, const Point2D& origin)
class Triangle class Triangle
{ {
public: public:
~Triangle();
const bool tiling; const bool tiling;
TIndices indices; TIndices indices;
@ -52,15 +58,12 @@ public:
// TODO: Is that the number of symmetries? // TODO: Is that the number of symmetries?
const uint32_t POLY = 10; const uint32_t POLY = 10;
const float BASE_SIZE = 4.0f; const float BASE_SIZE = 1.f;
//const uint32_t window_w = 1920 * scale; const uint32_t DEPTH = 7; //Recursion depth
//const uint32_t window_h = 1080 * scale;
const uint32_t DEPTH = 10; //recursion depth
const bool P2 = false; // Tiling type const bool P2 = false; // Tiling type
//const float line_w = 2.0f; //line width
void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); std::set<Point2D> generatePenroseTiling(size_t numZones, CRandomGenerator * rand);
private: private:
void split(Triangle& p, std::vector<Point2D>& points, std::array<std::vector<uint32_t>, 5>& indices, uint32_t depth); void split(Triangle& p, std::vector<Point2D>& points, std::array<std::vector<uint32_t>, 5>& indices, uint32_t depth);