1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

Merge pull request #3590 from vcmi/penrose_tiling

Positive opionions, no issues found.
This commit is contained in:
DjWarmonger
2024-02-03 20:00:46 +01:00
committed by GitHub
5 changed files with 292 additions and 35 deletions

View File

@@ -151,6 +151,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/rmg/Zone.cpp ${MAIN_LIB_DIR}/rmg/Zone.cpp
${MAIN_LIB_DIR}/rmg/Functions.cpp ${MAIN_LIB_DIR}/rmg/Functions.cpp
${MAIN_LIB_DIR}/rmg/RmgMap.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/Modificator.cpp
${MAIN_LIB_DIR}/rmg/modificators/ObjectManager.cpp ${MAIN_LIB_DIR}/rmg/modificators/ObjectManager.cpp
${MAIN_LIB_DIR}/rmg/modificators/ObjectDistributor.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/RmgMap.h
${MAIN_LIB_DIR}/rmg/float3.h ${MAIN_LIB_DIR}/rmg/float3.h
${MAIN_LIB_DIR}/rmg/Functions.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/Modificator.h
${MAIN_LIB_DIR}/rmg/modificators/ObjectManager.h ${MAIN_LIB_DIR}/rmg/modificators/ObjectManager.h
${MAIN_LIB_DIR}/rmg/modificators/ObjectDistributor.h ${MAIN_LIB_DIR}/rmg/modificators/ObjectDistributor.h

View File

@@ -20,13 +20,14 @@
#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
class CRandomGenerator; class CRandomGenerator;
CZonePlacer::CZonePlacer(RmgMap & map) CZonePlacer::CZonePlacer(RmgMap & map)
: width(0), height(0), scaleX(0), scaleY(0), mapSize(0), : width(0), height(0), mapSize(0),
gravityConstant(1e-3f), gravityConstant(1e-3f),
stiffnessConstant(3e-3f), stiffnessConstant(3e-3f),
stifness(0), stifness(0),
@@ -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,18 +803,8 @@ 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; return A.dist2dSQ(B);
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));
} }
void CZonePlacer::assignZones(CRandomGenerator * rand) void CZonePlacer::assignZones(CRandomGenerator * rand)
@@ -823,9 +814,6 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
auto width = map.getMapGenOptions().getWidth(); auto width = map.getMapGenOptions().getWidth();
auto height = map.getMapGenOptions().getHeight(); 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(); auto zones = map.getZones();
vstd::erase_if(zones, [](const std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>> & pr) vstd::erase_if(zones, [](const std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>> & pr)
@@ -845,7 +833,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 +849,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++)
@@ -893,11 +887,28 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
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() / map.levels(), 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
}
//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 +916,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

@@ -54,9 +54,7 @@ private:
private: private:
int width; int width;
int height; int height;
//metric coeficients //metric coeficient
float scaleX;
float scaleY;
float mapSize; float mapSize;
float gravityConstant; float gravityConstant;

165
lib/rmg/PenroseTiling.cpp Normal file
View File

@@ -0,0 +1,165 @@
/*
* 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 by Michael Percival
#include "StdInc.h"
#include "PenroseTiling.h"
VCMI_LIB_NAMESPACE_BEGIN
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());
}
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);
float sinAngle = sin(radians);
// Apply rotation matrix transformation
float newX = x() * cosAngle - y() * sinAngle;
float newY = x() * sinAngle + y() * cosAngle;
return Point2D(newX, newY);
}
void PenroseTiling::split(Triangle& p, std::vector<Point2D>& points,
std::array<std::vector<uint32_t>, 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(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)));
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] }));
p.subTriangles = { t1, t2, t3 };
}
else
{
points.push_back(Point2D((points[i[p2 * 2]] * (1.0f - PHI)) + (points[i[!p2]]) * PHI));
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 };
}
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;
}
std::set<Point2D> PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * rand)
{
float scale = 100.f / (numZones * 1.5f + 20);
float polyAngle = (2 * PI_CONSTANT) / POLY;
float randomAngle = rand->nextDouble(0, 2 * PI_CONSTANT);
std::vector<Point2D> points = { Point2D(0.0f, 0.0f), Point2D(0.0f, 1.0f).rotated(randomAngle) };
std::array<std::vector<uint32_t>, 5> indices;
for (uint32_t i = 1; i < POLY; ++i)
{
Point2D next = points[i].rotated(polyAngle);
points.push_back(next);
}
for (auto& p : points)
{
p.x(p.x() * scale * BASE_SIZE);
}
std::set<Point2D> finalPoints;
for (uint32_t i = 0; i < POLY; i++)
{
std::array<uint32_t, 2> 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);
}
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);
});
return finalPoints;
}
VCMI_LIB_NAMESPACE_END

71
lib/rmg/PenroseTiling.h Normal file
View File

@@ -0,0 +1,71 @@
/*
* 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 <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
VCMI_LIB_NAMESPACE_BEGIN
using namespace boost::geometry;
typedef std::array<uint32_t, 3> TIndices;
const float PI_CONSTANT = 3.141592f;
class Point2D : public model::d2::point_xy<float>
{
public:
using point_xy::point_xy;
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);
class Triangle
{
public:
~Triangle();
const bool tiling;
TIndices indices;
std::vector<Triangle *> subTriangles;
Triangle(bool t_123, const TIndices & inds);
};
class PenroseTiling
{
public:
const float PHI = 1.0 / ((1.0 + std::sqrt(5.0)) / 2);
const uint32_t POLY = 10; // Number of symmetries?
const float BASE_SIZE = 1.25f;
const uint32_t DEPTH = 7; //Recursion depth
const bool P2 = false; // Tiling type
std::set<Point2D> generatePenroseTiling(size_t numZones, CRandomGenerator * rand);
private:
void split(Triangle& p, std::vector<Point2D>& points, std::array<std::vector<uint32_t>, 5>& indices, uint32_t depth);
};
VCMI_LIB_NAMESPACE_END