Cleaned up generation filter code

This commit is contained in:
Anuken
2019-07-25 19:18:04 -04:00
parent b59b839029
commit c209528271
13 changed files with 103 additions and 94 deletions
@@ -0,0 +1,46 @@
package io.anuke.mindustry.maps.filters;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.maps.filters.FilterOption.BlockOption;
import io.anuke.mindustry.maps.filters.FilterOption.SliderOption;
import io.anuke.mindustry.world.Block;
import static io.anuke.mindustry.maps.filters.FilterOption.floorsOnly;
public class BlendFilter extends GenerateFilter{
float radius = 2f;
Block flooronto = Blocks.stone, floor = Blocks.ice;
{
options(
new SliderOption("radius", () -> radius, f -> radius = f, 1f, 10f),
new BlockOption("flooronto", () -> flooronto, b -> flooronto = b, floorsOnly),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly)
);
}
@Override
public void apply(){
if(in.floor == flooronto) return;
int rad = (int)radius;
boolean found = false;
outer:
for(int x = -rad; x <= rad; x++){
for(int y = -rad; y <= rad; y++){
if(Mathf.dst2(x, y) > rad*rad) continue;
if(in.tile(in.x + x, in.y + y).floor == flooronto.id){
found = true;
break outer;
}
}
}
if(found){
in.floor = floor;
}
}
}
@@ -0,0 +1,27 @@
package io.anuke.mindustry.maps.filters;
import io.anuke.mindustry.editor.MapGenerateDialog.GenTile;
import io.anuke.mindustry.maps.filters.FilterOption.SliderOption;
import io.anuke.mindustry.world.blocks.Floor;
import static io.anuke.mindustry.Vars.content;
public class DistortFilter extends GenerateFilter{
float scl = 40, mag = 5;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 400f),
new SliderOption("mag", () -> mag, f -> mag = f, 0.5f, 100f)
);
}
@Override
public void apply(){
GenTile tile = in.tile(in.x + noise(in.x, in.y, scl, mag) - mag / 2f, in.y + noise(in.x, in.y + o, scl, mag) - mag / 2f);
in.floor = content.block(tile.floor);
if(!content.block(tile.block).synthetic() && !in.block.synthetic()) in.block = content.block(tile.block);
if(!((Floor)in.floor).isLiquid) in.ore = content.block(tile.ore);
}
}
@@ -0,0 +1,100 @@
package io.anuke.mindustry.maps.filters;
import io.anuke.arc.*;
import io.anuke.arc.function.*;
import io.anuke.arc.scene.style.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ui.dialogs.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.Block.*;
import io.anuke.mindustry.world.blocks.*;
import static io.anuke.mindustry.Vars.updateEditorOnChange;
public abstract class FilterOption{
public static final Predicate<Block> floorsOnly = b -> (b instanceof Floor && !(b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(Icon.full));
public static final Predicate<Block> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(Icon.full));
public static final Predicate<Block> floorsOptional = b -> b == Blocks.air || ((b instanceof Floor && !(b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(Icon.full)));
public static final Predicate<Block> wallsOptional = b -> b == Blocks.air || ((!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(Icon.full)));
public static final Predicate<Block> wallsOresOptional = b -> b == Blocks.air || (((!b.synthetic() && !(b instanceof Floor)) || (b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(Icon.full)));
public static final Predicate<Block> oresOnly = b -> b instanceof OverlayFloor && Core.atlas.isFound(b.icon(Icon.full));
public abstract void build(Table table);
public Runnable changed = () -> {};
static class SliderOption extends FilterOption{
final String name;
final FloatProvider getter;
final FloatConsumer setter;
final float min, max, step;
SliderOption(String name, FloatProvider getter, FloatConsumer setter, float min, float max){
this(name, getter, setter, min, max, (max - min) / 200);
}
SliderOption(String name, FloatProvider getter, FloatConsumer setter, float min, float max, float step){
this.name = name;
this.getter = getter;
this.setter = setter;
this.min = min;
this.max = max;
this.step = step;
}
@Override
public void build(Table table){
table.add("$filter.option." + name);
table.row();
Slider slider = table.addSlider(min, max, step, setter).growX().get();
slider.setValue(getter.get());
if(updateEditorOnChange){
slider.changed(changed);
}else{
slider.released(changed);
}
}
}
static class BlockOption extends FilterOption{
final String name;
final Supplier<Block> supplier;
final Consumer<Block> consumer;
final Predicate<Block> filter;
BlockOption(String name, Supplier<Block> supplier, Consumer<Block> consumer, Predicate<Block> filter){
this.name = name;
this.supplier = supplier;
this.consumer = consumer;
this.filter = filter;
}
@Override
public void build(Table table){
table.addButton(b -> b.addImage(supplier.get().icon(Icon.small)).update(i -> ((TextureRegionDrawable)i.getDrawable())
.setRegion(supplier.get() == Blocks.air ? Core.atlas.find("icon-none") : supplier.get().icon(Icon.small))).size(8 * 3), () -> {
FloatingDialog dialog = new FloatingDialog("");
dialog.setFillParent(false);
int i = 0;
for(Block block : Vars.content.blocks()){
if(!filter.test(block)) continue;
dialog.cont.addImage(block == Blocks.air ? Core.atlas.find("icon-none-small") : block.icon(Icon.medium)).size(8 * 4).pad(3).get().clicked(() -> {
consumer.accept(block);
dialog.hide();
changed.run();
});
if(++i % 10 == 0) dialog.cont.row();
}
dialog.show();
}).pad(4).margin(12f);
table.add("$filter.option." + name);
}
}
}
@@ -0,0 +1,110 @@
package io.anuke.mindustry.maps.filters;
import io.anuke.arc.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.noise.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.editor.MapGenerateDialog.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
public abstract class GenerateFilter{
protected transient float o = (float)(Math.random() * 10000000.0);
protected transient long seed;
protected transient GenerateInput in;
public FilterOption[] options;
public final void apply(GenerateInput in){
this.in = in;
apply();
//remove extra ores on liquids
if(((Floor)in.floor).isLiquid){
in.ore = Blocks.air;
}
}
/** sets up the options; this is necessary since the constructor can't access subclass variables. */
protected void options(FilterOption... options){
this.options = options;
}
/** apply the actual filter on the input */
protected abstract void apply();
/** draw any additional guides */
public void draw(Image image){}
/** localized display name */
public String name(){
return Core.bundle.get("filter." + getClass().getSimpleName().toLowerCase().replace("filter", ""), getClass().getSimpleName().replace("Filter", ""));
}
/** set the seed to a random number */
public void randomize(){
seed = Mathf.random(99999999);
}
//utility generation functions
protected float noise(float x, float y, float scl, float mag){
return (float)in.noise.octaveNoise2D(1f, 0f, 1f / scl, x + o, y + o) * mag;
}
protected float noise(float x, float y, float scl, float mag, float octaves, float persistence){
return (float)in.noise.octaveNoise2D(octaves, persistence, 1f / scl, x + o, y + o) * mag;
}
protected float rnoise(float x, float y, float scl, float mag){
return in.pnoise.getValue((int)(x + o), (int)(y + o), 1f / scl) * mag;
}
protected float chance(){
return Mathf.randomSeed(Pack.longInt(in.x, in.y + (int)o));
}
/** an input for generating at a certain coordinate. should only be instantiated once. */
public static class GenerateInput{
/** input floor */
public Floor srcfloor;
/** input block */
public Block srcblock;
/** input overlay */
public Block srcore;
/** input size parameters */
public int x, y, width, height;
/** output parameters */
public Block floor, block, ore;
Simplex noise = new Simplex();
RidgedPerlin pnoise = new RidgedPerlin(0, 1);
TileProvider buffer;
public void apply(int x, int y, Block floor, Block block, Block ore){
this.floor = this.srcfloor = (Floor)floor;
this.block = this.srcblock = block;
this.ore = srcore = ore;
this.x = x;
this.y = y;
}
public void begin(GenerateFilter filter, int width, int height, TileProvider buffer){
this.buffer = buffer;
this.width = width;
this.height = height;
noise.setSeed(filter.seed);
pnoise.setSeed((int)(filter.seed + 1));
}
GenTile tile(float x, float y){
return buffer.get(Mathf.clamp((int)x, 0, width - 1), Mathf.clamp((int)y, 0, height - 1));
}
public interface TileProvider{
GenTile get(int x, int y);
}
}
}
@@ -0,0 +1,46 @@
package io.anuke.mindustry.maps.filters;
import io.anuke.arc.collection.IntArray;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.editor.MapGenerateDialog.GenTile;
import io.anuke.mindustry.maps.filters.FilterOption.SliderOption;
import static io.anuke.mindustry.Vars.content;
public class MedianFilter extends GenerateFilter{
float radius = 2;
float percentile = 0.5f;
IntArray blocks = new IntArray(), floors = new IntArray();
{
options(
new SliderOption("radius", () -> radius, f -> radius = f, 1f, 12f),
new SliderOption("percentile", () -> percentile, f -> percentile = f, 0f, 1f)
);
}
@Override
public void apply(){
int rad = (int)radius;
blocks.clear();
floors.clear();
for(int x = -rad; x <= rad; x++){
for(int y = -rad; y <= rad; y++){
if(Mathf.dst2(x, y) > rad*rad) continue;
GenTile tile = in.tile(in.x + x, in.y + y);
blocks.add(tile.block);
floors.add(tile.floor);
}
}
floors.sort();
blocks.sort();
int index = Math.min((int)(floors.size * percentile), floors.size - 1);
int floor = floors.get(index), block = blocks.get(index);
in.floor = content.block(floor);
if(!content.block(block).synthetic() && !in.block.synthetic()) in.block = content.block(block);
}
}
@@ -0,0 +1,69 @@
package io.anuke.mindustry.maps.filters;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.editor.MapGenerateDialog.*;
import io.anuke.mindustry.maps.filters.FilterOption.*;
import io.anuke.mindustry.graphics.*;
import static io.anuke.mindustry.Vars.content;
public class MirrorFilter extends GenerateFilter{
private final Vector2 v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2();
float angle = 45;
{
options(new SliderOption("angle", () -> angle, f -> angle = f, 0, 360, 45));
}
@Override
protected void apply(){
v1.trns(angle - 90, 1f);
v2.set(v1).scl(-1f);
v1.add(in.width/2f, in.height/2f);
v2.add(in.width/2f, in.height/2f);
v3.set(in.x, in.y);
if(!left(v1, v2, v3)){
mirror(v3, v1.x, v1.y, v2.x, v2.y);
GenTile tile = in.tile(v3.x, v3.y);
in.floor = content.block(tile.floor);
in.block = content.block(tile.block);
in.ore = content.block(tile.ore);
}
}
@Override
public void draw(Image image){
super.draw(image);
float size = Math.max(image.getWidth() *2, image.getHeight()*2);
Consumer<Vector2> clamper = v -> v.clamp(image.getX(), image.getX() + image.getWidth(), image.getY(), image.getY() + image.getHeight());
clamper.accept(Tmp.v1.trns(angle - 90, size).add(image.getWidth()/2f + image.getX(), image.getHeight()/2f + image.getY()));
clamper.accept(Tmp.v2.set(Tmp.v1).sub(image.getWidth()/2f + image.getX(), image.getHeight()/2f + image.getY()).rotate(180f).add(image.getWidth()/2f + image.getX(), image.getHeight()/2f + image.getY()));
Lines.stroke(Unit.dp.scl(3f), Pal.accent);
Lines.line(Tmp.v1.x, Tmp.v1.y, Tmp.v2.x, Tmp.v2.y);
Draw.reset();
}
void mirror(Vector2 p, float x0, float y0, float x1, float y1){
float dx = x1 - x0;
float dy = y1 - y0;
float a = (dx * dx - dy * dy) / (dx * dx + dy*dy);
float b = 2 * dx * dy / (dx*dx + dy*dy);
p.set((a * (p.x - x0) + b*(p.y - y0) + x0), (b * (p.x - x0) - a*(p.y - y0) + y0));
}
boolean left(Vector2 a, Vector2 b, Vector2 c){
return ((b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x)) > 0;
}
}
@@ -0,0 +1,35 @@
package io.anuke.mindustry.maps.filters;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.maps.filters.FilterOption.BlockOption;
import io.anuke.mindustry.maps.filters.FilterOption.SliderOption;
import io.anuke.mindustry.world.Block;
import static io.anuke.mindustry.maps.filters.FilterOption.floorsOnly;
import static io.anuke.mindustry.maps.filters.FilterOption.wallsOnly;
public class NoiseFilter extends GenerateFilter{
float scl = 40, threshold = 0.5f, octaves = 3f, falloff = 0.5f;
Block floor = Blocks.stone, block = Blocks.rocks;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f),
new SliderOption("threshold", () -> threshold, f -> threshold = f, 0f, 1f),
new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 10f),
new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("wall", () -> block, b -> block = b, wallsOnly)
);
}
@Override
public void apply(){
float noise = noise(in.x, in.y, scl, 1f, octaves, falloff);
if(noise > threshold){
in.floor = floor;
if(wallsOnly.test(in.srcblock)) in.block = block;
}
}
}
@@ -0,0 +1,32 @@
package io.anuke.mindustry.maps.filters;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.maps.filters.FilterOption.SliderOption;
import io.anuke.mindustry.world.Block;
import static io.anuke.mindustry.maps.filters.FilterOption.BlockOption;
import static io.anuke.mindustry.maps.filters.FilterOption.oresOnly;
public class OreFilter extends GenerateFilter{
public float scl = 50, threshold = 0.72f, octaves = 3f, falloff = 0.4f;
public Block ore = Blocks.oreCopper;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f),
new SliderOption("threshold", () -> threshold, f -> threshold = f, 0f, 1f),
new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 10f),
new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f),
new BlockOption("ore", () -> ore, b -> ore = b, oresOnly)
);
}
@Override
public void apply(){
float noise = noise(in.x, in.y, scl, 1f, octaves, falloff);
if(noise > threshold){
in.ore = ore;
}
}
}
@@ -0,0 +1,42 @@
package io.anuke.mindustry.maps.filters;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.maps.filters.FilterOption.BlockOption;
import io.anuke.mindustry.maps.filters.FilterOption.SliderOption;
import io.anuke.mindustry.world.Block;
import static io.anuke.mindustry.maps.filters.FilterOption.floorsOnly;
import static io.anuke.mindustry.maps.filters.FilterOption.wallsOnly;
public class RiverNoiseFilter extends GenerateFilter{
float scl = 40, threshold = 0f, threshold2 = 0.1f;
Block floor = Blocks.water, floor2 = Blocks.deepwater, block = Blocks.sandRocks;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f),
new SliderOption("threshold", () -> threshold, f -> threshold = f, 0f, 1f),
new SliderOption("threshold2", () -> threshold2, f -> threshold2 = f, 0f, 1f),
new BlockOption("block", () -> block, b -> block = b, wallsOnly),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("floor2", () -> floor2, b -> floor2 = b, floorsOnly)
);
}
@Override
public void apply(){
float noise = rnoise(in.x, in.y, scl, 1f);
if(noise >= threshold){
in.floor = floor;
if(in.srcblock.solid){
in.block = block;
}
if(noise >= threshold2){
in.floor = floor2;
}
}
}
}
@@ -0,0 +1,38 @@
package io.anuke.mindustry.maps.filters;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.maps.filters.FilterOption.BlockOption;
import io.anuke.mindustry.maps.filters.FilterOption.SliderOption;
import io.anuke.mindustry.world.Block;
import static io.anuke.mindustry.maps.filters.FilterOption.*;
public class ScatterFilter extends GenerateFilter{
float chance = 0.1f;
Block flooronto = Blocks.air, floor = Blocks.air, block = Blocks.air;
{
options(
new SliderOption("chance", () -> chance, f -> chance = f, 0f, 1f),
new BlockOption("flooronto", () -> flooronto, b -> flooronto = b, floorsOptional),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOptional),
new BlockOption("block", () -> block, b -> block = b, wallsOresOptional)
);
}
@Override
public void apply(){
if(block != Blocks.air && (in.srcfloor == flooronto || flooronto == Blocks.air) && in.srcblock == Blocks.air && chance() <= chance){
if(!block.isOverlay()){
in.block = block;
}else{
in.ore = block;
}
}
if(floor != Blocks.air && (in.srcfloor == flooronto || flooronto == Blocks.air) && chance() <= chance){
in.floor = floor;
}
}
}
@@ -0,0 +1,42 @@
package io.anuke.mindustry.maps.filters;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.maps.filters.FilterOption.BlockOption;
import io.anuke.mindustry.maps.filters.FilterOption.SliderOption;
import io.anuke.mindustry.world.Block;
import static io.anuke.mindustry.maps.filters.FilterOption.floorsOnly;
import static io.anuke.mindustry.maps.filters.FilterOption.wallsOnly;
public class TerrainFilter extends GenerateFilter{
float scl = 40, threshold = 0.9f, octaves = 3f, falloff = 0.5f, magnitude = 1f, circleScl = 2.1f;
Block floor = Blocks.stone, block = Blocks.rocks;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f),
new SliderOption("mag", () -> magnitude, f -> magnitude = f, 0f, 2f),
new SliderOption("threshold", () -> threshold, f -> threshold = f, 0f, 1f),
new SliderOption("circle-scale", () -> circleScl, f -> circleScl = f, 0f, 3f),
new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 10f),
new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("wall", () -> block, b -> block = b, wallsOnly)
);
}
@Override
public void apply(){
float noise = noise(in.x, in.y, scl, magnitude, octaves, falloff) + Mathf.dst((float)in.x / in.width, (float)in.y / in.height, 0.5f, 0.5f) * circleScl;
in.floor = floor;
in.ore = Blocks.air;
if(noise >= threshold){
in.block = block;
}else{
in.block = Blocks.air;
}
}
}