From c5aa31f46ac6580c74d3e2340fab7c8f4009c083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Tue, 30 Jan 2024 18:04:15 +0100 Subject: [PATCH 1/4] First sketch --- cmake_modules/VCMI_lib.cmake | 2 + lib/rmg/PenroseTiling.cpp | 227 +++++++++++++++++++++++++++++++++++ lib/rmg/PenroseTiling.h | 57 +++++++++ 3 files changed, 286 insertions(+) create mode 100644 lib/rmg/PenroseTiling.cpp create mode 100644 lib/rmg/PenroseTiling.h diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 39b8044f5..62218f98b 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -151,6 +151,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/rmg/Zone.cpp ${MAIN_LIB_DIR}/rmg/Functions.cpp ${MAIN_LIB_DIR}/rmg/RmgMap.cpp + ${MAIN_LIB_DIR}/rmg/PenroseTiling.cpp ${MAIN_LIB_DIR}/rmg/modificators/Modificator.cpp ${MAIN_LIB_DIR}/rmg/modificators/ObjectManager.cpp ${MAIN_LIB_DIR}/rmg/modificators/ObjectDistributor.cpp @@ -522,6 +523,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/rmg/RmgMap.h ${MAIN_LIB_DIR}/rmg/float3.h ${MAIN_LIB_DIR}/rmg/Functions.h + ${MAIN_LIB_DIR}/rmg/PenroseTiling.h ${MAIN_LIB_DIR}/rmg/modificators/Modificator.h ${MAIN_LIB_DIR}/rmg/modificators/ObjectManager.h ${MAIN_LIB_DIR}/rmg/modificators/ObjectDistributor.h diff --git a/lib/rmg/PenroseTiling.cpp b/lib/rmg/PenroseTiling.cpp new file mode 100644 index 000000000..2af1d7b71 --- /dev/null +++ b/lib/rmg/PenroseTiling.cpp @@ -0,0 +1,227 @@ +/* + * © 2020 Michael Percival + * See LICENSE file for copyright and license details. + */ + +// Adapted from https://github.com/mpizzzle/penrose + +// FIXME: Find library for geometry representation +//https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/geometry/reference/adapted/boost_polygon/point_data.html + +//#include +//#include + +//#include +//#include + +#include "StdInc.h" + +#include +#include +#include +#include + +#include +//https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/geometry/reference/strategies/strategy_transform_rotate_transformer.html + +//#include "shader.hpp" +//#include "png_writer.hpp" + +#include "PenroseTiling.h" + +VCMI_LIB_NAMESPACE_BEGIN + +//static const std::string file_name = "penrose.png"; + +Triangle::Triangle(bool t_123, const TIndices & inds): + tiling(t_123), + indices(inds) +{} + +void PenroseTiling::split(Triangle& p, std::vector& points, + std::array, 5>& indices, uint32_t depth) +{ + uint32_t s = points.size(); + TIndices& i = p.indices; + + const auto p2 = P2; + + if (depth > 0) + { + if (p.tiling ^ !p2) + { + points.push_back(glm::vec2(((1.0f - PHI) * points[i[0]]) + (PHI * points[i[2]]))); + points.push_back(glm::vec2(((1.0f - PHI) * points[i[p2]]) + (PHI * points[i[!p2]]))); + + Triangle t1(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] })); + Triangle t3(false, TIndices({ s, s + 1, i[0] })); + + // FIXME: Make sure these are not destroyed when we leave the scope + p.subTriangles = { &t1, &t2, &t3 }; + } + else + { + points.push_back(glm::vec2(((1.0f - PHI) * points[i[p2 * 2]]) + (PHI * points[i[!p2]]))); + + Triangle t1(true, TIndices({ i[2], s, i[1] })); + Triangle t2(false, TIndices({ i[(!p2) + 1], s, i[0] })); + + p.subTriangles = { &t1, &t2 }; + } + + for (auto& t : p.subTriangles) + { + if (depth == 1) + { + for (uint32_t k = 0; k < 3; ++k) + { + if (k != (t->tiling ^ !p2 ? 2 : 1)) + { + indices[indices.size() - 1].push_back(t->indices[k]); + indices[indices.size() - 1].push_back(t->indices[((k + 1) % 3)]); + } + } + + indices[t->tiling + (p.tiling ? 0 : 2)].insert(indices[t->tiling + (p.tiling ? 0 : 2)].end(), t->indices.begin(), t->indices.end()); + } + + // Split recursively + split(*t, points, indices, depth - 1); + } + } + + return; +} + +// TODO: Return something +void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); +{ + float scale = 100.f / (numZones + 20); //TODO: Use it to initialize the large tile + + //static std::default_random_engine e(std::random_device{}()); + static std::uniform_real_distribution<> d(0, 1); + + /* + std::vector 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 = glm::radians(360.0f / POLY); + + std::vector points = { glm::vec2(0.0f, 0.0f), glm::vec2(0.0f, 1.0f) }; + std::array, 5> indices; + + for (uint32_t i = 1; i < POLY; ++i) + { + //TODO: Use boost to rotate + glm::vec2 next = glm::rotate(points[i], polyAngle); + points.push_back(next); + } + + // TODO: Scale to unit square + for (auto& p : points) + { + p.x = (p.x / window_w) * window_h; + } + + for (uint32_t i = 0; i < POLY; i++) + { + std::array p = { (i % (POLY + 1)) + 1, ((i + 1) % POLY) + 1 }; + + triangle t(true, TIndices({ 0, p[i & 1], p[!(i & 1)] })); + + split(t, points, indices, DEPTH); + } + + // TODO: Return collection of triangles + // TODO: Get center point of the triangle + + // Do not draw anything + /* + if(!glfwInit()) + { + return -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; + */ +} + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/PenroseTiling.h b/lib/rmg/PenroseTiling.h new file mode 100644 index 000000000..cc3060a8a --- /dev/null +++ b/lib/rmg/PenroseTiling.h @@ -0,0 +1,57 @@ +/* + * PenroseTiling.h, part of VCMI engine + * + * 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 + * + */ + +#pragma once + +#include "../GameConstants.h" +#include "../CRandomGenerator.h" +#include +#include + + +VCMI_LIB_NAMESPACE_BEGIN + +typedef std::array TIndices; + +class Triangle +{ +public: + const bool tiling; + TIndices indices; + + std::vector subTriangles; + + Triangle(bool t_123, const TIndices & inds); +}; + +class PenroseTiling +{ + +public: + const float PHI = 1.0 / ((1.0 + std::sqrt(5.0)) / 2); + // TODO: Is that the number of symmetries? + const uint32_t POLY = 10; + + const float scale = 4.0f; + //const uint32_t window_w = 1920 * scale; + //const uint32_t window_h = 1080 * scale; + const uint32_t DEPTH = 10; //recursion depth + + const bool P2 = false; // Tiling type + //const float line_w = 2.0f; //line width + + void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); + +private: + void split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth); + +}; + +VCMI_LIB_NAMESPACE_END \ No newline at end of file From 9f7c986621a4174e4c097a8e9e50b685a8bf3686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 2 Feb 2024 10:37:08 +0100 Subject: [PATCH 2/4] First version that compiles successfully --- lib/rmg/PenroseTiling.cpp | 69 +++++++++++++++++++++++++++------------ lib/rmg/PenroseTiling.h | 19 +++++++++-- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/lib/rmg/PenroseTiling.cpp b/lib/rmg/PenroseTiling.cpp index 2af1d7b71..70d6696ee 100644 --- a/lib/rmg/PenroseTiling.cpp +++ b/lib/rmg/PenroseTiling.cpp @@ -5,7 +5,6 @@ // Adapted from https://github.com/mpizzzle/penrose -// FIXME: Find library for geometry representation //https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/geometry/reference/adapted/boost_polygon/point_data.html //#include @@ -15,30 +14,59 @@ //#include #include "StdInc.h" +#include "PenroseTiling.h" -#include -#include -#include -#include - -#include -//https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/geometry/reference/strategies/strategy_transform_rotate_transformer.html //#include "shader.hpp" //#include "png_writer.hpp" -#include "PenroseTiling.h" - VCMI_LIB_NAMESPACE_BEGIN //static const std::string file_name = "penrose.png"; +Point2D Point2D::operator * (float scale) const +{ + return Point2D(x() * scale, y() * scale); +} + +Point2D Point2D::operator + (const Point2D& other) const +{ + return Point2D(x() + other.x(), y() + other.y()); +} + Triangle::Triangle(bool t_123, const TIndices & inds): tiling(t_123), indices(inds) {} -void PenroseTiling::split(Triangle& p, std::vector& points, +Point2D Point2D::rotated(float radians) const +{ + float cosAngle = cos(radians); + float sinAngle = sin(radians); + + // Apply rotation matrix transformation + float newX = x() * cosAngle - y() * sinAngle; + float newY = x() * sinAngle + y() * cosAngle; + + 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 rot(radians); + + Point2D rotatedPoint; + rot.apply(point, rotatedPoint); + //transform(point, rotatedPoint, rot); + + return rotatedPoint; +} +*/ + +void PenroseTiling::split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth) { uint32_t s = points.size(); @@ -50,8 +78,8 @@ void PenroseTiling::split(Triangle& p, std::vector& points, { if (p.tiling ^ !p2) { - points.push_back(glm::vec2(((1.0f - PHI) * points[i[0]]) + (PHI * points[i[2]]))); - points.push_back(glm::vec2(((1.0f - PHI) * points[i[p2]]) + (PHI * points[i[!p2]]))); + 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))); Triangle t1(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] })); @@ -62,7 +90,7 @@ void PenroseTiling::split(Triangle& p, std::vector& points, } else { - points.push_back(glm::vec2(((1.0f - PHI) * points[i[p2 * 2]]) + (PHI * points[i[!p2]]))); + points.push_back(Point2D((points[i[p2 * 2]] * (1.0f - PHI)) + (points[i[!p2]]) * PHI)); Triangle t1(true, TIndices({ i[2], s, i[1] })); Triangle t2(false, TIndices({ i[(!p2) + 1], s, i[0] })); @@ -95,7 +123,7 @@ void PenroseTiling::split(Triangle& p, std::vector& points, } // TODO: Return something -void 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 @@ -108,29 +136,28 @@ void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); glm::vec3(d(e), d(e), d(e)), glm::vec3(d(e), d(e), d(e)) }; */ - float polyAngle = glm::radians(360.0f / POLY); + float polyAngle = 360.0f / POLY; - std::vector points = { glm::vec2(0.0f, 0.0f), glm::vec2(0.0f, 1.0f) }; + std::vector points = { Point2D(0.0f, 0.0f), Point2D(0.0f, 1.0f) }; std::array, 5> indices; for (uint32_t i = 1; i < POLY; ++i) { - //TODO: Use boost to rotate - glm::vec2 next = glm::rotate(points[i], polyAngle); + Point2D next = points[i].rotated(polyAngle); points.push_back(next); } // TODO: Scale to unit square for (auto& p : points) { - p.x = (p.x / window_w) * window_h; + p.x(p.x() * scale * BASE_SIZE); } for (uint32_t i = 0; i < POLY; i++) { std::array p = { (i % (POLY + 1)) + 1, ((i + 1) % POLY) + 1 }; - triangle t(true, TIndices({ 0, p[i & 1], p[!(i & 1)] })); + Triangle t(true, TIndices({ 0, p[i & 1], p[!(i & 1)] })); split(t, points, indices, DEPTH); } diff --git a/lib/rmg/PenroseTiling.h b/lib/rmg/PenroseTiling.h index cc3060a8a..708b425ad 100644 --- a/lib/rmg/PenroseTiling.h +++ b/lib/rmg/PenroseTiling.h @@ -14,12 +14,25 @@ #include "../CRandomGenerator.h" #include #include - +#include VCMI_LIB_NAMESPACE_BEGIN +using namespace boost::geometry; typedef std::array TIndices; +class Point2D : public model::d2::point_xy +{ +public: + using point_xy::point_xy; + + Point2D operator * (float scale) const; + Point2D operator + (const Point2D& other) const; + Point2D rotated(float radians) const; +}; + +Point2D rotatePoint(const Point2D& point, double radians, const Point2D& origin); + class Triangle { public: @@ -39,7 +52,7 @@ public: // TODO: Is that the number of symmetries? const uint32_t POLY = 10; - const float scale = 4.0f; + const float BASE_SIZE = 4.0f; //const uint32_t window_w = 1920 * scale; //const uint32_t window_h = 1080 * scale; const uint32_t DEPTH = 10; //recursion depth @@ -50,7 +63,7 @@ public: void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); private: - void split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth); + void split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth); }; From 178f960533d00d38adfa0c5206101a890249c441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 2 Feb 2024 14:27:32 +0100 Subject: [PATCH 3/4] First working version --- lib/rmg/CZonePlacer.cpp | 84 ++++++++++++---- lib/rmg/PenroseTiling.cpp | 203 +++++++++++++------------------------- lib/rmg/PenroseTiling.h | 15 +-- 3 files changed, 142 insertions(+), 160 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 44e372dbe..383370ab5 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -20,6 +20,7 @@ #include "RmgMap.h" #include "Zone.h" #include "Functions.h" +#include "PenroseTiling.h" VCMI_LIB_NAMESPACE_BEGIN @@ -524,7 +525,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const std::vector prescaler = { 0, 0 }; 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(sqrt(width * height)); 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 dx = abs(A.x - B.x) * scaleX; 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 */ + /* return dx * dx + dy * dy + 5 * std::sin(dx * dy / 10) + 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) @@ -845,7 +852,13 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize(); }; - auto moveZoneToCenterOfMass = [](const std::shared_ptr & 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) -> void { int3 total(0, 0, 0); auto tiles = zone->area().getTiles(); @@ -855,17 +868,17 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) } int size = static_cast(tiles.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(); - /* - 1. Create Voronoi diagram - 2. find current center of mass for each zone. Move zone to that center to balance zones sizes - */ + // Find current center of mass for each zone. Move zone to that center to balance zones sizes int3 pos; + for(pos.z = 0; pos.z < levels; pos.z++) { for(pos.x = 0; pos.x < width; pos.x++) @@ -890,14 +903,41 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) if(zone.second->area().empty()) 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); } - //assign actual tiles to each zone using nonlinear norm for fine edges - for(const auto & zone : zones) 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::set> 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.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++) { distances.clear(); - for(const auto & zone : zones) + for(const auto & zoneVertex : vertexMapping) { - if (zone.second->getPos().z == pos.z) - distances.emplace_back(zone.second, metric(pos, zone.second->getPos())); - else - distances.emplace_back(zone.second, std::numeric_limits::max()); + // FIXME: Find closest vertex, not closest zone + auto zone = zoneVertex.first; + for (const auto & vertex : zoneVertex.second) + { + if (zone->getCenter().z == pos.z) + distances.emplace_back(zone, metric(pos, vertex)); + else + distances.emplace_back(zone, std::numeric_limits::max()); + } } - auto zone = boost::min_element(distances, compareByDistance)->first; //closest tile belongs to zone - zone->area().add(pos); - map.setZoneID(pos, zone->getId()); + + auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; //closest tile belongs to zone + closestZone->area().add(pos); + map.setZoneID(pos, closestZone->getId()); } } } + //set position (town position) to center of mass of irregular zone for(const auto & zone : zones) { + if(zone.second->area().empty()) + throw rmgException("Empty zone after Penrose tiling"); + moveZoneToCenterOfMass(zone.second); //TODO: similiar for islands diff --git a/lib/rmg/PenroseTiling.cpp b/lib/rmg/PenroseTiling.cpp index 70d6696ee..84a1404aa 100644 --- a/lib/rmg/PenroseTiling.cpp +++ b/lib/rmg/PenroseTiling.cpp @@ -1,28 +1,20 @@ /* - * © 2020 Michael Percival - * See LICENSE file for copyright and license details. + * PenroseTiling.cpp, part of VCMI engine + * + * 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 - -//https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/geometry/reference/adapted/boost_polygon/point_data.html - -//#include -//#include - -//#include -//#include +// Adapted from https://github.com/mpizzzle/penrose by Michael Percival #include "StdInc.h" #include "PenroseTiling.h" - -//#include "shader.hpp" -//#include "png_writer.hpp" - VCMI_LIB_NAMESPACE_BEGIN -//static const std::string file_name = "penrose.png"; 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()); } +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): tiling(t_123), indices(inds) {} +Triangle::~Triangle() +{ + for (auto * triangle : subTriangles) + { + if (triangle) + { + delete triangle; + triangle = nullptr; + } + } +} + Point2D Point2D::rotated(float radians) const { float cosAngle = cos(radians); @@ -51,21 +67,6 @@ Point2D Point2D::rotated(float radians) const 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 rot(radians); - - Point2D rotatedPoint; - rot.apply(point, rotatedPoint); - //transform(point, rotatedPoint, rot); - - return rotatedPoint; -} -*/ - void PenroseTiling::split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth) { @@ -81,21 +82,20 @@ void PenroseTiling::split(Triangle& p, std::vector& points, 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))); - Triangle t1(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] })); - Triangle t3(false, TIndices({ s, s + 1, i[0] })); + auto * t1 = new Triangle(p2, TIndices({ i[(!p2) + 1], p2 ? i[2] : s, p2 ? s : i[1] })); + auto * t2 = new Triangle(true, TIndices({ p2 ? i[1] : s, s + 1, p2 ? s : i[1] })); + 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 { points.push_back(Point2D((points[i[p2 * 2]] * (1.0f - PHI)) + (points[i[!p2]]) * PHI)); - Triangle t1(true, TIndices({ i[2], s, i[1] })); - Triangle t2(false, TIndices({ i[(!p2) + 1], s, i[0] })); + auto * t1 = new Triangle(true, TIndices({ i[2], s, i[1] })); + 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) @@ -122,23 +122,14 @@ void PenroseTiling::split(Triangle& p, std::vector& points, return; } -// TODO: Return something -void PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * rand) +std::set 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{}()); - static std::uniform_real_distribution<> d(0, 1); + float randomAngle = rand->nextDouble(0, 2 * PI_CONSTANT); - /* - std::vector 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 points = { Point2D(0.0f, 0.0f), Point2D(0.0f, 1.0f) }; + std::vector points = { Point2D(0.0f, 0.0f), Point2D(0.0f, 1.0f).rotated(randomAngle) }; std::array, 5> indices; for (uint32_t i = 1; i < POLY; ++i) @@ -147,12 +138,21 @@ void PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * ra points.push_back(next); } - // TODO: Scale to unit square for (auto& p : points) { 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 finalPoints; + for (uint32_t i = 0; i < POLY; i++) { std::array 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); } - // 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 \ No newline at end of file diff --git a/lib/rmg/PenroseTiling.h b/lib/rmg/PenroseTiling.h index 708b425ad..3288f4dfb 100644 --- a/lib/rmg/PenroseTiling.h +++ b/lib/rmg/PenroseTiling.h @@ -21,6 +21,8 @@ VCMI_LIB_NAMESPACE_BEGIN using namespace boost::geometry; typedef std::array TIndices; +const float PI_CONSTANT = 3.141592f; + class Point2D : public model::d2::point_xy { public: @@ -29,6 +31,8 @@ public: Point2D operator * (float scale) const; Point2D operator + (const Point2D& other) const; Point2D rotated(float radians) const; + + bool operator < (const Point2D& other) const; }; 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 { public: + ~Triangle(); + const bool tiling; TIndices indices; @@ -52,15 +58,12 @@ public: // TODO: Is that the number of symmetries? const uint32_t POLY = 10; - const float BASE_SIZE = 4.0f; - //const uint32_t window_w = 1920 * scale; - //const uint32_t window_h = 1080 * scale; - const uint32_t DEPTH = 10; //recursion depth + const float BASE_SIZE = 1.f; + const uint32_t DEPTH = 7; //Recursion depth const bool P2 = false; // Tiling type - //const float line_w = 2.0f; //line width - void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); + std::set generatePenroseTiling(size_t numZones, CRandomGenerator * rand); private: void split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth); From e6f0afd5865f194e97ade60431cf0005c630331d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 2 Feb 2024 15:50:33 +0100 Subject: [PATCH 4/4] Tweaking parameters, cleanup --- lib/rmg/CZonePlacer.cpp | 33 ++------------------------------- lib/rmg/CZonePlacer.h | 4 +--- lib/rmg/PenroseTiling.cpp | 20 +------------------- lib/rmg/PenroseTiling.h | 6 ++---- 4 files changed, 6 insertions(+), 57 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 383370ab5..6bcc27d64 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -27,7 +27,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CRandomGenerator; CZonePlacer::CZonePlacer(RmgMap & map) - : width(0), height(0), scaleX(0), scaleY(0), mapSize(0), + : width(0), height(0), mapSize(0), gravityConstant(1e-3f), stiffnessConstant(3e-3f), stifness(0), @@ -803,22 +803,6 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista float CZonePlacer::metric (const int3 &A, const int3 &B) const { - /* - float dx = abs(A.x - B.x) * scaleX; - float dy = abs(A.y - B.y) * scaleY; - - /* - 1. Normal euclidean distance - 2. Sinus for extra curves - 3. Nonlinear mess for fuzzy edges - */ - - /* - return dx * dx + dy * dy + - 5 * std::sin(dx * dy / 10) + - 25 * std::sin (std::sqrt(A.x * B.x) * (A.y - B.y) / 100 * (scaleX * scaleY)); - */ - return A.dist2dSQ(B); } @@ -830,9 +814,6 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) auto width = map.getMapGenOptions().getWidth(); auto height = map.getMapGenOptions().getHeight(); - //scale to Medium map to ensure smooth results - scaleX = 72.f / width; - scaleY = 72.f / height; auto zones = map.getZones(); vstd::erase_if(zones, [](const std::pair> & pr) @@ -903,7 +884,6 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) if(zone.second->area().empty()) 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); } @@ -912,7 +892,7 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) // Assign zones to closest Penrose vertex PenroseTiling penrose; - auto vertices = penrose.generatePenroseTiling(zones.size(), rand); + auto vertices = penrose.generatePenroseTiling(zones.size() / map.levels(), rand); std::map, std::set> vertexMapping; @@ -928,15 +908,6 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) 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++) { diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 90b77b8f2..2eaf429ce 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -54,9 +54,7 @@ private: private: int width; int height; - //metric coeficients - float scaleX; - float scaleY; + //metric coeficient float mapSize; float gravityConstant; diff --git a/lib/rmg/PenroseTiling.cpp b/lib/rmg/PenroseTiling.cpp index 84a1404aa..e6ee0e9f4 100644 --- a/lib/rmg/PenroseTiling.cpp +++ b/lib/rmg/PenroseTiling.cpp @@ -124,7 +124,7 @@ void PenroseTiling::split(Triangle& p, std::vector& points, std::set PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * rand) { - float scale = 100.f / (numZones + 20); + float scale = 100.f / (numZones * 1.5f + 20); float polyAngle = (2 * PI_CONSTANT) / POLY; float randomAngle = rand->nextDouble(0, 2 * PI_CONSTANT); @@ -143,14 +143,6 @@ std::set PenroseTiling::generatePenroseTiling(size_t numZones, CRandomG 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 finalPoints; for (uint32_t i = 0; i < POLY; i++) @@ -162,21 +154,11 @@ std::set PenroseTiling::generatePenroseTiling(size_t numZones, CRandomG split(t, points, indices, DEPTH); } - /* - //No difference for the number of points - for (auto& p : points) - { - p = p + Point2D(0.5f, 0.5f); // Center in a square (0,1) - } - */ - 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; } diff --git a/lib/rmg/PenroseTiling.h b/lib/rmg/PenroseTiling.h index 3288f4dfb..bdda4c7c9 100644 --- a/lib/rmg/PenroseTiling.h +++ b/lib/rmg/PenroseTiling.h @@ -14,7 +14,6 @@ #include "../CRandomGenerator.h" #include #include -#include VCMI_LIB_NAMESPACE_BEGIN @@ -55,10 +54,9 @@ class PenroseTiling public: const float PHI = 1.0 / ((1.0 + std::sqrt(5.0)) / 2); - // TODO: Is that the number of symmetries? - const uint32_t POLY = 10; + const uint32_t POLY = 10; // Number of symmetries? - const float BASE_SIZE = 1.f; + const float BASE_SIZE = 1.25f; const uint32_t DEPTH = 7; //Recursion depth const bool P2 = false; // Tiling type