Skip to content

Commit

Permalink
Add morph brush (EngineHub#2078)
Browse files Browse the repository at this point in the history
* Add erosion brush

* Rename to the Morph brush, and add Erode and Dilate presets

Co-authored-by: Lewis B <[email protected]>
  • Loading branch information
me4502 and lewisjb authored Jun 4, 2022
1 parent 88db45b commit 6e72ee0
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 0 deletions.
147 changes: 147 additions & 0 deletions worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -2687,6 +2687,153 @@ protected BiomeType getBiome(int x, int y, int z, BiomeType defaultBiomeType) {
return changed;
}

public int morph(BlockVector3 position, double brushSize, int minErodeFaces, int numErodeIterations, int minDilateFaces, int numDilateIterations) throws MaxChangedBlocksException {
int ceilBrushSize = (int) Math.ceil(brushSize);
int bufferSize = ceilBrushSize * 2 + 3; // + 1 due to checking the adjacent blocks, plus the 0th block
// Store block states in a 3d array so we can do multiple mutations then commit.
// Two are required as for each iteration, one is "current" and the other is "new"
BlockState[][][] currentBuffer = new BlockState[bufferSize][bufferSize][bufferSize];
BlockState[][][] nextBuffer = new BlockState[bufferSize][bufferSize][bufferSize];

// Simply used for swapping the two
BlockState[][][] tmp;

// Load into buffer
for (int x = 0; x < bufferSize; x++) {
for (int y = 0; y < bufferSize; y++) {
for (int z = 0; z < bufferSize; z++) {
BlockState blockState = getBlock(position.add(x - ceilBrushSize - 1, y - ceilBrushSize - 1, z - ceilBrushSize - 1));
currentBuffer[x][y][z] = blockState;
nextBuffer[x][y][z] = blockState;
}
}
}

double brushSizeSq = brushSize * brushSize;
Map<BlockState, Integer> blockStateFrequency = new HashMap<>();
int totalFaces;
int highestFreq;
BlockState highestState;
for (int i = 0; i < numErodeIterations; i++) {
for (int x = 0; x <= ceilBrushSize * 2; x++) {
for (int y = 0; y <= ceilBrushSize * 2; y++) {
for (int z = 0; z <= ceilBrushSize * 2; z++) {
int realX = x - ceilBrushSize;
int realY = y - ceilBrushSize;
int realZ = z - ceilBrushSize;
if (lengthSq(realX, realY, realZ) > brushSizeSq) {
continue;
}

// Copy across changes
nextBuffer[x + 1][y + 1][z + 1] = currentBuffer[x + 1][y + 1][z + 1];

BlockState blockState = currentBuffer[x + 1][y + 1][z + 1];

if (blockState.getBlockType().getMaterial().isLiquid() || blockState.getBlockType().getMaterial().isAir()) {
continue;
}

blockStateFrequency.clear();
totalFaces = 0;
highestFreq = 0;
highestState = blockState;
for (BlockVector3 vec3 : recurseDirections) {
BlockState adj = currentBuffer[x + 1 + vec3.getX()][y + 1 + vec3.getY()][z + 1 + vec3.getZ()];

if (!adj.getBlockType().getMaterial().isLiquid() && !adj.getBlockType().getMaterial().isAir()) {
continue;
}

totalFaces++;
int newFreq = blockStateFrequency.getOrDefault(adj, 0) + 1;
blockStateFrequency.put(adj, newFreq);

if (newFreq > highestFreq) {
highestFreq = newFreq;
highestState = adj;
}
}

if (totalFaces >= minErodeFaces) {
nextBuffer[x + 1][y + 1][z + 1] = highestState;
}
}
}
}
// Swap current and next
tmp = currentBuffer;
currentBuffer = nextBuffer;
nextBuffer = tmp;
}

for (int i = 0; i < numDilateIterations; i++) {
for (int x = 0; x <= ceilBrushSize * 2; x++) {
for (int y = 0; y <= ceilBrushSize * 2; y++) {
for (int z = 0; z <= ceilBrushSize * 2; z++) {
int realX = x - ceilBrushSize;
int realY = y - ceilBrushSize;
int realZ = z - ceilBrushSize;
if (lengthSq(realX, realY, realZ) > brushSizeSq) {
continue;
}

// Copy across changes
nextBuffer[x + 1][y + 1][z + 1] = currentBuffer[x + 1][y + 1][z + 1];

BlockState blockState = currentBuffer[x + 1][y + 1][z + 1];
// Needs to be empty
if (!blockState.getBlockType().getMaterial().isLiquid() && !blockState.getBlockType().getMaterial().isAir()) {
continue;
}

blockStateFrequency.clear();
totalFaces = 0;
highestFreq = 0;
highestState = blockState;
for (BlockVector3 vec3 : recurseDirections) {
BlockState adj = currentBuffer[x + 1 + vec3.getX()][y + 1 + vec3.getY()][z + 1 + vec3.getZ()];
if (adj.getBlockType().getMaterial().isLiquid() || adj.getBlockType().getMaterial().isAir()) {
continue;
}

totalFaces++;
int newFreq = blockStateFrequency.getOrDefault(adj, 0) + 1;
blockStateFrequency.put(adj, newFreq);

if (newFreq > highestFreq) {
highestFreq = newFreq;
highestState = adj;
}
}

if (totalFaces >= minDilateFaces) {
nextBuffer[x + 1][y + 1][z + 1] = highestState;
}
}
}
}
// Swap current and next
tmp = currentBuffer;
currentBuffer = nextBuffer;
nextBuffer = tmp;
}

// Commit to world
int changed = 0;
for (int x = 0; x < bufferSize; x++) {
for (int y = 0; y < bufferSize; y++) {
for (int z = 0; z < bufferSize; z++) {
if (setBlock(position.add(x - ceilBrushSize - 1, y - ceilBrushSize - 1, z - ceilBrushSize - 1), currentBuffer[x][y][z])) {
changed++;
}
}
}
}

return changed;
}

private static final BlockVector3[] recurseDirections = {
Direction.NORTH.toBlockVector(),
Direction.EAST.toBlockVector(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.sk89q.worldedit.command.tool.brush.HollowCylinderBrush;
import com.sk89q.worldedit.command.tool.brush.HollowSphereBrush;
import com.sk89q.worldedit.command.tool.brush.ImageHeightmapBrush;
import com.sk89q.worldedit.command.tool.brush.MorphBrush;
import com.sk89q.worldedit.command.tool.brush.OperationFactoryBrush;
import com.sk89q.worldedit.command.tool.brush.SmoothBrush;
import com.sk89q.worldedit.command.tool.brush.SnowSmoothBrush;
Expand Down Expand Up @@ -581,6 +582,62 @@ public void biome(Player player, LocalSession localSession,
player.printInfo(TranslatableComponent.of("worldedit.setbiome.warning"));
}

@Command(
name = "morph",
desc = "Morph brush, morphs blocks in the area"
)
@CommandPermissions("worldedit.brush.morph")
public void morph(Player player, LocalSession session,
@Arg(desc = "The size of the brush", def = "5")
double brushSize,
@Arg(desc = "Minimum number of faces for erosion", def = "3")
int minErodeFaces,
@Arg(desc = "Erode iterations", def = "1")
int numErodeIterations,
@Arg(desc = "Minimum number of faces for dilation", def = "3")
int minDilateFaces,
@Arg(desc = "Dilate iterations", def = "1")
int numDilateIterations) throws WorldEditException {
worldEdit.checkMaxBrushRadius(brushSize);
BrushTool tool = session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType());
tool.setSize(brushSize);
tool.setBrush(new MorphBrush(minErodeFaces, numErodeIterations, minDilateFaces, numDilateIterations), "worldedit.brush.morph");

player.printInfo(TranslatableComponent.of("worldedit.brush.morph.equip", TextComponent.of((int) brushSize)));
}

@Command(
name = "erode",
desc = "Erode preset for morph brush, erodes blocks in the area"
)
@CommandPermissions("worldedit.brush.morph")
public void erode(Player player, LocalSession session,
@Arg(desc = "The size of the brush", def = "5")
double brushSize) throws WorldEditException {
worldEdit.checkMaxBrushRadius(brushSize);
BrushTool tool = session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType());
tool.setSize(brushSize);
tool.setBrush(new MorphBrush(2, 1, 5, 1), "worldedit.brush.morph");

player.printInfo(TranslatableComponent.of("worldedit.brush.morph.equip", TextComponent.of((int) brushSize)));
}

@Command(
name = "dilate",
desc = "Dilate preset for morph brush, dilates blocks in the area"
)
@CommandPermissions("worldedit.brush.morph")
public void dilate(Player player, LocalSession session,
@Arg(desc = "The size of the brush", def = "5")
double brushSize) throws WorldEditException {
worldEdit.checkMaxBrushRadius(brushSize);
BrushTool tool = session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType());
tool.setSize(brushSize);
tool.setBrush(new MorphBrush(5, 1, 2, 1), "worldedit.brush.morph");

player.printInfo(TranslatableComponent.of("worldedit.brush.morph.equip", TextComponent.of((int) brushSize)));
}

