Java
Tôi đã sử dụng một cách tiếp cận dựa trên sơ đồ Voronoi đệ quy. Các kết quả đầu ra trông không thực tế lắm, nhưng tôi đoán chúng ổn.
Dưới đây là một số hình ảnh ví dụ (được thay đổi kích thước thành 250x250 để nó không lấp đầy toàn bộ màn hình):
0:
1:
Thêm chi tiết về thuật toán:
Tất cả các hình ảnh trong phần này đang sử dụng cùng một hạt giống.
Thuật toán bắt đầu bằng cách tạo sơ đồ Voronoi với 5 điểm:
Nếu chúng ta nhìn vào các hình ảnh gốc trong thử thách, chúng ta có thể thấy rằng các đường thẳng không giống nhau, vì vậy chúng ta cân nhắc khoảng cách bằng một giá trị ngẫu nhiên, dựa trên góc tới điểm, ngoài ra, các góc gần hơn sẽ cho các giá trị gần hơn :
Bây giờ, chúng tôi vẽ đệ quy các loại sơ đồ Voronoi bên trong mỗi vùng, với đường kẻ mỏng hơn và trong suốt hơn, và xóa nền, với độ sâu đệ quy tối đa là 3 và chúng tôi nhận được:
Bây giờ, chúng ta chỉ cần thêm nền màu nâu nhạt, và chúng ta đã hoàn thành!
Mã số:
Mã này bao gồm ba lớp, Main.java
, VoronoiPoint.java
và 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
: (Lớp này được sao chép từ một trong các dự án khác của tôi, vì vậy nó chứa một số mã không cần thiết)
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);
}
}
Nhưng tôi không muốn biên dịch một loạt các lớp Java!
Đây là một tệp JAR mà bạn có thể chạy để tự tạo các hình ảnh này. Chạy như là java -jar Soil.jar number
, number
hạt giống ở đâu (có thể là bất cứ thứ gì lên đến 2 31 -1), hoặc chạy như java -jar Soil.jar
, và nó tự chọn một hạt giống. Sẽ có một số đầu ra gỡ lỗi.