Skip to content

Commit

Permalink
✨ Place filled rectangles of copies or tiles with Shift+Ctrl modifier…
Browse files Browse the repository at this point in the history
… in a room editor
  • Loading branch information
CosmoMyzrailGorynych committed Jul 19, 2024
1 parent 901ef4d commit 57e3820
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const deleteCopies: IRoomEditorInteraction<affixedData> = {
if (this.riotEditor.currentTool !== 'addCopies') {
return false;
}
return e.button === 0 && (e.ctrlKey || e.metaKey);
return e.button === 0 && (e.ctrlKey || e.metaKey) && !e.shiftKey;
},
listeners: {
pointerdown(e: PIXI.FederatedPointerEvent, riotTag, affixedData) {
Expand Down
15 changes: 10 additions & 5 deletions src/node_requires/roomEditor/interactions/copies/placeCopy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {soundbox} from '../../../3rdparty/soundbox';
import * as PIXI from 'pixi.js';

interface IAffixedData {
mode: 'free' | 'straight';
mode: 'free' | 'straight' | 'rect';
startPos: PIXI.IPoint;
prevPos: PIXI.IPoint;
prevLength: number;
Expand Down Expand Up @@ -57,13 +57,18 @@ export const placeCopy: IRoomEditorInteraction<IAffixedData> = {
pointerdown(e: PIXI.FederatedPointerEvent, roomTag, affixedData) {
this.compoundGhost.removeChildren();
affixedData.created = new Set();
// Two possible modes: placing in straight vertical/horizontal/diagonal lines
// Tree possible modes: placing in straight vertical/horizontal/diagonal lines,
// filling in a rectangle (shift + ctrl keys),
// and in a free form, like drawing with a brush.
// Straight method creates a ghost preview before actually creating all the copies,
// while the free form places copies as a user moves their cursor.
if (e.shiftKey) {
affixedData.mode = 'straight';
affixedData.prevLength = 1;
if (e.ctrlKey) {
affixedData.mode = 'rect';
} else {
affixedData.mode = 'straight';
affixedData.prevLength = 1;
}
} else {
affixedData.mode = 'free';
}
Expand Down Expand Up @@ -127,7 +132,7 @@ export const placeCopy: IRoomEditorInteraction<IAffixedData> = {
}
},
pointerup(e, roomTag, affixedData, callback) {
if (affixedData.mode === 'straight') {
if (affixedData.mode === 'straight' || affixedData.mode === 'rect') {
// Replace all the preview copies with real ones
for (const ghost of this.compoundGhost.children) {
const copy = createCopy(
Expand Down
140 changes: 85 additions & 55 deletions src/node_requires/roomEditor/interactions/placementCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {RoomEditor} from '..';
import * as PIXI from 'pixi.js';

type PlacementData = {
mode: 'free' | 'straight';
mode: 'free' | 'straight' | 'rect';
startPos: PIXI.IPoint;
prevPos: PIXI.IPoint;
prevLength: number;
Expand Down Expand Up @@ -84,8 +84,6 @@ export const calcPlacement = (
}

affixedData.prevPos = newPos;

// Straight-line placement
const startGrid = to(
affixedData.startPos,
affixedData.gridX,
Expand All @@ -98,62 +96,94 @@ export const calcPlacement = (
);
const dx = Math.abs(startGrid.x - endGrid.x),
dy = Math.abs(startGrid.y - endGrid.y);
const straightEndGrid: ISimplePoint = {
x: 0,
y: 0
};
const angle = Math.atan2(dy, dx);
if (Math.abs(angle) > Math.PI * 0.375 && Math.abs(angle) < Math.PI * 0.525) {
// Seems to be a vertical line
straightEndGrid.x = startGrid.x;
straightEndGrid.y = endGrid.y;
} else if (Math.abs(angle) < Math.PI * 0.125) {
// Seems to be a horizontal line
straightEndGrid.x = endGrid.x;
straightEndGrid.y = startGrid.y;
} else {
// It is more or so diagonal
const max = Math.max(dx, dy);
straightEndGrid.x = endGrid.x > startGrid.x ?
startGrid.x + max :
startGrid.x - max;
straightEndGrid.y = endGrid.y > startGrid.y ?
startGrid.y + max :
startGrid.y - max;

// Display a copy counter when placing items in a straight line or in a rectangle
editor.ghostCounter.visible = true;
editor.ghostCounter.scale.set(editor.camera.scale.x, editor.camera.scale.y);

// Straight-line placement
if (affixedData.mode === 'straight') {
const straightEndGrid: ISimplePoint = {
x: 0,
y: 0
};
const angle = Math.atan2(dy, dx);
if (Math.abs(angle) > Math.PI * 0.375 && Math.abs(angle) < Math.PI * 0.525) {
// Seems to be a vertical line
straightEndGrid.x = startGrid.x;
straightEndGrid.y = endGrid.y;
} else if (Math.abs(angle) < Math.PI * 0.125) {
// Seems to be a horizontal line
straightEndGrid.x = endGrid.x;
straightEndGrid.y = startGrid.y;
} else {
// It is more or so diagonal
const max = Math.max(dx, dy);
straightEndGrid.x = endGrid.x > startGrid.x ?
startGrid.x + max :
startGrid.x - max;
straightEndGrid.y = endGrid.y > startGrid.y ?
startGrid.y + max :
startGrid.y - max;
}
const incX = Math.sign(straightEndGrid.x - startGrid.x) * affixedData.stepX,
incY = Math.sign(straightEndGrid.y - startGrid.y) * affixedData.stepY;
const l = Math.max(dx / affixedData.stepX, dy / affixedData.stepY);
const ghosts = [];
// Calculate ghost positions
for (let i = 0, {x, y} = startGrid;
i < l;
i++, x += incX, y += incY
) {
const localPos = from({
x: x + incX,
y: y + incY
}, affixedData.gridX, affixedData.gridY);
ghosts.push(localPos);
}

const count = ghosts.length + 1;
if (editor.ghostCounter.text !== count.toString()) {
editor.ghostCounter.text = count;
}
if (ghosts.length > 0) {
const [firstGhost] = ghosts,
lastGhost = ghosts[ghosts.length - 1];
editor.ghostCounter.position.set(
(firstGhost.x + lastGhost.x) / 2,
(firstGhost.y + lastGhost.y) / 2
);
} else {
editor.ghostCounter.position.set(affixedData.startPos.x, affixedData.startPos.y);
}

return ghosts;
}
const incX = Math.sign(straightEndGrid.x - startGrid.x) * affixedData.stepX,
incY = Math.sign(straightEndGrid.y - startGrid.y) * affixedData.stepY;
const l = Math.max(dx / affixedData.stepX, dy / affixedData.stepY);

// Rectangle fill mode
const left = Math.min(startGrid.x, endGrid.x),
top = Math.min(startGrid.y, endGrid.y),
right = Math.max(startGrid.x, endGrid.x),
bottom = Math.max(startGrid.y, endGrid.y);
const ghosts = [];
// Calculate ghost positions
for (let i = 0, {x, y} = startGrid;
i < l;
i++, x += incX, y += incY
) {
const localPos = from({
x: x + incX,
y: y + incY
}, affixedData.gridX, affixedData.gridY);
ghosts.push(localPos);
for (let x = left; x <= right; x++) {
for (let y = top; y <= bottom; y++) {
const localPos = from({
x,
y
}, affixedData.gridX, affixedData.gridY);
ghosts.push(localPos);
}
}

// Display a copy counter when placing items in a straight line
editor.ghostCounter.visible = true;
const count = ghosts.length + 1;
if (editor.ghostCounter.text !== count.toString()) {
editor.ghostCounter.text = count;
}
if (ghosts.length > 0) {
const [firstGhost] = ghosts,
lastGhost = ghosts[ghosts.length - 1];
editor.ghostCounter.position.set(
(firstGhost.x + lastGhost.x) / 2,
(firstGhost.y + lastGhost.y) / 2
);
} else {
editor.ghostCounter.position.set(affixedData.startPos.x, affixedData.startPos.y);
const text = `${right - left + 1}×${bottom - top + 1}`;
if (editor.ghostCounter.text !== text) {
editor.ghostCounter.text = text;
}
editor.ghostCounter.scale.set(editor.camera.scale.x, editor.camera.scale.y);

const globalCenter = from({
x: (left + right) / 2,
y: (top + bottom) / 2
}, affixedData.gridX, affixedData.gridY);
editor.ghostCounter.position.set(globalCenter.x, globalCenter.y);
return ghosts;
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const deleteTiles: IRoomEditorInteraction<affixedData> = {
if (this.riotEditor.currentTool !== 'addTiles' || !this.riotEditor.currentTileLayer) {
return false;
}
return e.button === 0 && (e.ctrlKey || e.metaKey);
return e.button === 0 && (e.ctrlKey || e.metaKey) && !e.shiftKey;
},
listeners: {
pointerdown(e: PIXI.FederatedPointerEvent, riotTag, affixedData) {
Expand Down
19 changes: 12 additions & 7 deletions src/node_requires/roomEditor/interactions/tiles/placeTile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {soundbox} from '../../../3rdparty/soundbox';
import {getLanguageJSON} from '../../../i18n';

interface IAffixedData {
mode: 'free' | 'straight';
mode: 'free' | 'straight' | 'rect';
startPos: PIXI.IPoint;
prevPos: PIXI.IPoint;
prevLength: number;
Expand Down Expand Up @@ -86,16 +86,21 @@ export const placeTile: IRoomEditorInteraction<IAffixedData> = {
return Boolean(riotTag.tilePatch?.texture);
},
listeners: {
pointerdown(e, riotTag, affixedData) {
pointerdown(e: PIXI.FederatedPointerEvent, riotTag, affixedData) {
this.compoundGhost.removeChildren();
affixedData.created = new Set();
// Two possible modes: placing in straight vertical/horizontal/diagonal lines
// Tree possible modes: placing in straight vertical/horizontal/diagonal lines,
// filling in a rectangle (shift + ctrl keys),
// and in a free form, like drawing with a brush.
// Straight method creates a ghost preview before actually creating all the copies,
// while the free form places copies as a user moves their cursor.
if ((e as PIXI.FederatedPointerEvent).shiftKey) {
affixedData.mode = 'straight';
affixedData.prevLength = 1;
if (e.shiftKey) {
if (e.ctrlKey) {
affixedData.mode = 'rect';
} else {
affixedData.mode = 'straight';
affixedData.prevLength = 1;
}
} else {
affixedData.mode = 'free';
}
Expand Down Expand Up @@ -171,7 +176,7 @@ export const placeTile: IRoomEditorInteraction<IAffixedData> = {
}
},
pointerup(e, riotTag, affixedData, callback) {
if (affixedData.mode === 'straight') {
if (affixedData.mode === 'straight' || affixedData.mode === 'rect') {
// Replace all the preview copies with real ones
for (const ghost of this.compoundGhost.children) {
const newTiles = createTilePatch(
Expand Down
8 changes: 7 additions & 1 deletion src/riotTags/editors/room-editor/room-editor.tag
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,14 @@ room-editor.aPanel.aView(data-hotkey-scope="{asset.uid}")
this.freePlacementMode = true;
e.preventDefault();
} else if (e.key === 'Control' || e.key === 'Meta') {
this.controlMode = true;
if (e.shiftKey) {
this.controlMode = false;
} else {
this.controlMode = true;
}
e.preventDefault();
} else if (e.key === 'Shift') {
this.controlMode = false;
}
};
const modifiersUpListener = e => {
Expand Down

0 comments on commit 57e3820

Please sign in to comment.