static void setOperationBasedBrush(Player player, LocalSession session, double radius,
Contextual<? extends Operation> factory,
RegionFactory shape,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.sk89q.worldedit.command.tool.brush;

import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.math.BlockVector3;

public class MorphBrush implements Brush {

private final int minErodeFaces;
private final int numErodeIterations;
private final int minDilateFaces;
private final int minDilateIterations;

public MorphBrush(int minErodeFaces, int numErodeIterations, int minDilateFaces, int minDilateIterations) {
this.minErodeFaces = minErodeFaces;
this.numErodeIterations = numErodeIterations;
this.minDilateFaces = minDilateFaces;
this.minDilateIterations = minDilateIterations;
}

@Override
public void build(EditSession editSession, BlockVector3 position, Pattern pattern, double size) throws MaxChangedBlocksException {
editSession.morph(position, size, this.minErodeFaces, this.numErodeIterations, this.minDilateFaces, this.minDilateIterations);
}

}
1 change: 1 addition & 0 deletions worldedit-core/src/main/resources/lang/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"worldedit.brush.operation.equip": "Set brush to {0}.",
"worldedit.brush.heightmap.equip": "Heightmap brush equipped ({0}).",
"worldedit.brush.heightmap.unknown": "Unknown heightmap brush: {0}.",
"worldedit.brush.morph.equip": "Morph brush shape equipped: {0}.",
"worldedit.brush.none.equip": "Brush unbound from your current item.",

"worldedit.setbiome.changed": "Biomes were changed for approximately {0} blocks.",
Expand Down

0 comments on commit 6e72ee0

Please sign in to comment.