Java
Я використовував підхід, заснований на рекурсивних діаграмах Вороного. Виходи не виглядають дуже реалістично, але я думаю, що вони нормальні.
Ось кілька прикладів зображень (розмір яких змінюється до 250x250, щоб не заповнити весь екран):
0:
1:
Детальніше про алгоритм:
Усі зображення в цьому розділі використовують однакове насіння.
Алгоритм починається з генерування діаграми Вороного з 5 балів:
Якщо ми подивимось на вихідні зображення у виклику, то можемо побачити, що лінії не всі такі прямі, тому ми зважуємо відстань випадковим значенням, виходячи з кута до точки, також, більш близькі кути дають ближчі значення :
Тепер ми рекурсивно малюємо такі діаграми Вороного всередині кожного регіону тоншою та прозорою лінією та видаляємо фон із максимальною глибиною рекурсії 3, і отримуємо:
Тепер ми просто додаємо блідо-коричневий фон, і ми закінчили!
Код:
Код складається з трьох класів Main.java
, VoronoiPoint.java
і 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
: (Цей клас скопійовано з одного з моїх інших проектів, тому він містить непотрібний код)
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);
}
}
Але я не хочу складати купу Java-класів!
Ось файл JAR, який ви можете запустити для створення цих зображень самостійно. Виконайте як java -jar Soil.jar number
, де number
є насіння (може бути що завгодно до 2 31 -1), або біжіть як java -jar Soil.jar
, і він вибирає насіння сам. Буде якийсь вихід налагодження.