Java
Ich habe einen Ansatz verwendet, der auf rekursiven Voronoi-Diagrammen basiert. Die Ausgaben sehen nicht sehr realistisch aus, aber ich denke, sie sind in Ordnung.
Hier sind einige Beispielbilder (Größe auf 250x250 angepasst, damit nicht der gesamte Bildschirm ausgefüllt wird):
0:
1:
Weitere Details zum Algorithmus:
Alle Bilder in diesem Abschnitt verwenden denselben Startwert.
Der Algorithmus generiert zunächst ein Voronoi-Diagramm mit 5 Punkten:
Wenn wir uns die Originalbilder in der Challenge ansehen, können wir sehen, dass die Linien nicht alle so gerade sind. Wir wägen den Abstand also mit einem zufälligen Wert, basierend auf dem Winkel zum Punkt. Außerdem ergeben engere Winkel engere Werte :
Jetzt zeichnen wir diese Arten von Voronoi-Diagrammen innerhalb jeder Region rekursiv mit einer dünneren und transparenteren Linie und entfernen den Hintergrund mit einer maximalen Rekursionstiefe von 3, und wir erhalten:
Jetzt fügen wir nur noch den hellbraunen Hintergrund hinzu und wir sind fertig!
Code:
Der Code besteht aus drei Klassen Main.java
, VoronoiPoint.java
und 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
: (Diese Klasse wurde aus einem meiner anderen Projekte kopiert und enthält daher unnötigen Code.)
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);
}
}
Aber ich möchte nicht ein paar Java-Klassen kompilieren!
Hier ist eine JAR-Datei, die Sie ausführen können, um diese Bilder selbst zu generieren. Ausführen als java -jar Soil.jar number
, wo number
sich der Startwert befindet (kann bis zu 2 31 -1 betragen) oder Ausführen als java -jar Soil.jar
, und der Startwert wird von selbst ausgewählt. Es wird einige Debug-Ausgaben geben.