Skip to content
Permalink
Browse files

AutoMapping: Changed matching outside of map boundaries

By default, rules will no longer match when their input region is
partially outside of the map boundaries (except on infinite maps, where
this behavior doesn't make sense).

This behavior can now also be explicitly controlled by adding a boolean
MatchOutsideMap property to the rules map, since some people may desire
the previous behavior even on bounded maps.

Using the property, you can try to disable matching outside of the map
for infinite maps, but I expect that will just break most rules rather
than do anything useful.

See also: https://discourse.mapeditor.org/t/automapper-ignore-the-empty-void-beyond-the-perimeter-of-map
  • Loading branch information...
bjorn committed Sep 4, 2018
1 parent a4ddca1 commit 9db8dd4f07091b05652de7ce7d13c12bdddebb08
Showing with 75 additions and 40 deletions.
  1. +16 −2 docs/manual/automapping.rst
  2. +30 −20 src/tiled/automapper.cpp
  3. +29 −18 src/tiled/automapper.h
@@ -155,8 +155,8 @@ true.
Map Properties
--------------

There are three different map properties, which can be used to add
additional information to a **rulefile**:
The following map properties can be used to customize the behavior of
the rules in a **rulefile**:

DeleteTiles
This map property is a boolean property: it can be
@@ -174,6 +174,20 @@ AutomappingRadius
determines how many tiles around your changes will be checked as well
for redoing the Automapping at live Automapping.

.. raw:: html

<div class="new">New in Tiled 1.2</div>

MatchOutsideMap
This map property determines whether rules can match even when their input
region falls partially outside of a map. By default it is ``false`` for
bounded maps and ``true`` for infinite maps. In some cases it can be useful
to enable this also for bounded maps. Tiles outside of the map boundaries
are simply considered empty.

Tiled 1.0 and 1.1 behaved as if this property was ``true``, whereas older
versions of Tiled have behaved as if this property was ``false``.

NoOverlappingRules
This map property is a boolean property:
A rule is not allowed to overlap on itself.
@@ -59,9 +59,6 @@ AutoMapper::AutoMapper(MapDocument *workingDocument, Map *rules,
, mLayerInputRegions(nullptr)
, mLayerOutputRegions(nullptr)
, mRulePath(rulePath)
, mDeleteTiles(false)
, mAutoMappingRadius(0)
, mNoOverlappingRules(false)
{
Q_ASSERT(mMapRules);

@@ -92,26 +89,34 @@ bool AutoMapper::ruleLayerNameUsed(const QString &ruleLayerName) const

bool AutoMapper::setupRuleMapProperties()
{
// By default, only infinite maps match rules outside of their boundaries
mOptions.matchOutsideMap = mMapWork->infinite();

QMapIterator<QString, QVariant> it(mMapRules->properties());
while (it.hasNext()) {
it.next();

const QString &name = it.key();
const QVariant &value = it.value();

if (name.compare(QLatin1String("deletetiles"), Qt::CaseInsensitive) == 0) {
if (name.compare(QLatin1String("DeleteTiles"), Qt::CaseInsensitive) == 0) {
if (value.canConvert(QVariant::Bool)) {
mOptions.deleteTiles = value.toBool();
continue;
}
} else if (name.compare(QLatin1String("MatchOutsideMap"), Qt::CaseInsensitive) == 0) {
if (value.canConvert(QVariant::Bool)) {
mDeleteTiles = value.toBool();
mOptions.matchOutsideMap = value.toBool();
continue;
}
} else if (name.compare(QLatin1String("automappingradius"), Qt::CaseInsensitive) == 0) {
} else if (name.compare(QLatin1String("AutomappingRadius"), Qt::CaseInsensitive) == 0) {
if (value.canConvert(QVariant::Int)) {
mAutoMappingRadius = value.toInt();
mOptions.autoMappingRadius = value.toInt();
continue;
}
} else if (name.compare(QLatin1String("nooverlappingrules"), Qt::CaseInsensitive) == 0) {
} else if (name.compare(QLatin1String("NoOverlappingRules"), Qt::CaseInsensitive) == 0) {
if (value.canConvert(QVariant::Bool)) {
mNoOverlappingRules = value.toBool();
mOptions.noOverlappingRules = value.toBool();
continue;
}
}
@@ -442,24 +447,24 @@ void AutoMapper::autoMap(QRegion *where)
{
Q_ASSERT(mRulesInput.size() == mRulesOutput.size());
// first resize the active area
if (mAutoMappingRadius) {
if (mOptions.autoMappingRadius) {
QRegion region;
#if QT_VERSION < 0x050800
const auto rects = where->rects();
for (const QRect &r : rects) {
#else
for (const QRect &r : *where) {
#endif
region += r.adjusted(- mAutoMappingRadius,
- mAutoMappingRadius,
+ mAutoMappingRadius,
+ mAutoMappingRadius);
region += r.adjusted(- mOptions.autoMappingRadius,
- mOptions.autoMappingRadius,
+ mOptions.autoMappingRadius,
+ mOptions.autoMappingRadius);
}
where->swap(region);
}

// delete all the relevant area, if the property "DeleteTiles" is set
if (mDeleteTiles) {
if (mOptions.deleteTiles) {
const QRegion setLayersRegion = computeSetLayersRegion();
for (const RuleOutput &translationTable : mLayerList) {
for (const int index : translationTable) {
@@ -603,7 +608,8 @@ static void collectCellsInRegion(const QVector<InputLayer> &list,
static bool layerMatchesConditions(const TileLayer &setLayer,
const InputConditions &conditions,
const QRegion &ruleRegion,
const QPoint &offset)
const QPoint &offset,
const AutoMapper::Options &options)
{
const auto &listYes = conditions.listYes;
const auto &listNo = conditions.listNo;
@@ -622,6 +628,10 @@ static bool layerMatchesConditions(const TileLayer &setLayer,
#endif
for (int x = rect.left(); x <= rect.right(); ++x) {
for (int y = rect.top(); y <= rect.bottom(); ++y) {
if (!options.matchOutsideMap &&
!setLayer.contains(x + offset.x(), y + offset.y()))
return false;

const Cell &setCell = setLayer.cellAt(x + offset.x(),
y + offset.y());

@@ -691,10 +701,10 @@ QRect AutoMapper::applyRule(int ruleIndex, const QRect &where)
// make sure there are no overlaps of the same rule applied to
// (neighbouring) places
QVector<QRegion> appliedRegions;
if (mNoOverlappingRules)
if (mOptions.noOverlappingRules)
appliedRegions.resize(mMapWork->layerCount());

const TileLayer dummy(QString(), 0, 0, 0, 0);
const TileLayer dummy(QString(), 0, 0, mMapWork->width(), mMapWork->height());

for (int y = minY; y <= maxY; ++y)
for (int x = minX; x <= maxX; ++x) {
@@ -713,7 +723,7 @@ QRect AutoMapper::applyRule(int ruleIndex, const QRect &where)
const int i = mMapWork->indexOfLayer(name, Layer::TileLayerType);
const TileLayer &setLayer = (i >= 0) ? *mMapWork->layerAt(i)->asTileLayer() : dummy;

if (!layerMatchesConditions(setLayer, conditions, ruleInputRegion, QPoint(x, y))) {
if (!layerMatchesConditions(setLayer, conditions, ruleInputRegion, QPoint(x, y), mOptions)) {
allLayerNamesMatch = false;
break;
}
@@ -730,7 +740,7 @@ QRect AutoMapper::applyRule(int ruleIndex, const QRect &where)
const int r = qrand() % mLayerList.size();
const RuleOutput &translationTable = mLayerList.at(r);

if (mNoOverlappingRules) {
if (mOptions.noOverlappingRules) {
bool overlap = false;
const QList<Layer*> layers = translationTable.keys();

@@ -84,8 +84,36 @@ class AutoMapper : public QObject
Q_OBJECT

public:
struct Options
{
/**
* Determines if all tiles in all touched layers should be deleted first.
*/
bool deleteTiles = false;

/**
* Whether rules can match when their input region is partially outside
* of the map.
*/
bool matchOutsideMap = true;

/**
* Determines if a rule is allowed to overlap itself.
*/
bool noOverlappingRules = false;

/**
* This variable determines, how many overlapping tiles should be used.
* The bigger the more area is remapped at an automapping operation.
* This can lead to higher latency, but provides a better behavior on
* interactive automapping.
*/
int autoMappingRadius = 0;
};

/**
* Constructs an AutoMapper.
*
* All data structures, which only rely on the rules map are setup
* here.
*
@@ -337,24 +365,7 @@ class AutoMapper : public QObject
*/
QString mRulePath;

/**
* determines if all tiles in all touched layers should be deleted first.
*/
bool mDeleteTiles;

/**
* This variable determines, how many overlapping tiles should be used.
* The bigger the more area is remapped at an automapping operation.
* This can lead to higher latency, but provides a better behavior on
* interactive automapping.
* It defaults to zero.
*/
int mAutoMappingRadius;

/**
* Determines if a rule is allowed to overlap itself.
*/
bool mNoOverlappingRules;
Options mOptions;

QSet<QString> mTouchedTileLayers;
QSet<QString> mTouchedObjectGroups;

0 comments on commit 9db8dd4

Please sign in to comment.
You can’t perform that action at this time.