lumosquad/BlueNoise/BLUE_NOISE.cpp
2025-07-26 22:14:37 +02:00

448 lines
12 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// File : BLUE_NOISE.h
///////////////////////////////////////////////////////////////////////////////////
//
// LumosQuad - A Lightning Generator
// Copyright 2007
// The University of North Carolina at Chapel Hill
//
///////////////////////////////////////////////////////////////////////////////////
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// The University of North Carolina at Chapel Hill makes no representations
// about the suitability of this software for any purpose. It is provided
// "as is" without express or implied warranty.
//
// Permission to use, copy, modify and distribute this software and its
// documentation for educational, research and non-profit purposes, without
// fee, and without a written agreement is hereby granted, provided that the
// above copyright notice and the following three paragraphs appear in all
// copies.
//
// THE UNIVERSITY OF NORTH CAROLINA SPECIFICALLY DISCLAIM ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN
// "AS IS" BASIS, AND THE UNIVERSITY OF NORTH CAROLINA HAS NO OBLIGATION TO
// PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
//
// Please send questions and comments about LumosQuad to kim@cs.unc.edu.
//
///////////////////////////////////////////////////////////////////////////////////
//
// This program uses OpenEXR, which has the following restrictions:
//
// Copyright (c) 2002, Industrial Light & Magic, a division of Lucas
// Digital Ltd. LLC
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Industrial Light & Magic nor the names of
// its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
///////////////////////////////////////////////////////////////////////////////////
//
// This class is a very thin wrapper to Daniel Dunbar's blue noise generator.
// With the exception of BLUE_NOISE.h and BLUE_NOISE.cpp, the other files
// in this directory are unmodified copies of his code.
//
// For the original, untainted code, see:
// http://www.cs.virginia.edu/~gfx/pubs/antimony/
//
///////////////////////////////////////////////////////////////////////////////////
// $Id: PDSampling.cpp,v 1.12 2006/07/11 16:45:22 zr Exp $
#define _USE_MATH_DEFINES
#include <cmath>
#include <map>
#include "BLUE_NOISE.h"
#include "RangeList.h"
#include "ScallopedSector.h"
#include "WeightedDiscretePDF.h"
typedef std::vector<int> IntVector;
///
BLUE_NOISE::BLUE_NOISE(float _radius, bool _isTiled, bool usesGrid) :
m_rng(123456),
radius(_radius),
isTiled(_isTiled)
{
if (usesGrid) {
// grid size is chosen so that 4*radius search only
// requires searching adjacent cells, this also
// determines max points per cell
m_gridSize = (int) ceil(2./(4.*_radius));
if (m_gridSize<2) m_gridSize = 2;
m_gridCellSize = 2.0f/m_gridSize;
m_grid = new int[m_gridSize*m_gridSize][kMaxPointsPerCell];
for (int y=0; y<m_gridSize; y++) {
for (int x=0; x<m_gridSize; x++) {
for (int k=0; k<kMaxPointsPerCell; k++) {
m_grid[y*m_gridSize + x][k] = -1;
}
}
}
} else {
m_gridSize = 0;
m_gridCellSize = 0;
m_grid = 0;
}
}
bool BLUE_NOISE::pointInDomain(Vec2 &a)
{
return -1<=a.x && -1<=a.y && 1>=a.x && 1>=a.y;
}
Vec2 BLUE_NOISE::randomPoint()
{
return Vec2(2*m_rng.getFloatL()-1, 2*m_rng.getFloatL()-1);
}
Vec2 BLUE_NOISE::getTiled(Vec2 v)
{
float x = v.x, y = v.y;
if (isTiled) {
if (x<-1) x += 2;
else if (x>1) x -= 2;
if (y<-1) y += 2;
else if (y>1) y -= 2;
}
return Vec2(x,y);
}
void BLUE_NOISE::getGridXY(Vec2 &v, int *gx_out, int *gy_out)
{
int gx = *gx_out = (int) floor(.5*(v.x + 1)*m_gridSize);
int gy = *gy_out = (int) floor(.5*(v.y + 1)*m_gridSize);
if (gx<0 || gx>=m_gridSize || gy<0 || gy>=m_gridSize) {
printf("Internal error, point outside grid was generated, ignoring.\n");
}
}
void BLUE_NOISE::addPoint(Vec2 pt)
{
int i, gx, gy, *cell;
points.push_back(pt);
if (m_grid) {
getGridXY(pt, &gx, &gy);
cell = m_grid[gy*m_gridSize + gx];
for (i=0; i<kMaxPointsPerCell; i++) {
if (cell[i]==-1) {
cell[i] = (int) points.size()-1;
break;
}
}
if (i==kMaxPointsPerCell) {
printf("Internal error, overflowed max points per grid cell. Exiting.\n");
exit(1);
}
}
}
int BLUE_NOISE::findNeighbors(Vec2 &pt, float distance)
{
if (!m_grid) {
printf("Internal error, sampler cannot search without grid.\n");
exit(1);
}
float distanceSqrd = distance*distance;
int i, j, k, gx, gy, N = (int) ceil(distance/m_gridCellSize);
if (N>(m_gridSize>>1)) N = m_gridSize>>1;
m_neighbors.clear();
getGridXY(pt, &gx, &gy);
for (j=-N; j<=N; j++) {
for (i=-N; i<=N; i++) {
int cx = (gx+i+m_gridSize)%m_gridSize;
int cy = (gy+j+m_gridSize)%m_gridSize;
int *cell = m_grid[cy*m_gridSize + cx];
for (k=0; k<kMaxPointsPerCell; k++) {
if (cell[k]==-1) {
break;
} else {
if (getDistanceSquared(pt, points[cell[k]])<distanceSqrd)
m_neighbors.push_back(cell[k]);
}
}
}
}
return (int) m_neighbors.size();
}
float BLUE_NOISE::findClosestNeighbor(Vec2 &pt, float distance)
{
if (!m_grid) {
printf("Internal error, sampler cannot search without grid.\n");
exit(1);
}
float closestSqrd = distance*distance;
int i, j, k, gx, gy, N = (int) ceil(distance/m_gridCellSize);
if (N>(m_gridSize>>1)) N = m_gridSize>>1;
getGridXY(pt, &gx, &gy);
for (j=-N; j<=N; j++) {
for (i=-N; i<=N; i++) {
int cx = (gx+i+m_gridSize)%m_gridSize;
int cy = (gy+j+m_gridSize)%m_gridSize;
int *cell = m_grid[cy*m_gridSize + cx];
for (k=0; k<kMaxPointsPerCell; k++) {
if (cell[k]==-1) {
break;
} else {
float d = getDistanceSquared(pt, points[cell[k]]);
if (d<closestSqrd)
closestSqrd = d;
}
}
}
}
return sqrt(closestSqrd);
}
void BLUE_NOISE::findNeighborRanges(int index, RangeList &rl)
{
if (!m_grid) {
printf("Internal error, sampler cannot search without grid.\n");
exit(1);
}
Vec2 &candidate = points[index];
float rangeSqrd = 4*4*radius*radius;
int i, j, k, gx, gy, N = (int) ceil(4*radius/m_gridCellSize);
if (N>(m_gridSize>>1)) N = m_gridSize>>1;
getGridXY(candidate, &gx, &gy);
int xSide = (candidate.x - (-1 + gx*m_gridCellSize))>m_gridCellSize*.5;
int ySide = (candidate.y - (-1 + gy*m_gridCellSize))>m_gridCellSize*.5;
int iy = 1;
for (j=-N; j<=N; j++) {
int ix = 1;
if (j==0) iy = ySide;
else if (j==1) iy = 0;
for (i=-N; i<=N; i++) {
if (i==0) ix = xSide;
else if (i==1) ix = 0;
// offset to closest cell point
float dx = candidate.x - (-1 + (gx+i+ix)*m_gridCellSize);
float dy = candidate.y - (-1 + (gy+j+iy)*m_gridCellSize);
if (dx*dx+dy*dy<rangeSqrd) {
int cx = (gx+i+m_gridSize)%m_gridSize;
int cy = (gy+j+m_gridSize)%m_gridSize;
int *cell = m_grid[cy*m_gridSize + cx];
for (k=0; k<kMaxPointsPerCell; k++) {
if (cell[k]==-1) {
break;
} else if (cell[k]!=index) {
Vec2 &pt = points[cell[k]];
Vec2 v = getTiled(pt-candidate);
float distSqrd = v.x*v.x + v.y*v.y;
if (distSqrd<rangeSqrd) {
float dist = sqrt(distSqrd);
float angle = atan2(v.y,v.x);
float theta = acos(.25f*dist/radius);
rl.subtract(angle-theta, angle+theta);
}
}
}
}
}
}
}
void BLUE_NOISE::maximize()
{
RangeList rl(0,0);
int i, N = (int) points.size();
for (i=0; i<N; i++) {
Vec2 &candidate = points[i];
rl.reset(0, (float) M_PI*2);
findNeighborRanges(i, rl);
while (rl.numRanges) {
RangeEntry &re = rl.ranges[m_rng.getInt31()%rl.numRanges];
float angle = re.min + (re.max-re.min)*m_rng.getFloatL();
Vec2 pt = getTiled(Vec2(candidate.x + cos(angle)*2*radius,
candidate.y + sin(angle)*2*radius));
addPoint(pt);
rl.subtract(angle - (float) M_PI/3, angle + (float) M_PI/3);
}
}
}
void BLUE_NOISE::relax()
{
FILE *tmp = fopen("relaxTmpIn.txt","w");
int dim, numVerts, numFaces;
Vec2 *verts = 0;
int numPoints = (int) points.size();
// will overwrite later
fprintf(tmp, "2 \n");
for (int i=0; i<(int) points.size(); i++) {
Vec2 &pt = points[i];
fprintf(tmp, "%f %f\n", pt.x, pt.y);
}
for (int y=-1; y<=1; y++) {
for (int x=-1; x<=1; x++) {
if (x || y) {
for (int i=0; i<(int) points.size(); i++) {
Vec2 &pt = points[i];
if (fabs(pt.x+x*2)-1<radius*4 || fabs(pt.y+y*2)-1<radius*4) {
fprintf(tmp, "%f %f\n", pt.x+x*2, pt.y+y*2);
numPoints++;
}
}
}
}
}
fseek(tmp, 0, 0);
fprintf(tmp, "2 %d", numPoints);
fclose(tmp);
tmp = fopen("relaxTmpOut.txt", "w");
fclose(tmp);
system("qvoronoi p FN < relaxTmpIn.txt > relaxTmpOut.txt");
tmp = fopen("relaxTmpOut.txt", "r");
fscanf(tmp, "%d\n%d\n", &dim, &numVerts);
if (dim!=2) {
printf("Error calling out to qvoronoi, skipping relaxation.\n");
goto exit;
}
verts = new Vec2[numVerts];
for (int i=0; i<numVerts; i++) {
fscanf(tmp, "%f %f\n", &verts[i].x, &verts[i].y);
}
fscanf(tmp, "%d\n", &numFaces);
for (int i=0; i<(int) points.size(); i++) {
Vec2 center(0,0);
int N, skip=0;
fscanf(tmp, "%d", &N);
for (int j=0; j<N; j++) {
int index;
fscanf(tmp, "%d", &index);
if (index<0) {
skip = 1;
} else {
center += verts[index];
}
}
if (!skip) {
center *= (1.0f/N);
points[i] = getTiled(center);
}
}
exit:
if (verts) delete verts;
}
///
typedef std::map<int, ScallopedRegion*> RegionMap;
void BLUE_NOISE::complete()
{
RangeList rl(0,0);
IntVector candidates;
addPoint(randomPoint());
candidates.push_back((int) points.size()-1);
while (candidates.size()) {
int c = m_rng.getInt32()%candidates.size();
int index = candidates[c];
Vec2 candidate = points[index];
candidates[c] = candidates[candidates.size()-1];
candidates.pop_back();
rl.reset(0, (float) M_PI*2);
findNeighborRanges(index, rl);
while (rl.numRanges) {
RangeEntry &re = rl.ranges[m_rng.getInt32()%rl.numRanges];
float angle = re.min + (re.max-re.min)*m_rng.getFloatL();
Vec2 pt = getTiled(Vec2(candidate.x + cos(angle)*2*radius,
candidate.y + sin(angle)*2*radius));
addPoint(pt);
candidates.push_back((int) points.size()-1);
rl.subtract(angle - (float) M_PI/3, angle + (float) M_PI/3);
}
}
}
void BLUE_NOISE::writeToBool(bool* noise, int size)
{
// wipe
int index = 0;
for (index = 0; index < size * size; index++)
noise[index] = false;
for (int x = 0; x < points.size(); x++)
{
int i = (points[x].x + 1.0f) * 0.5f * size;
int j = (points[x].y + 1.0f) * 0.5f * size;
index = i + j * size;
noise[index] = true;
}
}