Skip to content

Commit

Permalink
Scripting: Added API for editing tile layers using terrain sets (#3758)
Browse files Browse the repository at this point in the history
* Added TileLayer.wangEdit, which returns a TileLayerWangEdit instance
* Moved shared functionality from WangBrush to WangFiller
* Fixed issue in WangFiller::findBestMatch when corrections are not enabled

Co-authored-by: Thorbjørn Lindeijer <[email protected]>
  • Loading branch information
a-morphous and bjorn authored Jun 28, 2023
1 parent c5c6971 commit fcd9733
Show file tree
Hide file tree
Showing 20 changed files with 601 additions and 165 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
### Unreleased

* Removed Space and Ctrl+Space shortcuts from Layers view to avoid conflict with panning (#3672)
* Scripting: Added API for editing tile layers using terrain sets (with a-morphous, #3758)
* Fixed object preview position with parallax factor on group layer (#3669)
* Fixed hover highlight rendering with active parallax factor (#3669)
* Fixed updating of object selection outlines when changing parallax factor (#3669)
Expand Down
150 changes: 147 additions & 3 deletions docs/scripting-doc/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2069,6 +2069,8 @@ interface MapEditor {
/**
* Gets the currently selected {@link WangSet} in the "Terrain Sets" view.
*
* See also {@link TileLayerWangEdit}.
*
* @since 1.8
*/
readonly currentWangSet: WangSet
Expand All @@ -2085,6 +2087,8 @@ interface MapEditor {
* The value 0 is used to represent the eraser mode, and the first Wang color
* has index 1.
*
* See also {@link TileLayerWangEdit}.
*
* @since 1.8
*/
readonly currentWangColorIndex: number
Expand Down Expand Up @@ -2845,6 +2849,14 @@ declare class TileLayer extends Layer {
* Returns an object that enables making modifications to the tile layer.
*/
edit() : TileLayerEdit

/**
* Returns an object that enables making modifications to the tile layer
* using the given {@link WangSet}.
*
* @since 1.10.2
*/
wangEdit(wangSet: WangSet) : TileLayerWangEdit
}

/**
Expand All @@ -2861,7 +2873,8 @@ interface TileLayerEdit {
readonly target : TileLayer

/**
* Whether applied edits are mergeable with previous edits. Starts out as false and is automatically set to true by {@link apply}.
* Whether applied edits are mergeable with previous edits. Starts out as
* `false` and is automatically set to `true` by {@link apply}.
*/
mergeable : boolean

Expand All @@ -2880,6 +2893,137 @@ interface TileLayerEdit {
apply() : void
}

/**
* The Wang indexes are arranged as follows:
*
* ```
* 7 0 1
* 6 - 2
* 5 4 3
* ```
*
* These indexes are used by the {@link TileLayerWangEdit}.
*
* @since 1.10.2
*/
declare enum WangIndex {
Top = 0,
TopRight = 1,
Right = 2,
BottomRight = 3,
Bottom = 4,
BottomLeft = 5,
Left = 6,
TopLeft = 7,
NumCorners = 4,
NumEdges = 4,
NumIndexes = 8,
}

/**
* This object enables modifying the tiles on a tile layer using a
* {@link WangSet}. For performance reasons, the changes are not applied
* directly. The {@link apply} function needs to be called when you're done
* making changes.
*
* Note that the results of calling {@link apply} may vary since the changes
* are applied by looking for tiles matching the desired Wang colors, which
* includes a random factor in case of multiple matches.
*
* Colors in a {@link WangSet} are numbered starting from 1. To request no Wang
* color, usually for Wang-aware erasing, use 0. The currently selected {@link
* WangSet} and color are available through {@link MapEditor.currentWangSet}
* and {@link MapEditor.currentWangColorIndex}.
*
* An instance of this object is created by calling {@link TileLayer.wangEdit}.
*
* @since 1.10.2
*/
interface TileLayerWangEdit {
/**
* The target layer of this edit object.
*/
readonly target : TileLayer

/**
* Whether applied edits are mergeable with previous edits. Starts out as
* `false` and is automatically set to `true` by {@link apply}.
*/
mergeable : boolean

/**
* Whether neighboring tiles will be corrected to match up with any marked
* changes once {@link apply} is called. This can cause a larger area to get
* modified. Defaults to `false`.
*/
correctionsEnabled : boolean

/**
* Sets the desired color for the given Wang index at the given location.
*
* This is a low-level function, which only affects the given location and
* does not automatically adjust any neighboring tiles. Use {@link setCorner}
* or {@link setEdge} when that is desired or set {@link correctionsEnabled}
* to `true`.
*/
setWangIndex(x : number, y : number, wangIndex: WangIndex, color : number) : void

/**
* Sets the desired color for the given Wang index at the given location.
*
* This is a low-level function, which only affects the given location and
* does not automatically adjust any neighboring tiles. Use {@link setCorner}
* or {@link setEdge} when that is desired or set {@link correctionsEnabled}
* to `true`.
*/
setWangIndex(pos : point, wangIndex: WangIndex, color : number) : void

/**
* Sets the desired color for the given corner at the given vertex location.
*
* The vertex location refers to a point in between the tiles, where (0, 0) is
* the top-left corner of the map and (mapWidth, mapHeight) is the bottom-right
* corner.
*
* Changing the color of a corner affects all 4 tiles meeting at that corner.
*/
setCorner(x : number, y : number, color : number) : void

/**
* Sets the desired color for the given corner at the given vertex location.
*
* The vertex location refers to a point in between the tiles, where (0, 0) is
* the top-left corner of the map and (mapWidth, mapHeight) is the bottom-right
* corner.
*
* Changing the color of a corner affects all 4 tiles meeting at that corner.
*/
setCorner(pos : point, color : number) : void

/**
* Sets the desired color for the given edge at the given location. Only the
* values {@link WangIndex.Top}, {@link WangIndex.Left}, {@link
* WangIndex.Right} and {@link WangIndex.Bottom} are supported.
*
* Changing the color of an edge affects the 2 tiles connected by that edge.
*/
setEdge(x : number, y : number, edge: WangIndex, color : number) : void

/**
* Sets the desired color for the given edge at the given location. Only the
* values {@link WangIndex.Top}, {@link WangIndex.Left}, {@link
* WangIndex.Right} and {@link WangIndex.Bottom} are supported.
*
* Changing the color of an edge affects the 2 tiles connected by that edge.
*/
setEdge(pos : point, edge: WangIndex, color : number) : void

/**
* Applies all changes made through this object. This object can be reused to make further changes.
*/
apply() : void
}

/**
* Defines a "Terrain Set".
*
Expand Down Expand Up @@ -2921,15 +3065,15 @@ declare class WangSet extends TiledObject {
/**
* Returns the current Wang ID associated with the given tile.
*
* The Wang ID is given by an array of 8 numbers, indicating the colors associated with each index in the following order: [Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left, TopLeft].
* The Wang ID is given by an array of 8 numbers, indicating the colors associated with each index in the following order: [Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left, TopLeft] (see {@link WangIndex}).
* A value of 0 indicates that no color is associated with a given index.
*/
public wangId(tile : Tile) : number[]

/**
* Sets the Wang ID associated with the given tile.
*
* The Wang ID is given by an array of 8 numbers, indicating the colors associated with each index in the following order: [Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left, TopLeft].
* The Wang ID is given by an array of 8 numbers, indicating the colors associated with each index in the following order: [Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left, TopLeft] (see {@link WangIndex}).
* A value of 0 indicates that no color is associated with a given index.
*
* Make sure the Wang set color count is set before calling this function, because it will raise an error when the Wang ID refers to non-existing colors.
Expand Down
5 changes: 3 additions & 2 deletions src/libtiled/wangset.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* wangset.cpp
* Copyright 2017, Benjamin Trotter <[email protected]>
*
* This file is part of libtiled.
*
* Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -218,7 +219,7 @@ bool WangId::hasEdgeWithColor(int value) const
/**
* Rotates the wang Id clockwise by (90 * rotations) degrees.
* Meaning with one rotation, the top edge becomes the right edge,
* and the top right corner, becomes the top bottom.
* and the top right corner, becomes the bottom right.
*/
void WangId::rotate(int rotations)
{
Expand Down Expand Up @@ -727,7 +728,7 @@ QList<WangTile> WangSet::sortedWangTiles() const
* 6|X|2
* 5|4|3
*/
WangId WangSet::wangIdFromSurrounding(const WangId surroundingWangIds[]) const
WangId WangSet::wangIdFromSurrounding(const WangId surroundingWangIds[])
{
quint64 id = 0;

Expand Down
3 changes: 2 additions & 1 deletion src/libtiled/wangset.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* wangset.h
* Copyright 2017, Benjamin Trotter <[email protected]>
*
* This file is part of libtiled.
*
* Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -277,7 +278,7 @@ class TILEDSHARED_EXPORT WangSet : public Object

QList<WangTile> sortedWangTiles() const;

WangId wangIdFromSurrounding(const WangId surroundingWangIds[]) const;
static WangId wangIdFromSurrounding(const WangId surroundingWangIds[]);
WangId wangIdFromSurrounding(const Cell surroundingCells[]) const;

WangId wangIdOfTile(const Tile *tile) const;
Expand Down
4 changes: 2 additions & 2 deletions src/tiled/abstracttilefilltool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ void AbstractTileFillTool::wangFill(TileLayer &tileLayerToFill,
return;

WangFiller wangFiller(*mWangSet, mapDocument()->renderer());

wangFiller.fillRegion(tileLayerToFill, backgroundTileLayer, region);
wangFiller.setRegion(region);
wangFiller.apply(tileLayerToFill, backgroundTileLayer);
}

void AbstractTileFillTool::fillWithStamp(Map &map,
Expand Down
2 changes: 1 addition & 1 deletion src/tiled/debugdrawitem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ QRectF DebugDrawItem::boundingRect() const

void DebugDrawItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
for (const Entry &entry : mEntries)
for (const Entry &entry : std::as_const(mEntries))
const_cast<QPicture *>(&entry.picture)->play(painter);
}

Expand Down
62 changes: 61 additions & 1 deletion src/tiled/editabletilelayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@

#include "editabletilelayer.h"

#include "addremovetileset.h"
#include "changelayer.h"
#include "editablemanager.h"
#include "editablemap.h"
#include "painttilelayer.h"
#include "resizetilelayer.h"
#include "scriptmanager.h"
#include "tilelayeredit.h"
#include "tilesetdocument.h"
#include "tilelayerwangedit.h"

namespace Tiled {

Expand All @@ -45,6 +48,8 @@ EditableTileLayer::~EditableTileLayer()
{
while (!mActiveEdits.isEmpty())
delete mActiveEdits.first();
while (!mActiveWangEdits.isEmpty())
delete mActiveWangEdits.first();
}

void EditableTileLayer::setSize(QSize size)
Expand Down Expand Up @@ -101,6 +106,61 @@ TileLayerEdit *EditableTileLayer::edit()
return new TileLayerEdit(this);
}

TileLayerWangEdit *EditableTileLayer::wangEdit(EditableWangSet *wangSet)
{
if (!wangSet) {
ScriptManager::instance().throwNullArgError(0);
return nullptr;
}

if (!map()) {
ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Layer not part of a map"));
return nullptr;
}

return new TileLayerWangEdit(this, wangSet);
}

void EditableTileLayer::applyChangesFrom(TileLayer *changes, bool mergeable)
{
// Determine painted region and normalize the changes layer
auto paintedRegion = changes->region([] (const Cell &cell) { return cell.checked(); });

// If the painted region is empty there's nothing else to do
if (paintedRegion.isEmpty())
return;

auto rect = paintedRegion.boundingRect();
changes->resize(rect.size(), -rect.topLeft());
const auto tilesets = changes->usedTilesets();

if (mapDocument()) {
// Apply the change using an undo command
auto mapDocument = map()->mapDocument();
auto paint = new PaintTileLayer(mapDocument,
tileLayer(),
rect.x(), rect.y(),
changes,
paintedRegion);
paint->setMergeable(mergeable);

// Add any used tilesets that aren't yet part of the target map
const auto existingTilesets = mapDocument->map()->tilesets();
for (const SharedTileset &tileset : tilesets)
if (!existingTilesets.contains(tileset))
new AddTileset(mapDocument, tileset, paint);

map()->push(paint);
} else {
// Add any used tilesets that aren't yet part of the target map
if (auto map = tileLayer()->map())
map->addTilesets(tilesets);

// Apply the change directly
tileLayer()->setCells(rect.x(), rect.y(), changes, paintedRegion);
}
}

} // namespace Tiled

#include "moc_editabletilelayer.cpp"
9 changes: 8 additions & 1 deletion src/tiled/editabletilelayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
namespace Tiled {

class EditableTile;
class EditableWangSet;
class TileLayerEdit;
class TileLayerWangEdit;

class EditableTileLayer : public EditableLayer
{
Expand Down Expand Up @@ -64,13 +66,18 @@ class EditableTileLayer : public EditableLayer
Q_INVOKABLE Tiled::EditableTile *tileAt(int x, int y) const;

Q_INVOKABLE Tiled::TileLayerEdit *edit();
Q_INVOKABLE Tiled::TileLayerWangEdit *wangEdit(Tiled::EditableWangSet *wangSet);

TileLayer *tileLayer() const;

private:
friend TileLayerEdit;
friend class TileLayerEdit;
friend class TileLayerWangEdit;

QList<TileLayerEdit*> mActiveEdits;
QList<TileLayerWangEdit*> mActiveWangEdits;

void applyChangesFrom(TileLayer *changes, bool mergeable);
};


Expand Down
2 changes: 2 additions & 0 deletions src/tiled/libtilededitor.qbs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,8 @@ DynamicLibrary {
"tilelayeredit.h",
"tilelayeritem.cpp",
"tilelayeritem.h",
"tilelayerwangedit.cpp",
"tilelayerwangedit.h",
"tilepainter.cpp",
"tilepainter.h",
"tileselectionitem.cpp",
Expand Down
Loading

0 comments on commit fcd9733

Please sign in to comment.