EG/RenderPipelineFile/toolkit/poisson_disk_generator/source/PoissonDiskGenerator.h
2025-07-24 11:37:46 +08:00

199 lines
5.2 KiB
C++

#include "pandabase.h"
#include "pnmImage.h"
#include <stdlib.h>
#include <time.h>
#include <random>
#include <iostream>
#include <fstream>
#include <functional>
#include <string>
using namespace std;
#define TWO_PI 6.28318530718f
#define square(x) ((x) * (x))
struct Vec {
float x, y, z;
inline float length_sq() const {
return x*x + y*y + z*z;
}
inline float dist_sq(const Vec& other) const {
return square(x - other.x) + square(y - other.y) + square(z - other.z);
}
inline float angle_signed() const {
return atan2(y, x);
}
friend inline ostream& operator<<(ostream& stream, const Vec& v) {
return stream << "Vec(" << v.x << ", " << v.y << ", " << v.z << ")";
}
};
int compare_vec(const void * a, const void * b)
{
return ((Vec*)a)->angle_signed() - ((Vec*)b)->angle_signed();
}
class PDGenerator
{
PUBLISHED:
PDGenerator(int sample_count, int evaluations, int num_tries, int is_3d_)
{
is_3d = is_3d_;
rnd_device = new random_device();
rnd_generator = new mt19937(rnd_device->operator()());
rng = new uniform_real_distribution<float>(-1.0f, 1.0f);
randvec = is_3d ? &PDGenerator::randvec_3d : &PDGenerator::randvec_2d;
cout << "Sample count = " << sample_count << endl;
cout << "Evaluations = " << evaluations << endl;
cout << "Num tries = " << num_tries << endl;
Vec* sample_points = new Vec[sample_count];
Vec* best_points = new Vec[sample_count];
float best_score = -100000.0;
for (size_t try_count = 0; try_count < num_tries; ++try_count) {
// Generate the initial sample point
sample_points[0] = randvec(this);
// Fill the points
for (size_t i = 1; i < sample_count; ++i) {
Vec best_point;
float best_point_min_dist = 0.0f;
// Find the point which has the greatest minimum distance to all current points
for (size_t k = 0; k < evaluations; ++k) {
Vec point = randvec(this);
float min_dist = 10000.0;
// Get the minimum distance to all current points
for (size_t j = 0; j < i; ++j) {
min_dist = min(min_dist, point.dist_sq(sample_points[j]));
}
if (min_dist > best_point_min_dist) {
best_point = point;
best_point_min_dist = min_dist;
}
}
sample_points[i] = best_point;
}
// Rate the points
float score = 0.0;
// Check for all points
for (size_t i = 0; i < sample_count; ++i) {
// Find minimum distance to all other points
// float mindist = 10000.0;
float mindist = 0.0;
Vec point = sample_points[i];
for (size_t j = 0; j < sample_count; ++j) {
if (j == i) continue;
// mindist = min(mindist, point.dist_sq(sample_points[j]));
mindist += point.dist_sq(sample_points[j]);
}
score += mindist * mindist;
}
if (try_count % 20 == 0) {
cout << "Try " << try_count << " has a score of " << score << endl;
}
if (score >= best_score) {
memcpy(best_points, sample_points, sizeof(Vec) * sample_count);
best_score = score;
}
}
// Sort points based on their angle
qsort(best_points, sample_count, sizeof(Vec), compare_vec);
// write results
string datatype = is_3d ? "vec3" : "vec2";
ofstream output("disk.txt");
output << "CONST_ARRAY " << datatype << " poisson_disk_" << (is_3d ? "3D" : "2D")
<< "_" << sample_count << "[" << sample_count << "] = " << datatype << "[](" << endl;
// write out all points
for (size_t i = 0; i < sample_count; ++i) {
Vec v = best_points[i];
output << " " << datatype << "(" << v.x << ", " << v.y;
if (is_3d) {
output << ", " << v.z;
}
output << ")" << (i == sample_count - 1 ? "" : ",") << endl;
}
output << ");" << endl;
output.close();
// Plot the points
PNMImage dest(256, 256);
for (float rad = 0.0f; rad < TWO_PI; rad += 0.005f) {
size_t x = int(sin(rad) * 66.0f) + 127;
size_t y = int(cos(rad) * 66.0f) + 127;
if ( int(rad / TWO_PI * 64.0f) % 2 == 0) {
dest.set_xel(x, y, 0, 0.1, 0.1);
}
}
for (size_t i = 0; i < sample_count; ++i) {
Vec point = best_points[i];
int px = int(point.x * 64.0) + 127;
int py = int(point.y * 64.0) + 127;
float f = float(i / float(sample_count)) * 0.8 + 0.2;
dest.set_xel(px, py, f, f, f);
}
dest.write("result.png");
}
inline float frand() {
return rng->operator()(*rnd_generator);
}
inline Vec randvec_3d() {
float r = frand();
// this is so wrong that its almost correct again
float phi = frand() * TWO_PI * 0.5;
float theta = (frand() * 0.5 + 0.5) * TWO_PI;
float sin_phi = sin(phi);
Vec v = { sin_phi * cos(theta) * r, sin_phi * sin(theta) * r,
cos(phi) * r };
return v;
}
inline Vec randvec_2d() {
float r = frand() * 0.5 + 0.5;
float phi = (frand() * 0.5 + 0.5) * TWO_PI;
Vec v = { cos(phi) * r, sin(phi) * r, 0.0 };
return v;
}
bool is_3d;
#ifndef INTERROGATE
std::function<Vec(PDGenerator*)> randvec;
std::random_device* rnd_device;
mt19937* rnd_generator;
uniform_real_distribution<float>* rng;
#endif
};