Java
Utilicé un enfoque basado en diagramas recursivos de Voronoi. Los resultados no parecen muy realistas, pero supongo que están bien.
Aquí hay algunas imágenes de ejemplo (redimensionadas a 250x250 para que no llene toda la pantalla):
0:
1:
Más detalles sobre el algoritmo:
Todas las imágenes en esta sección están usando la misma semilla.
El algoritmo comienza generando un diagrama de Voronoi con 5 puntos:
Si miramos las imágenes originales en el desafío, podemos ver que las líneas no son todas rectas así, por lo que sopesamos la distancia por un valor aleatorio, basado en el ángulo del punto, también, ángulos más cercanos dan valores más cercanos :
Ahora, dibujamos recursivamente este tipo de diagramas de Voronoi dentro de cada región, con una línea más delgada y transparente, y eliminamos el fondo, con una profundidad de recursión máxima de 3, y obtenemos:
Ahora, solo agregamos el fondo marrón pálido, ¡y listo!
Código:
El código consta de tres clases Main.java
, VoronoiPoint.java
y Vector.java
:
Main.java
:
import java.awt.Desktop;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;
import javax.imageio.ImageIO;
public class Main {
public static int WIDTH = 500;
public static int HEIGHT = 500;
public static int RECURSION_LEVELS = 3;
public static int AMOUNT_OF_POINTS = 5;
public static int ROTATION_RESOLUTION = 600;
public static int ROTATION_SMOOTHNESS = 10;
public static int BACKGROUND = 0xFFE0CBAD;
public static Random RAND;
public static void main(String[] args) {
int seed = new Random().nextInt(65536);
if (args.length == 1) {
System.out.println(Arrays.toString(args));
seed = Integer.parseInt(args[0]);
} else {
System.out.println("Generated seed: " + seed);
}
RAND = new Random(seed);
ArrayList<Vector> points = new ArrayList<Vector>();
for (int x = 0; x < WIDTH; x++) {
for (int y = 0; y < HEIGHT; y++) {
points.add(new Vector(x, y));
}
}
BufferedImage soil = generateSoil(WIDTH, HEIGHT, seed, points, AMOUNT_OF_POINTS, RECURSION_LEVELS);
BufferedImage background = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < background.getWidth(); x++) {
for (int y = 0; y < background.getHeight(); y++) {
background.setRGB(x, y, BACKGROUND ^ (RAND.nextInt(10) * 0x010101));
}
}
Graphics g = background.getGraphics();
g.drawImage(soil, 0, 0, null);
g.dispose();
String fileName = "soil";
File output = new File(fileName + ".png");
for (int i = 0; output.exists(); i++) {
output = new File(fileName + i + ".png");
}
try {
ImageIO.write(background, "png", output);
Desktop.getDesktop().open(output);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Done. Saved as " + output);
}
private static BufferedImage generateSoil(int width, int height, int seed, ArrayList<Vector> drawPoints,
int amountOfPoints, int recursionLevel) {
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
ArrayList<VoronoiPoint> points = new ArrayList<VoronoiPoint>();
for (int i = 0; i < amountOfPoints; i++) {
points.add(new VoronoiPoint(drawPoints.get(RAND.nextInt(drawPoints.size()))));
}
HashMap<Integer, ArrayList<Vector>> pointMaps = new HashMap<Integer, ArrayList<Vector>>();
for (VoronoiPoint point : points) {
pointMaps.put(point.hashCode(), new ArrayList<Vector>());
}
System.out.println(pointMaps);
System.out.println(points);
for (Vector v : drawPoints) {
VoronoiPoint closest = null;
VoronoiPoint secondClosest = null;
for (VoronoiPoint point : points) {
double distance = point.getMultiplicativeDistanceTo(v);
if (closest == null || distance < closest.getMultiplicativeDistanceTo(v)) {
secondClosest = closest;
closest = point;
} else if (secondClosest == null || distance < secondClosest.getMultiplicativeDistanceTo(v)) {
secondClosest = point;
}
}
int col = 0;
if (Math.abs(closest.getMultiplicativeDistanceTo(v)
- secondClosest.getMultiplicativeDistanceTo(v)) < (recursionLevel * 5 / RECURSION_LEVELS)) {
col = 0x01000000 * (recursionLevel * 255 / RECURSION_LEVELS);
} else {
pointMaps.get(closest.hashCode()).add(v);
}
result.setRGB((int) v.getX(), (int) v.getY(), col);
}
Graphics g = result.getGraphics();
if (recursionLevel > 0) {
for (ArrayList<Vector> pixels : pointMaps.values()) {
if (pixels.size() > 10) {
BufferedImage img = generateSoil(width, height, seed, pixels, amountOfPoints,
recursionLevel - 1);
g.drawImage(img, 0, 0, null);
}
}
}
g.dispose();
return result;
}
public static int modInts(int a, int b) {
return (int) mod(a, b);
}
public static double mod(double a, double b) {
a = a % b;
while (a < 0)
a += b;
return a;
}
}
VoronoiPoint.java
:
public class VoronoiPoint {
private Vector pos;
private double[] distances;
public VoronoiPoint(Vector pos) {
this.pos = pos;
distances = new double[Main.ROTATION_RESOLUTION];
for (int i = 0; i < distances.length; i++)
distances[i] = Main.RAND.nextFloat() / 2 + 0.51;
for (int iter = 0; iter < Main.ROTATION_SMOOTHNESS; iter++) {
for (int i = 0; i < distances.length; i++) {
distances[i] = (distances[Main.modInts(i - Main.RAND.nextInt(4) - 2, distances.length)] + distances[i]
+ distances[Main.modInts(i + Main.RAND.nextInt(4) - 2, distances.length)]) / 3;
}
}
}
public Vector getPos() {
return pos;
}
public double getRotationFromAngle(double radians) {
return distances[(int) (Main.mod(Math.toDegrees(radians) / 360, 1) * distances.length)];
}
public double getRotationFromVector(Vector vec) {
return getRotationFromAngle(Math.atan2(pos.getY() - vec.getY(), -(pos.getX() - vec.getX())));
}
public double getMultiplicativeDistanceTo(Vector other) {
return pos.getLengthTo(other) * getRotationFromVector(other);
}
public String toString() {
return "VoronoiPoint(pos=[" + pos.getX() + ", " + pos.getY() + "])";
}
public int hashCode() {
return distances.hashCode() ^ pos.hashCode();
}
}
Vector.java
: (Esta clase se copia de uno de mis otros proyectos, por lo que contiene un código innecesario)
package com.loovjo.soil;
import java.util.ArrayList;
import java.util.Random;
public class Vector {
private static final float SMALL = 1f / Float.MAX_EXPONENT * 100;
private float x, y;
public Vector(float x, float y) {
this.setX(x);
this.setY(y);
}
public Vector(int x, int y) {
this.setX(x);
this.setY(y);
}
public Vector(double x, double y) {
this.setX((float) x);
this.setY((float) y);
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
/*
* Gets the length ^ 2 This is faster than getting the length.
*/
public float getLengthToSqrd(float x, float y) {
return (float) ((this.x - x) * (this.x - x) + (this.y - y) * (this.y - y));
}
public float getLengthToSqrd(Vector v) {
return getLengthToSqrd(v.x, v.y);
}
public float getLengthSqrd() {
return getLengthToSqrd(0, 0);
}
public float getLengthTo(float x, float y) {
return (float) Math.sqrt(getLengthToSqrd(x, y));
}
public float getLengthTo(Vector v) {
return getLengthTo(v.x, v.y);
}
public float getLength() {
return getLengthTo(0, 0);
}
public Vector setLength(float setLength) {
float length = getLength();
x *= setLength / length;
y *= setLength / length;
return this;
}
public float getFastLengthTo(float x, float y) {
return getFastLengthTo(new Vector(x, y));
}
public float getFastLengthTo(Vector v) {
float taxiLength = getTaxiCabLengthTo(v);
float chebyDist = getChebyshevDistanceTo(v);
return Float.min(taxiLength * 0.7f, chebyDist);
}
public float getFastLength() {
return getLengthTo(0, 0);
}
public Vector setFastLength(float setLength) {
float length = getFastLength();
x *= setLength / length;
y *= setLength / length;
return this;
}
public float getTaxiCabLengthTo(float x, float y) {
return Math.abs(this.x - x) + Math.abs(this.y - y);
}
public float getTaxiCabLengthTo(Vector v) {
return getTaxiCabLengthTo(v.x, v.y);
}
public float getTaxiCabLength() {
return getTaxiCabLengthTo(0, 0);
}
public Vector setTaxiCabLength(float setLength) {
float length = getTaxiCabLength();
x *= setLength / length;
y *= setLength / length;
return this;
}
public Vector absIfBoth() {
if (x < 0 && y < 0)
return new Vector(-x, -y);
return this;
}
public Vector abs() {
return new Vector(x < 0 ? -x : x, y < 0 ? -y : y);
}
public float getChebyshevDistanceTo(float x, float y) {
return Math.max(Math.abs(this.x - x), Math.abs(this.y - y));
}
public float getChebyshevDistanceTo(Vector v) {
return getChebyshevDistanceTo(v.x, v.y);
}
public float getChebyshevDistance() {
return getChebyshevDistanceTo(0, 0);
}
public Vector setChebyshevLength(float setLength) {
float length = getChebyshevDistance();
x *= setLength / length;
y *= setLength / length;
return this;
}
public Vector sub(Vector v) {
return new Vector(this.x - v.getX(), this.y - v.getY());
}
public Vector add(Vector v) {
return new Vector(this.x + v.getX(), this.y + v.getY());
}
public Vector mul(Vector v) {
return new Vector(this.x * v.getX(), this.y * v.getY());
}
public Vector mul(float f) {
return mul(new Vector(f, f));
}
public Vector div(Vector v) {
return new Vector(this.x / v.getX(), this.y / v.getY());
}
public Vector div(float f) {
return div(new Vector(f, f));
}
public Vector mod(Vector v) {
return new Vector(this.x % v.getX(), this.y % v.getY());
}
public Vector mod(int a, int b) {
return mod(new Vector(a, b));
}
public Vector mod(int a) {
return mod(a, a);
}
public String toString() {
return "Vector(" + getX() + ", " + getY() + ")";
}
/*
* Returns a list with vectors, starting with this, ending with to, and each
* one having length between them
*/
public ArrayList<Vector> loop(Vector to, float length) {
Vector delta = this.sub(to);
float l = delta.getLength();
ArrayList<Vector> loops = new ArrayList<Vector>();
for (float i = length; i < l; i += length) {
delta.setLength(i);
loops.add(delta.add(to));
}
loops.add(this);
return loops;
}
public boolean intersects(Vector pos, Vector size) {
pos.sub(this);
if (pos.getX() < getX())
return false;
if (pos.getY() < getY())
return false;
return true;
}
public Vector copy() {
return new Vector(x, y);
}
public void distort(float d) {
x += Math.random() * d - d / 2;
y += Math.random() * d - d / 2;
}
@Override
public boolean equals(Object o) {
if (o instanceof Vector) {
Vector v = (Vector) o;
return getLengthToSquared(v) < SMALL * SMALL;
}
return false;
}
private float getLengthToSquared(Vector v) {
return sub(v).getLengthSquared();
}
private float getLengthSquared() {
return x * x + y * y;
}
public boolean kindaEquals(Vector o, int i) {
if (o.x + i < x)
return false;
if (o.x - i > x)
return false;
if (o.y + i < y)
return false;
if (o.y - i > y)
return false;
return true;
}
/*
* Gets the direction, from 0 to 8.
*/
public int getDirection() {
return (getDirectionInDegrees()) / (360 / 8);
}
/*
* Gets the direction in degrees.
*/
public int getDirectionInDegrees() {
return (int) positize((float) Math.toDegrees(Math.atan2(x, -y)), 360f);
}
private float positize(float f, float base) {
while (f < 0)
f += base;
return f;
}
// 0 = north,
// 1 = northeast,
// 2 = east,
// 3 = southeast,
// 4 = south,
// 5 = southwest,
// 6 = west,
// 7 = northwest
public Vector moveInDir(int d) {
d = d % 8;
d = (int) positize(d, 8);
if (d == 0)
return this.add(new Vector(0, -1));
if (d == 1)
return this.add(new Vector(1, -1));
if (d == 2)
return this.add(new Vector(1, 0));
if (d == 3)
return this.add(new Vector(1, 1));
if (d == 4)
return this.add(new Vector(0, 1));
if (d == 5)
return this.add(new Vector(-1, 1));
if (d == 6)
return this.add(new Vector(-1, 0));
if (d == 7)
return this.add(new Vector(-1, -1));
return this;
}
/*
* Gets the angle in degrees to o.
*/
public float getRotationTo(Vector o) {
float d = (float) Math.toDegrees((Math.atan2(y - o.y, -(x - o.x))));
while (d < 0)
d += 360;
while (d > 360)
d -= 360;
return d;
}
public float getRotation() {
return getRotationTo(new Vector(0, 0));
}
/*
* In degrees
*/
public Vector rotate(double n) {
n = Math.toRadians(n);
float rx = (float) ((this.x * Math.cos(n)) - (this.y * Math.sin(n)));
float ry = (float) ((this.x * Math.sin(n)) + (this.y * Math.cos(n)));
return new Vector(rx, ry);
}
public int hashCode() {
int xx = (int) x ^ (int)(x * Integer.MAX_VALUE);
int yy = (int) y ^ (int)(y * Integer.MAX_VALUE);
return new Random(12665 * xx).nextInt() ^ new Random(5349 * yy).nextInt() + new Random((30513 * xx) ^ (19972 * yy)).nextInt();
}
public boolean isPositive() {
return x >= 0 && y >= 0;
}
public Vector clone() {
return new Vector(x, y);
}
}
¡Pero no quiero compilar un montón de clases Java!
Aquí hay un archivo JAR que puede ejecutar para generar estas imágenes usted mismo. Ejecutar como java -jar Soil.jar number
, donde number
está la semilla (puede ser cualquier cosa hasta 2 31 -1), o correr como java -jar Soil.jar
, y elige una semilla por sí mismo. Habrá alguna salida de depuración.