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:
@@ -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
|
||||
|
@@ -20,13 +20,14 @@
|
||||
#include "RmgMap.h"
|
||||
#include "Zone.h"
|
||||
#include "Functions.h"
|
||||
#include "PenroseTiling.h"
|
||||
|
||||
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),
|
||||
@@ -524,7 +525,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const
|
||||
|
||||
std::vector<float> 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<float>(sqrt(width * height));
|
||||
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 dx = abs(A.x - B.x) * scaleX;
|
||||
float dy = abs(A.y - B.y) * scaleY;
|
||||
return A.dist2dSQ(B);
|
||||
|
||||
/*
|
||||
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)
|
||||
@@ -823,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<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();
|
||||
};
|
||||
|
||||
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);
|
||||
auto tiles = zone->area().getTiles();
|
||||
@@ -855,17 +849,17 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
|
||||
}
|
||||
int size = static_cast<int>(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++)
|
||||
@@ -893,11 +887,28 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
|
||||
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() / 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.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++)
|
||||
{
|
||||
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<float>::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<float>::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
|
||||
|
@@ -54,9 +54,7 @@ private:
|
||||
private:
|
||||
int width;
|
||||
int height;
|
||||
//metric coeficients
|
||||
float scaleX;
|
||||
float scaleY;
|
||||
//metric coeficient
|
||||
float mapSize;
|
||||
|
||||
float gravityConstant;
|
||||
|
165
lib/rmg/PenroseTiling.cpp
Normal file
165
lib/rmg/PenroseTiling.cpp
Normal 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
71
lib/rmg/PenroseTiling.h
Normal 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
|
Reference in New Issue
Block a user