-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add setTerrain functionality to TileLayerEdit #3758
Conversation
I don't know enough about the code to offer useful feedback, but:
editor.setTerrain(3, 3, 1, terrain, WangSet.BottomRight);
// would modify tiles 3,3, 4,3, 3,4, and 4,4 to create a 2x2 terrain out of four convex corners
editor.setTerrain(5, 3, 1, terrain, WangSet.BottomLeft);
// would expand the shape above into a 3x2 rectangle with two sides and the four convex corners
editor.setTerrain(3, 5, 1, terrain, WangSet.TopRight);
// *should* expand the shape above into a theoretical sideways L shape,
// but this would require a concave corner tile, which we don't have.
// At present, this will probably produce garbage (or, if we're lucky, the final result we want).
// But if the function doesn't compute the tiles for the desired terrains until
// TileLayerEdit.apply() is called, then there would be no issue.
editor.setTerrain(5, 5, 1, terrain, WangSet.TopLeft);
// Finish up the square by placing the final corner. Ideally, this should just give us our square.
// But if the previous step tried to place actual tiles before we finished defining our desired shape,
// then this bit might do nothing, or it might be polishing a turd :] While a paint-whole-tile mode like Ctrl on the Terrain Brush could help with the above 3x3 rectangle scenario, it would not be enough for rectangles that are larger than 3x3 in both directions, as that would still require an intermediate shape with a concave corner. |
@eishiya, to answer your questions: Directions for cornerThe reason why directions aren't required for corner is because that's true for the behavior of the wang brush when using it in the map editor; the direction is always set to As an example, a single This behavior does sometimes differ from the terrain stamp, because the logic there also takes into account the mouse position between tiles to possibly shift the position that new tiles should be drawn in, and thus how the terrain is painted. As the Meanwhile, for edge or edge-and-corner based terrains, the brush will change directions when you paint with the mouse depending on where the mouse is relative to the center of a tile in the tilemap. So, a call to and this with a direction of ...at least theoretically. This works on my local build, but when trying it with the build produced by Github Actions, the direction isn't being set properly from the extension, and is always Left. I'm not sure why this is the case, and I'll need to investigate. Parameter orderThe idea that I'm still thinking on this. Remembering terrain between callsThe example you gave with incomplete concave tilesets will indeed break with my code as written...sometimes; sometimes the end result is the 3x3 square you want, and sometimes the end result is Trying to make it so all terrain is set at the very end is going to require a total rewrite, though, since right now I'm placing terrain using the exact same logic as the existing terrain brush, which calculates all the edges as it's generated. I'm actually kind of confused how these incomplete tileset cases are handled in the map editor; when painting terrain from within Tiled using an incomplete tileset, trying to draw the square, sometimes I get the square as expected, and sometimes I get shapes that overwrite previous tiles and are not squares: I suspect that once again, the lack of a relative mouse position to the current tile is causing a difference in behavior from what the API is capable of vs what you get when using the wang brush tool in editor. ADDENDUM: The tests I did above were with a weird incomplete terrain set where I'd set the following corners: If you have an incomplete tileset that just has the exterior corners, e.g. |
The current Terrain code in Tiled, when it can't find a perfect match for desired terrains at a location, will modify some of the terrains/tiles to try to get something that makes sense overall, even if it's not quite what the user asked for. It does a search with some modified terrains I think, and the attempted modifications are random, which is why you get different results. On top of this, in the interest of performance, the search is not exhaustive, so it can end up with completely invalid scenarios too. Regarding remembering terrain between calls / dealing with incomplete terrains: Indeed, this would require a lot of rewriting, and I think it should be tackled along with the two issues I linked. The WangPainter you added is a great idea IMO, and I think that rather than duplicating the existing code, it might be best to tackle the existing Terrain issues, and the resulting improved WangSet functionality could then be exposed to scripting using whatever parameters are deemed appropriate once the new code is in place. In addition to the two issues I linked above, another issue that would benefit from the ability to compute terrains for more than a tile at a time is #2035. |
First of all I agree it's a nice idea to expose the terrain painting functionality to the scripting API! Since it's already getting late here I'll only note two things.
(and boy am I starting to hate the naming inconsistency between "wang" and "terrain" now...) |
Some updates, still not ready to be merged yet. I've addressed some, but not all of the concerns that were laid out above:
Still unaddressed
It shouldn't take too too much time to get the rest of the concerns addressed, but this is all for today. |
4ae0c97
to
2fd002e
Compare
2fd002e
to
532edce
Compare
I think at this point, I've implemented all the requested changes. The latest changes implements bjorn's suggestion to create a new So now to edit terrain in an extension you'd do something like this (in Typescript): // typings for direction
enum Direction {
Top = 0,
TopRight = 1,
Right = 2,
BottomRight = 3,
Bottom = 4,
BottomLeft = 5,
Left = 6,
TopLeft = 7,
}
const editor = currentLayer.wangEdit(wangSet)
// setTerrain(x, y, color, direction)
editor.setTerrain(1, 1, 1, Direction.Right)
editor.setTerrain(3, 3, 1, Direction.Top)
editor.setTerrain(6, 3, 1, Direction.Left)
editor.setTerrain(10, 3, 1, Direction.Top)
editor.apply() I'm also marking this PR 'ready for review' since all the functionality is here and I've tested it to the best of my ability, and will need feedback from maintainers to make sure it's suitable to be merged. Specifically:
|
* Moved part of WangPainter back to WangBrush (BrushMode / TileMode). Tile mode could be re-introduced in WangPainter, if it is useful enough for a scripting API function. * Merged the remainder of WangPainter with WangFiller, renaming the result to WangPainter. It did not seem like there was any advantage to separating these. * Fixed leaking WangPainter instance in TileLayerWangEdit.
Even though I like WangPainter more, I'm sticking with WangFiller for now to keep the patch easier to read.
* Exposed WangId as Q_GADGET, so that in JS we can do WangId.Top, etc. to specify Wang indexes. Also made the masks enum available, though it may not have a use with the current API. A WangId value is currently useless on the script side. * Changed the parameter order to be consistently: position, index, color. * Fixed registration of TileLayerWangEdit (needed for Qt 5). * Added nullptr check to TileLayer.wangEdit. * Added error when calling TileLayerWangEdit.setEdge with non-edge index. * Added overloads to TileLayerWangEdit functions taking QPoint, which might be convenient in some cases. * Renamed TileLayerWangEdit.setTerrain to setWangIndex, to better reflect its low-level behavior.
Instead of WangId.Top, you now type WangIndex.Top in JS. This allowed for more intuitively using the WangIndex type as a function parameter. Also exposed the "corrections enabled" setting of WangFiller, which could be useful. Finally, updated the JS API docs.
* Destroy the TileLayerWangEdit when the EditableWangSet is deleted. This might be too early in some cases, since the WangSet might still be alive, but it's the safest option for now. * Support creating a TileLayerWangEdit even when there is no MapDocument. The layer still has to be part of a map though, since we need to know the orientation. A check has been added to wangEdit(). * Protect against issues when the originally referred map is deleted by copying its parameters (though not sure about the exact flow, which might already delete the EditableTileLayer).
@a-morphous I think this change is ready to go in now, but I will await your feedback. It could really use some more testing through the scripting APIs. I've mainly just tested that the existing Terrain Brush and Terrain Fill modes still work, that it compiles and runs against Qt 5 and such, and testing a very simple script: ws = tiled.mapEditor.currentWangSet
we = tiled.activeAsset.currentLayer.wangEdit(ws)
we.setCorner(5, 5, tiled.mapEditor.currentWangColorIndex)
we.apply() |
When corrections are disabled, and the Wang set is not complete, a matching tile is only placed when it will not make it impossible to place a matching tile on any of its neighboring cells. For incomplete Wang sets with transitions to empty, it can happen that a matching tile requires an empty WangId as neighbor. However, the empty WangId is never explicitly part of a WangSet. As such, we should probably never discard a match when it requires an empty neighbor.
Hmm, actually I saw the same problem in my local build on Linux. I've investigated the issue and I think I found the right solution. It seems to have been an existing bug, which only triggers for Wang sets that contain transitions to empty when used without having corrections enabled (and then only in cases like above, where two modified corners are close but not touching...). Good job catching this one! :-) |
Since the TileLayerWangEdit allows the WangFiller to be reused, we need to make sure it starts off with a clean state.
@a-morphous Thank you so much for getting this change started! I think it is a very useful addition which will hopefully also allow @eishiya to eliminate much of the code from https://github.com/eishiya/tiled-scripts/blob/main/Terrain%20Rectangle%20Tool/TerrainRectangleTool.js. I intend to make a Tiled 1.10.2 release soon, but I'll wait a bit on further feedback to allow for small fixes / additions. |
Is the new API documentation available anywhere easy-to-read, or is the diff of index.ts.d the best option right now? I'd love to try it out for that script and maybe some WIPs I have. Edit: Since TileLayerWangEdit is for a specific layer, making a Tool that previews the changes is rather annoying. I can't think of any way to use this for a Tool with a preview that doesn't also duplicate the entire working layer (since the affected area isn't known ahead of time in the case of incomplete terrain sets + corrections enabled) and then remove the unchanged tiles ): Perhaps there should be an alternative to |
Hmm, that makes a lot of sense and should be easy to add. Alternatively, maybe we could have an |
Something like |
I've re-generated the scripting docs, so they are now available at https://www.mapeditor.org/docs/scripting/interfaces/TileLayerWangEdit.html I'll look into adding the |
An initial version of this is now proposed at #3770. |
This adds a new function
setTerrain
toTileLayerEdit
, which can be called by extensions. ThesetTerrain
function operates very similarly to how the stamp brush works when working with wang terrain sets, and is meant for use cases like procedurally generating maps using terrain sets.This is implemented by creating a new
wangpainter
class thatwangbrush
now uses functionality from that handles the creation of wang tiles, which can also be used outside of the brush engine to programmatically set terrain. There's also now a newTileLayerWangEdit
class, which can be used to create terrain edits to a tile layer, similar to howTileLayerEdit
works for regular tile edits.This allows extensions to use the following API
setTerrain
's usage is fairly straightforward for corner wang tiles. For edge and edge/corner, the direction needs to be set because of how the stamp works, and it's not very intuitive.For instance, to get this shape:
You'd need to call the following code:
Any feedback at all is welcome, from the shape of the API to implementation details.
(Modified to reflect latest state of the pull request.)