Skip to content

Commit dbb586a

Browse files
authored
Improve tilemap edition with new tools (#8264)
- Replace the existing Paint Brush with the Rectangle tool. - Implement a proper Freehand Brush tool. It works with multiple tiles and single tile. - Add a Picker tool (and it remember your previous tool before picking, and restore it once picking is done) - Add a Bucket Fill tool: it can replace existing tiles or empty spaces. Filling is limited to the tilemap bounds within the canvas. - Optimize drawing performance: reduce and limit the number of points stored while the cursor moves. - Visually separate the tools and the (flips) modes.
1 parent 6bb56c6 commit dbb586a

File tree

8 files changed

+680
-116
lines changed

8 files changed

+680
-116
lines changed

newIDE/app/src/InstancesEditor/ClickInterceptor.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,42 @@ class ClickInterceptor {
151151
deviceY
152152
);
153153

154-
if (pointerPathCoordinates[1]) {
154+
const tileMapTileSelection = this.getTileMapTileSelection();
155+
156+
// For floodfill and picker, keep only a single coordinate (no drag path).
157+
if (
158+
tileMapTileSelection &&
159+
(tileMapTileSelection.kind === 'floodfill' ||
160+
tileMapTileSelection.kind === 'picker')
161+
) {
162+
pointerPathCoordinates[0] = {
163+
x: sceneCoordinates[0],
164+
y: sceneCoordinates[1],
165+
};
166+
return;
167+
}
168+
169+
if (tileMapTileSelection && tileMapTileSelection.kind === 'freehand') {
170+
const lastPoint =
171+
pointerPathCoordinates[pointerPathCoordinates.length - 1];
172+
if (lastPoint) {
173+
// Prevent near-duplicate points within minimum distance
174+
const MIN_DISTANCE = 2; // pixels
175+
const dx = sceneCoordinates[0] - lastPoint.x;
176+
const dy = sceneCoordinates[1] - lastPoint.y;
177+
if (Math.abs(dx) < MIN_DISTANCE && Math.abs(dy) < MIN_DISTANCE) {
178+
return;
179+
}
180+
}
181+
// Limit path points to prevent excessive memory usage
182+
const MAX_PATH_POINTS = 10000;
183+
if (pointerPathCoordinates.length < MAX_PATH_POINTS) {
184+
pointerPathCoordinates.push({
185+
x: sceneCoordinates[0],
186+
y: sceneCoordinates[1],
187+
});
188+
}
189+
} else if (pointerPathCoordinates[1]) {
155190
pointerPathCoordinates[1] = {
156191
x: sceneCoordinates[0],
157192
y: sceneCoordinates[1],

newIDE/app/src/InstancesEditor/TileMapPaintingPreview.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import PixiResourcesLoader from '../ObjectsRendering/PixiResourcesLoader';
77
import ViewPosition from './ViewPosition';
88
import RenderedInstance from '../ObjectsRendering/Renderers/RenderedInstance';
99
import Rendered3DInstance from '../ObjectsRendering/Renderers/Rendered3DInstance';
10-
import { type TileMapTileSelection } from './TileSetVisualizer';
10+
import {
11+
type TileMapTileSelection,
12+
getTileMapPaintingSelection,
13+
} from './TileSetVisualizer';
1114
import { AffineTransformation } from '../Utils/AffineTransformation';
1215
import {
1316
getTileSet,
@@ -159,6 +162,7 @@ class TileMapPaintingPreview {
159162
try {
160163
const texture = new PIXI.Texture(atlasTexture, rect);
161164
this.cache.set(cacheKey, texture);
165+
return texture;
162166
} catch (error) {
163167
console.error(`Tile could not be extracted from atlas texture:`, error);
164168
return PixiResourcesLoader.getInvalidPIXITexture();
@@ -261,6 +265,7 @@ class TileMapPaintingPreview {
261265
return null;
262266
}
263267
const container = new PIXI.Container();
268+
const paintingSelection = getTileMapPaintingSelection(tileMapTileSelection);
264269
tilesCoordinatesInTileMapGrid.forEach(tilesCoordinates => {
265270
const {
266271
bottomRightCorner,
@@ -271,7 +276,7 @@ class TileMapPaintingPreview {
271276
if (isBadlyConfigured) {
272277
texture = PixiResourcesLoader.getInvalidPIXITexture();
273278
} else {
274-
if (tileMapTileSelection.kind === 'rectangle' && tileCoordinates) {
279+
if (paintingSelection && tileCoordinates) {
275280
texture = this._getTextureInAtlas({
276281
tileSet,
277282
...tileCoordinates,
@@ -290,8 +295,12 @@ class TileMapPaintingPreview {
290295
texture,
291296
scaleX,
292297
scaleY,
293-
flipHorizontally: tileMapTileSelection.flipHorizontally || false,
294-
flipVertically: tileMapTileSelection.flipVertically || false,
298+
flipHorizontally: paintingSelection
299+
? paintingSelection.flipHorizontally
300+
: false,
301+
flipVertically: paintingSelection
302+
? paintingSelection.flipVertically
303+
: false,
295304
tileSize,
296305
angle: instance.getAngle(),
297306
});
@@ -319,10 +328,11 @@ class TileMapPaintingPreview {
319328
if (!object || object.getType() !== 'TileMap::SimpleTileMap') return;
320329
const tileSet = getTileSet(object);
321330
const isBadlyConfigured = isTileSetBadlyConfigured(tileSet);
331+
const paintingSelection = getTileMapPaintingSelection(tileMapTileSelection);
322332

323333
if (
324334
isBadlyConfigured ||
325-
tileMapTileSelection.kind === 'rectangle' ||
335+
paintingSelection ||
326336
tileMapTileSelection.kind === 'erase'
327337
) {
328338
const container = this._getPreviewSprites({

0 commit comments

Comments
 (0)