Skip to content

Commit 9d2a254

Browse files
me4502lewisjb
authored andcommitted
Add morph brush (#2078)
* Add erosion brush * Rename to the Morph brush, and add Erode and Dilate presets Co-authored-by: Lewis B <[email protected]>
1 parent 81b7a7e commit 9d2a254

File tree

4 files changed

+242
-1
lines changed

4 files changed

+242
-1
lines changed

worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3740,6 +3740,159 @@ protected BiomeType getBiome(int x, int y, int z, BiomeType defaultBiomeType) {
37403740
return changed;
37413741
}
37423742

3743+
public int morph(BlockVector3 position, double brushSize, int minErodeFaces, int numErodeIterations, int minDilateFaces, int numDilateIterations) throws MaxChangedBlocksException {
3744+
int ceilBrushSize = (int) Math.ceil(brushSize);
3745+
int bufferSize = ceilBrushSize * 2 + 3; // + 1 due to checking the adjacent blocks, plus the 0th block
3746+
// Store block states in a 3d array so we can do multiple mutations then commit.
3747+
// Two are required as for each iteration, one is "current" and the other is "new"
3748+
BlockState[][][] currentBuffer = new BlockState[bufferSize][bufferSize][bufferSize];
3749+
BlockState[][][] nextBuffer = new BlockState[bufferSize][bufferSize][bufferSize];
3750+
3751+
// Simply used for swapping the two
3752+
BlockState[][][] tmp;
3753+
3754+
// Load into buffer
3755+
for (int x = 0; x < bufferSize; x++) {
3756+
for (int y = 0; y < bufferSize; y++) {
3757+
for (int z = 0; z < bufferSize; z++) {
3758+
BlockState blockState = getBlock(position.add(x - ceilBrushSize - 1, y - ceilBrushSize - 1, z - ceilBrushSize - 1));
3759+
currentBuffer[x][y][z] = blockState;
3760+
nextBuffer[x][y][z] = blockState;
3761+
}
3762+
}
3763+
}
3764+
3765+
double brushSizeSq = brushSize * brushSize;
3766+
Map<BlockState, Integer> blockStateFrequency = new HashMap<>();
3767+
int totalFaces;
3768+
int highestFreq;
3769+
BlockState highestState;
3770+
for (int i = 0; i < numErodeIterations; i++) {
3771+
for (int x = 0; x <= ceilBrushSize * 2; x++) {
3772+
int realX = x - ceilBrushSize;
3773+
int xsqr = realX * realX;
3774+
for (int y = 0; y <= ceilBrushSize * 2; y++) {
3775+
int realY = y - ceilBrushSize;
3776+
int ysqr = realY * realY;
3777+
for (int z = 0; z <= ceilBrushSize * 2; z++) {
3778+
int realZ = z - ceilBrushSize;
3779+
int zsqr = realZ * realZ;
3780+
if (xsqr + ysqr + zsqr > brushSizeSq) {
3781+
continue;
3782+
}
3783+
3784+
// Copy across changes
3785+
nextBuffer[x + 1][y + 1][z + 1] = currentBuffer[x + 1][y + 1][z + 1];
3786+
3787+
BlockState blockState = currentBuffer[x + 1][y + 1][z + 1];
3788+
3789+
if (blockState.getBlockType().getMaterial().isLiquid() || blockState.getBlockType().getMaterial().isAir()) {
3790+
continue;
3791+
}
3792+
3793+
blockStateFrequency.clear();
3794+
totalFaces = 0;
3795+
highestFreq = 0;
3796+
highestState = blockState;
3797+
for (BlockVector3 vec3 : recurseDirections) {
3798+
BlockState adj = currentBuffer[x + 1 + vec3.x()][y + 1 + vec3.y()][z + 1 + vec3.z()];
3799+
3800+
if (!adj.getBlockType().getMaterial().isLiquid() && !adj.getBlockType().getMaterial().isAir()) {
3801+
continue;
3802+
}
3803+
3804+
totalFaces++;
3805+
int newFreq = blockStateFrequency.getOrDefault(adj, 0) + 1;
3806+
blockStateFrequency.put(adj, newFreq);
3807+
3808+
if (newFreq > highestFreq) {
3809+
highestFreq = newFreq;
3810+
highestState = adj;
3811+
}
3812+
}
3813+
3814+
if (totalFaces >= minErodeFaces) {
3815+
nextBuffer[x + 1][y + 1][z + 1] = highestState;
3816+
}
3817+
}
3818+
}
3819+
}
3820+
// Swap current and next
3821+
tmp = currentBuffer;
3822+
currentBuffer = nextBuffer;
3823+
nextBuffer = tmp;
3824+
}
3825+
3826+
for (int i = 0; i < numDilateIterations; i++) {
3827+
for (int x = 0; x <= ceilBrushSize * 2; x++) {
3828+
int realX = x - ceilBrushSize;
3829+
int xsqr = realX * realX;
3830+
for (int y = 0; y <= ceilBrushSize * 2; y++) {
3831+
int realY = y - ceilBrushSize;
3832+
int ysqr = realY * realY;
3833+
for (int z = 0; z <= ceilBrushSize * 2; z++) {
3834+
int realZ = z - ceilBrushSize;
3835+
int zsqr = realZ * realZ;
3836+
if (xsqr + ysqr + zsqr > brushSizeSq) {
3837+
continue;
3838+
}
3839+
3840+
// Copy across changes
3841+
nextBuffer[x + 1][y + 1][z + 1] = currentBuffer[x + 1][y + 1][z + 1];
3842+
3843+
BlockState blockState = currentBuffer[x + 1][y + 1][z + 1];
3844+
// Needs to be empty
3845+
if (!blockState.getBlockType().getMaterial().isLiquid() && !blockState.getBlockType().getMaterial().isAir()) {
3846+
continue;
3847+
}
3848+
3849+
blockStateFrequency.clear();
3850+
totalFaces = 0;
3851+
highestFreq = 0;
3852+
highestState = blockState;
3853+
for (BlockVector3 vec3 : recurseDirections) {
3854+
BlockState adj = currentBuffer[x + 1 + vec3.x()][y + 1 + vec3.y()][z + 1 + vec3.z()];
3855+
if (adj.getBlockType().getMaterial().isLiquid() || adj.getBlockType().getMaterial().isAir()) {
3856+
continue;
3857+
}
3858+
3859+
totalFaces++;
3860+
int newFreq = blockStateFrequency.getOrDefault(adj, 0) + 1;
3861+
blockStateFrequency.put(adj, newFreq);
3862+
3863+
if (newFreq > highestFreq) {
3864+
highestFreq = newFreq;
3865+
highestState = adj;
3866+
}
3867+
}
3868+
3869+
if (totalFaces >= minDilateFaces) {
3870+
nextBuffer[x + 1][y + 1][z + 1] = highestState;
3871+
}
3872+
}
3873+
}
3874+
}
3875+
// Swap current and next
3876+
tmp = currentBuffer;
3877+
currentBuffer = nextBuffer;
3878+
nextBuffer = tmp;
3879+
}
3880+
3881+
// Commit to world
3882+
int changed = 0;
3883+
for (int x = 0; x < bufferSize; x++) {
3884+
for (int y = 0; y < bufferSize; y++) {
3885+
for (int z = 0; z < bufferSize; z++) {
3886+
if (setBlock(position.add(x - ceilBrushSize - 1, y - ceilBrushSize - 1, z - ceilBrushSize - 1), currentBuffer[x][y][z])) {
3887+
changed++;
3888+
}
3889+
}
3890+
}
3891+
}
3892+
3893+
return changed;
3894+
}
3895+
37433896
private static final BlockVector3[] recurseDirections = {
37443897
Direction.NORTH.toBlockVector(),
37453898
Direction.EAST.toBlockVector(),
@@ -4060,4 +4213,5 @@ public int makeBlob(
40604213
return changes;
40614214
}
40624215
//FAWE end
4216+
40634217
}

worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import com.sk89q.worldedit.command.tool.brush.GravityBrush;
7878
import com.sk89q.worldedit.command.tool.brush.HollowCylinderBrush;
7979
import com.sk89q.worldedit.command.tool.brush.HollowSphereBrush;
80+
import com.sk89q.worldedit.command.tool.brush.MorphBrush;
8081
import com.sk89q.worldedit.command.tool.brush.OperationFactoryBrush;
8182
import com.sk89q.worldedit.command.tool.brush.SmoothBrush;
8283
import com.sk89q.worldedit.command.tool.brush.SnowSmoothBrush;
@@ -1576,6 +1577,46 @@ public void biome(
15761577
player.printInfo(TranslatableComponent.of("worldedit.setbiome.warning"));
15771578
}
15781579

1580+
@Command(
1581+
name = "morph",
1582+
desc = "Morph brush, morphs blocks in the area"
1583+
)
1584+
@CommandPermissions("worldedit.brush.morph")
1585+
public void morph(Player player, LocalSession session,
1586+
@Arg(desc = "The size of the brush", def = "5")
1587+
double brushSize,
1588+
@Arg(desc = "Minimum number of faces for erosion", def = "3")
1589+
int minErodeFaces,
1590+
@Arg(desc = "Erode iterations", def = "1")
1591+
int numErodeIterations,
1592+
@Arg(desc = "Minimum number of faces for dilation", def = "3")
1593+
int minDilateFaces,
1594+
@Arg(desc = "Dilate iterations", def = "1")
1595+
int numDilateIterations) throws WorldEditException {
1596+
worldEdit.checkMaxBrushRadius(brushSize);
1597+
BrushTool tool = session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType());
1598+
tool.setSize(brushSize);
1599+
tool.setBrush(new MorphBrush(minErodeFaces, numErodeIterations, minDilateFaces, numDilateIterations), "worldedit.brush.morph");
1600+
1601+
player.printInfo(TranslatableComponent.of("worldedit.brush.morph.equip", TextComponent.of((int) brushSize)));
1602+
}
1603+
1604+
@Command(
1605+
name = "dilate",
1606+
desc = "Dilate preset for morph brush, dilates blocks in the area"
1607+
)
1608+
@CommandPermissions("worldedit.brush.morph")
1609+
public void dilate(Player player, LocalSession session,
1610+
@Arg(desc = "The size of the brush", def = "5")
1611+
double brushSize) throws WorldEditException {
1612+
worldEdit.checkMaxBrushRadius(brushSize);
1613+
BrushTool tool = session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType());
1614+
tool.setSize(brushSize);
1615+
tool.setBrush(new MorphBrush(5, 1, 2, 1), "worldedit.brush.morph");
1616+
1617+
player.printInfo(TranslatableComponent.of("worldedit.brush.morph.equip", TextComponent.of((int) brushSize)));
1618+
}
1619+
15791620
//FAWE start
15801621
public BrushSettings process(Player player, Arguments arguments, BrushSettings settings)
15811622
throws WorldEditException {
@@ -1620,7 +1661,6 @@ public BrushSettings set(InjectedValueAccess context, Brush brush, String permis
16201661
}
16211662
//FAWE end
16221663

1623-
16241664
static void setOperationBasedBrush(Player player, LocalSession session, double radius,
16251665
Contextual<? extends Operation> factory,
16261666
RegionFactory shape,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* WorldEdit, a Minecraft world manipulation toolkit
3+
* Copyright (C) sk89q <http://www.sk89q.com>
4+
* Copyright (C) WorldEdit team and contributors
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
package com.sk89q.worldedit.command.tool.brush;
21+
22+
import com.sk89q.worldedit.EditSession;
23+
import com.sk89q.worldedit.MaxChangedBlocksException;
24+
import com.sk89q.worldedit.function.pattern.Pattern;
25+
import com.sk89q.worldedit.math.BlockVector3;
26+
27+
public class MorphBrush implements Brush {
28+
29+
private final int minErodeFaces;
30+
private final int numErodeIterations;
31+
private final int minDilateFaces;
32+
private final int minDilateIterations;
33+
34+
public MorphBrush(int minErodeFaces, int numErodeIterations, int minDilateFaces, int minDilateIterations) {
35+
this.minErodeFaces = minErodeFaces;
36+
this.numErodeIterations = numErodeIterations;
37+
this.minDilateFaces = minDilateFaces;
38+
this.minDilateIterations = minDilateIterations;
39+
}
40+
41+
@Override
42+
public void build(EditSession editSession, BlockVector3 position, Pattern pattern, double size) throws MaxChangedBlocksException {
43+
editSession.morph(position, size, this.minErodeFaces, this.numErodeIterations, this.minDilateFaces, this.minDilateIterations);
44+
}
45+
46+
}

worldedit-core/src/main/resources/lang/strings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@
295295
"worldedit.brush.gravity.equip": "Gravity brush equipped ({0}).",
296296
"worldedit.brush.butcher.equip": "Butcher brush equipped ({0}).",
297297
"worldedit.brush.operation.equip": "Set brush to {0}.",
298+
"worldedit.brush.morph.equip": "Morph brush shape equipped: {0}.",
298299
"worldedit.brush.none.equip": "Brush unbound from your current item.",
299300
"worldedit.brush.none.equipped": "You have no brush bound to your current item. Try /brush sphere for a basic brush.",
300301
"worldedit.setbiome.changed": "Biomes were changed in {0} columns. You may have to rejoin your game (or close and reopen your world) to see a change.",

0 commit comments

Comments
 (0)