Initial commit

This commit is contained in:
Pascal Engélibert 2025-07-26 22:14:37 +02:00
commit 7cdac8ded9
45 changed files with 7275 additions and 0 deletions

448
BlueNoise/BLUE_NOISE.cpp Normal file
View file

@ -0,0 +1,448 @@
///////////////////////////////////////////////////////////////////////////////////
// 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;
}
}

183
BlueNoise/BLUE_NOISE.h Normal file
View file

@ -0,0 +1,183 @@
///////////////////////////////////////////////////////////////////////////////////
// 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.h,v 1.6 2006/07/06 23:13:18 zr Exp $
#include "RNG.h"
#include <cmath>
#include <vector>
#define kMaxPointsPerCell 9
class RangeList;
class ScallopedRegion;
class Vec2 {
public:
Vec2() {};
Vec2(float _x, float _y) : x(_x), y(_y) {};
float x,y;
float length() { return sqrt(x*x + y*y); }
bool operator ==(const Vec2 &b) const { return x==b.x && y==b.y; }
Vec2 operator +(Vec2 b) { return Vec2(x+b.x, y+b.y); }
Vec2 operator -(Vec2 b) { return Vec2(x-b.x, y-b.y); }
Vec2 operator *(Vec2 b) { return Vec2(x*b.x, y*b.y); }
Vec2 operator /(Vec2 b) { return Vec2(x/b.x, y*b.y); }
Vec2 operator +(float n) { return Vec2(x+n, y+n); }
Vec2 operator -(float n) { return Vec2(x-n, y-n); }
Vec2 operator *(float n) { return Vec2(x*n, y*n); }
Vec2 operator /(float n) { return Vec2(x/n, y*n); }
Vec2 &operator +=(Vec2 b) { x+=b.x; y+=b.y; return *this; }
Vec2 &operator -=(Vec2 b) { x-=b.x; y-=b.y; return *this; }
Vec2 &operator *=(Vec2 b) { x*=b.x; y*=b.y; return *this; }
Vec2 &operator /=(Vec2 b) { x/=b.x; y/=b.y; return *this; }
Vec2 &operator +=(float n) { x+=n; y+=n; return *this; }
Vec2 &operator -=(float n) { x-=n; y-=n; return *this; }
Vec2 &operator *=(float n) { x*=n; y*=n; return *this; }
Vec2 &operator /=(float n) { x/=n; y/=n; return *this; }
};
/// \brief Daniel Dunbar's blue noise generator
///
/// The original code has been modified so that the 'boundary sampling'
/// method is the only one available.
class BLUE_NOISE {
protected:
RNG m_rng;
std::vector<int> m_neighbors;
int (*m_grid)[kMaxPointsPerCell];
int m_gridSize;
float m_gridCellSize;
public:
std::vector<Vec2> points;
float radius;
bool isTiled;
public:
BLUE_NOISE(float radius, bool isTiled=true, bool usesGrid=true);
virtual ~BLUE_NOISE() { };
//
bool pointInDomain(Vec2 &a);
// return shortest distance between _a_
// and _b_ (accounting for tiling)
float getDistanceSquared(Vec2 &a, Vec2 &b) { Vec2 v = getTiled(b-a); return v.x*v.x + v.y*v.y; }
float getDistance(Vec2 &a, Vec2 &b) { return sqrt(getDistanceSquared(a, b)); }
// generate a random point in square
Vec2 randomPoint();
// return tiled coordinates of _v_
Vec2 getTiled(Vec2 v);
// return grid x,y for point
void getGridXY(Vec2 &v, int *gx_out, int *gy_out);
// add _pt_ to point list and grid
void addPoint(Vec2 pt);
// populate m_neighbors with list of
// all points within _radius_ of _pt_
// and return number of such points
int findNeighbors(Vec2 &pt, float radius);
// return distance to closest neighbor within _radius_
float findClosestNeighbor(Vec2 &pt, float radius);
// find available angle ranges on boundary for candidate
// by subtracting occluded neighbor ranges from _rl_
void findNeighborRanges(int index, RangeList &rl);
// extend point set by boundary sampling until domain is
// full
void maximize();
// apply one step of Lloyd relaxation
void relax();
void complete();
void writeToBool(bool* noise, int size);
};

7
BlueNoise/LICENSE.txt Normal file
View file

@ -0,0 +1,7 @@
This code is released into the public domain. You can do whatever
you want with it.
I do ask that you respect the authorship and credit myself (Daniel
Dunbar) when referencing the code. Additionally, if you use the
code in an interesting or integral manner I would like to hear
about it.

101
BlueNoise/README.txt Normal file
View file

@ -0,0 +1,101 @@
PDSample - Poisson-Disk sample set generation
Daniel Dunbar, daniel@zuster.org
----
Overview
--
PDSample generates Poisson-disk sampling sets in the domain [-1,1]^2
using a variety of methods. See "A Spatial Data Structure for Fast
Poisson-Disk Sample Generation", in Proc' of SIGGRAPH 2006 for more
information.
Building
---
The code should be portable to any platform with 32-bit float's and int's.
Windows: There is an included PDSample.sln for MSVS version 7.
Unix: Type 'make' and hope for the best.
Usage
---
PDSample [-m] [-t] [-r <relax count=0>] [-M <multiplier=1>]
[-N <minMaxThrows=1000>] <method> <radius> <output>
Options
--
o -t
Uses tiled (toroidal) domain for supporting samplers. The resulting point
set will be suitable for tiling repeatedly in the x and y directions.
o -m
Maximize the resulting point set. For samplers which do not already produce
a maximal point set then this will use the Boundary sampling method to
ensure the resulting point set is maximal.
o -r <relax count>
Apply the specified number of relaxations to the resulting point set. This
requires that qvoronoi be in the path.
o -M <multiplier>
For DartThrowing and BestCandidate methods this determines the factor to
multiply the current number of points by to determine how many samples to
take before exiting (DartThrowing) or accepting the best candidate
(BestCandidate).
o -N <minMaxThrows>
This specifies a minimum number of samples that will be taken for the
DartThrowing sampler. See below.
Available Samplers (for method argument)
--
o DartThrowing
Standard dart throwing. On each iteration the DartThrowing sampler will
try min(N*multiplier,minMaxThrows) samples before termination. Note that
for regular dart throwing where simply a maximum number of throws is used
to determine the termination point, the multiplier should be set to 0.
o BestCandidate
Mitchell's Best Candidate algorithm. Uses the multiplier argument.
o Boundary
Dart throwing by maximizing boundaries.
o Pure
Dart throwing using scalloped sectors.
o LinearPure
Dart throwing using scalloped sectors but without sampling regions according
to their probability of being hit.
o Penrose
Ostromoukhov et al.'s sampling method using their quasisampler_prototype.h
o Uniform
Random point generation. The number of samples to take is calculated as
.75/radius^2 to approximately match the density of Poisson-disk sampling.
Output format
--
Point sets are output in a trivial binary format. The format is not intended
for distribution and does not encode the endianness of the generating platform.
The format matches the pseudo-C struct below:
struct {
int N; // number of points
float t; // generation time
float r; // radius used in generation
int isTiled; // flag for if the set is tileable
float points[N][2];
};
Acknowledgments
--
Thanks to Ares Lagae for comments on a preliminary release of the code,
Ostromoukhov et al. for making available their quasisampler implementation,
as well as Takuji Nishimura and Makoto Matsumoto for their Mersenne
Twister random number generator.

174
BlueNoise/RNG.cpp Normal file
View file

@ -0,0 +1,174 @@
/*
A C-program for MT19937, with initialization improved 2002/1/26.
Coded by Takuji Nishimura and Makoto Matsumoto.
Modified to be a C++ class by Daniel Dunbar.
Before using, initialize the state by using init_genrand(seed)
or init_by_array(init_key, key_length).
Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Any feedback is very welcome.
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
*/
#include "RNG.h"
/* initializes mt[N] with a seed */
RNG::RNG(unsigned long s)
{
seed(s);
}
/* initialize by an array with array-length */
/* init_key is the array for initializing keys */
/* key_length is its length */
/* slight change for C++, 2004/2/26 */
RNG::RNG(unsigned long init_key[], int key_length)
{
int i, j, k;
seed(19650218UL);
i=1; j=0;
k = (N>key_length ? N : key_length);
for (; k; k--) {
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525UL))
+ init_key[j] + j; /* non linear */
mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
i++; j++;
if (i>=N) { mt[0] = mt[N-1]; i=1; }
if (j>=key_length) j=0;
}
for (k=N-1; k; k--) {
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL))
- i; /* non linear */
mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
i++;
if (i>=N) { mt[0] = mt[N-1]; i=1; }
}
mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */
}
void RNG::seed(unsigned long s)
{
mt[0]= s & 0xffffffffUL;
for (mti=1; mti<N; mti++) {
mt[mti] =
(1812433253UL * (mt[mti-1] ^ (mt[mti-1] >> 30)) + mti);
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
/* In the previous versions, MSBs of the seed affect */
/* only MSBs of the array mt[]. */
/* 2002/01/09 modified by Makoto Matsumoto */
mt[mti] &= 0xffffffffUL;
/* for >32 bit machines */
}
}
/* generates a random number on [0,0xffffffff]-interval */
unsigned long RNG::getInt32()
{
unsigned long y;
static unsigned long mag01[2]={0x0UL, _MATRIX_A};
/* mag01[x] = x * _MATRIX_A for x=0,1 */
if (mti >= N) { /* generate N words at one time */
int kk;
for (kk=0;kk<N-_M;kk++) {
y = (mt[kk]&_UPPER_MASK)|(mt[kk+1]&_LOWER_MASK);
mt[kk] = mt[kk+_M] ^ (y >> 1) ^ mag01[y & 0x1UL];
}
for (;kk<N-1;kk++) {
y = (mt[kk]&_UPPER_MASK)|(mt[kk+1]&_LOWER_MASK);
mt[kk] = mt[kk+(_M-N)] ^ (y >> 1) ^ mag01[y & 0x1UL];
}
y = (mt[N-1]&_UPPER_MASK)|(mt[0]&_LOWER_MASK);
mt[N-1] = mt[_M-1] ^ (y >> 1) ^ mag01[y & 0x1UL];
mti = 0;
}
y = mt[mti++];
/* Tempering */
y ^= (y >> 11);
y ^= (y << 7) & 0x9d2c5680UL;
y ^= (y << 15) & 0xefc60000UL;
y ^= (y >> 18);
return y;
}
/* generates a random number on [0,0x7fffffff]-interval */
long RNG::getInt31()
{
return (long)(getInt32()>>1);
}
/* generates a random number on [0,1]-real-interval */
double RNG::getDoubleLR()
{
return getInt32()*(1.0/4294967295.0);
/* divided by 2^32-1 */
}
/* generates a random number on [0,1)-real-interval */
double RNG::getDoubleL()
{
return getInt32()*(1.0/4294967296.0);
/* divided by 2^32 */
}
/* generates a random number on (0,1)-real-interval */
double RNG::getDouble()
{
return (((double)getInt32()) + 0.5)*(1.0/4294967296.0);
/* divided by 2^32 */
}
float RNG::getFloatLR()
{
return getInt32()*(1.0f/4294967295.0f);
/* divided by 2^32-1 */
}
float RNG::getFloatL()
{
return getInt32()*(1.0f/4294967296.0f);
/* divided by 2^32 */
}
float RNG::getFloat()
{
return (getInt32() + 0.5f)*(1.0f/4294967296.0f);
/* divided by 2^32 */
}

37
BlueNoise/RNG.h Normal file
View file

@ -0,0 +1,37 @@
#include <iostream>
using namespace std;
class RNG {
private:
/* Period parameters */
static const long N = 624;
static const long _M = 397;
static const unsigned long _MATRIX_A = 0x9908b0dfUL; /* constant vector a */
static const unsigned long _UPPER_MASK = 0x80000000UL; /* most significant w-r bits */
static const unsigned long _LOWER_MASK = 0x7fffffffUL; /* least significant r bits */
private:
unsigned long mt[N]; /* the array for the state vector */
int mti;
public:
RNG(unsigned long seed=5489UL);
RNG(unsigned long *init_key, int key_length);
void seed(unsigned long seed);
/* generates a random number on [0,0xffffffff]-interval */
unsigned long getInt32();
/* generates a random number on [0,0x7fffffff]-interval */
long getInt31();
/* generates a random number on [0,1]-real-interval */
double getDoubleLR();
float getFloatLR();
/* generates a random number on [0,1)-real-interval */
double getDoubleL();
float getFloatL();
/* generates a random number on (0,1)-real-interval */
double getDouble();
float getFloat();
};

139
BlueNoise/RangeList.cpp Normal file
View file

@ -0,0 +1,139 @@
// $Id: RangeList.cpp,v 1.4 2006/01/24 03:22:14 zr Exp $
#define _USE_MATH_DEFINES
#include <math.h>
#include <stdio.h>
#include <cstring>
#include "RangeList.h"
///
static const float kSmallestRange = .000001f;
RangeList::RangeList(float min, float max)
{
numRanges = 0;
rangesSize = 8;
ranges = new RangeEntry[rangesSize];
reset(min, max);
}
RangeList::~RangeList()
{
delete[] ranges;
}
void RangeList::reset(float min, float max)
{
numRanges = 1;
ranges[0].min = min;
ranges[0].max = max;
}
void RangeList::deleteRange(int pos)
{
if (pos<numRanges-1) {
memmove(&ranges[pos], &ranges[pos+1], sizeof(*ranges)*(numRanges-(pos+1)));
}
numRanges--;
}
void RangeList::insertRange(int pos, float min, float max)
{
if (numRanges==rangesSize) {
RangeEntry *tmp = new RangeEntry[rangesSize];
memcpy(tmp, ranges, numRanges*sizeof(*tmp));
delete[] ranges;
ranges = tmp;
}
if (pos<numRanges) {
memmove(&ranges[pos+1], &ranges[pos], sizeof(*ranges)*(numRanges-pos));
}
ranges[pos].min = min;
ranges[pos].max = max;
numRanges++;
}
void RangeList::subtract(float a, float b)
{
static const float twoPi = (float) (M_PI*2);
if (a>twoPi) {
subtract(a-twoPi, b-twoPi);
} else if (b<0) {
subtract(a+twoPi, b+twoPi);
} else if (a<0) {
subtract(0, b);
subtract(a+twoPi,twoPi);
} else if (b>twoPi) {
subtract(a, twoPi);
subtract(0, b-twoPi);
} else if (numRanges==0) {
;
} else {
int pos;
if (a<ranges[0].min) {
pos = -1;
} else {
int lo=0, mid=0, hi=numRanges;
while (lo<hi-1) {
mid = (lo+hi)>>1;
if (ranges[mid].min<a) {
lo = mid;
} else {
hi = mid;
}
}
pos = lo;
}
if (pos==-1) {
pos = 0;
} else if (a<ranges[pos].max) {
float c = ranges[pos].min;
float d = ranges[pos].max;
if (a-c<kSmallestRange) {
if (b<d) {
ranges[pos].min = b;
} else {
deleteRange(pos);
}
} else {
ranges[pos].max = a;
if (b<d) {
insertRange(pos+1, b, d);
}
pos++;
}
} else {
if (pos<numRanges-1 && b>ranges[pos+1].min) {
pos++;
} else {
return;
}
}
while (pos<numRanges && b>ranges[pos].min) {
if (ranges[pos].max-b<kSmallestRange) {
deleteRange(pos);
} else {
ranges[pos].min = b;
}
}
}
}
void RangeList::print()
{
printf("[");
for (int i=0; i<numRanges; i++) {
printf("(%f,%f)%s", ranges[i].min, ranges[i].max, (i==numRanges-1)?"":", ");
}
printf("]\n");
}

27
BlueNoise/RangeList.h Normal file
View file

@ -0,0 +1,27 @@
// $Id: RangeList.h,v 1.3 2006/01/12 16:39:19 zr Exp $
#include <vector>
typedef struct _RangeEntry {
float min, max;
} RangeEntry;
class RangeList {
public:
RangeEntry *ranges;
int numRanges, rangesSize;
public:
RangeList(float min, float max);
~RangeList();
void reset(float min, float max);
void print();
void subtract(float min, float max);
private:
void deleteRange(int pos);
void insertRange(int pos, float min, float max);
};

View file

@ -0,0 +1,289 @@
#define _USE_MATH_DEFINES
#include <math.h>
#include <float.h>
#include <algorithm>
#include "BLUE_NOISE.h"
#include "ScallopedSector.h"
static const float kTwoPi = (float) (M_PI*2);
static float integralOfDistToCircle(float x, float d, float r, float k)
{
if (r<FLT_EPSILON)
return 0.0;
float sin_x = sin(x);
float d_sin_x = d*sin_x;
float y = sin_x*d/r;
if (y<-1) y = -1;
else if (y>1) y = 1;
float theta = asin(y);
return (r*(r*(x +
k*theta) +
k*cos(theta)*d_sin_x) +
d*cos(x)*d_sin_x)*.5f;
}
ScallopedSector::ScallopedSector(Vec2 &_Pt, float _a1, float _a2, Vec2 &P1, float r1, float sign1, Vec2 &P2, float r2, float sign2)
{
Vec2 v1 = Vec2(P1.x - _Pt.x, P1.y - _Pt.y);
Vec2 v2 = Vec2(P2.x - _Pt.x, P2.y - _Pt.y);
P = _Pt;
a1 = _a1;
a2 = _a2;
arcs[0].P = P1;
arcs[0].r = r1;
arcs[0].sign = sign1;
arcs[0].d = sqrt(v1.x*v1.x + v1.y*v1.y);
arcs[0].rSqrd = arcs[0].r*arcs[0].r;
arcs[0].dSqrd = arcs[0].d*arcs[0].d;
arcs[0].theta = atan2(v1.y,v1.x);
arcs[0].integralAtStart = integralOfDistToCircle(a1 - arcs[0].theta, arcs[0].d, arcs[0].r, arcs[0].sign);
arcs[1].P = P2;
arcs[1].r = r2;
arcs[1].sign = sign2;
arcs[1].d = sqrt(v2.x*v2.x + v2.y*v2.y);
arcs[1].rSqrd = arcs[1].r*arcs[1].r;
arcs[1].dSqrd = arcs[1].d*arcs[1].d;
arcs[1].theta = atan2(v2.y,v2.x);
arcs[1].integralAtStart = integralOfDistToCircle(a1 - arcs[1].theta, arcs[1].d, arcs[1].r, arcs[1].sign);
area = calcAreaToAngle(a2);
}
float ScallopedSector::calcAreaToAngle(float angle)
{
float underInner = integralOfDistToCircle(angle - arcs[0].theta, arcs[0].d, arcs[0].r, arcs[0].sign) - arcs[0].integralAtStart;
float underOuter = integralOfDistToCircle(angle - arcs[1].theta, arcs[1].d, arcs[1].r, arcs[1].sign) - arcs[1].integralAtStart;
return underOuter-underInner;
}
float ScallopedSector::calcAngleForArea(float area, RNG &rng)
{
float lo = a1, hi = a2, cur = lo + (hi-lo)*rng.getFloat();
for (int i=0; i<10; i++) {
if (calcAreaToAngle(cur)<area) {
lo = cur;
cur = (cur + hi)*.5f;
} else {
hi = cur;
cur = (lo + cur)*.5f;
}
}
return cur;
}
float ScallopedSector::distToCurve(float angle, int index)
{
float alpha = angle - arcs[index].theta;
float sin_alpha = sin(alpha);
float t0 = arcs[index].rSqrd - arcs[index].dSqrd*sin_alpha*sin_alpha;
if (t0<0) {
return arcs[index].d*cos(alpha);
} else {
return arcs[index].d*cos(alpha) + arcs[index].sign*sqrt(t0);
}
}
Vec2 ScallopedSector::sample(RNG &rng)
{
float angle = calcAngleForArea(area*rng.getFloatL(), rng);
float d1 = distToCurve(angle, 0);
float d2 = distToCurve(angle, 1);
float d = sqrt(d1*d1 + (d2*d2 - d1*d1)*rng.getFloat());
return Vec2(P.x + cos(angle)*d, P.y + sin(angle)*d);
}
///
float ScallopedSector::canonizeAngle(float angle)
{
float delta = fmod(angle - a1, kTwoPi);
if (delta<0) delta += kTwoPi;
return a1 + delta;
}
void ScallopedSector::distToCircle(float angle, Vec2 &C, float r, float *d1_out, float *d2_out)
{
Vec2 v(C.x - P.x, C.y - P.y);
float dSqrd = v.x*v.x + v.y*v.y;
float theta = atan2(v.y, v.x);
float alpha = angle - theta;
float sin_alpha = sin(alpha);
float xSqrd = r*r - dSqrd*sin_alpha*sin_alpha;
if (xSqrd<0) {
*d1_out = *d2_out = -10000000;
} else {
float a = sqrt(dSqrd)*cos(alpha);
float x = sqrt(xSqrd);
*d1_out = a-x;
*d2_out = a+x;
}
}
void ScallopedSector::subtractDisk(Vec2 &C, float r, std::vector<ScallopedSector> *regions)
{
std::vector<float> angles;
Vec2 v(C.x - P.x, C.y-P.y);
float d = sqrt(v.x*v.x + v.y*v.y);
if (r<d) {
float theta = atan2(v.y, v.x);
float x = sqrt(d*d-r*r);
float angle, alpha = asin(r/d);
angle = canonizeAngle(theta+alpha);
if (a1<angle && angle<a2) {
if (distToCurve(angle,0)<x && x<distToCurve(angle,1))
angles.push_back(angle);
}
angle = canonizeAngle(theta-alpha);
if (a1<angle && angle<a2) {
if (distToCurve(angle,0)<x && x<distToCurve(angle,1))
angles.push_back(angle);
}
}
for (int arcIndex=0; arcIndex<2; arcIndex++) {
Vec2 &C2 = arcs[arcIndex].P;
float R = arcs[arcIndex].r;
Vec2 v(C.x - C2.x, C.y - C2.y);
float d = sqrt(v.x*v.x + v.y*v.y);
if (d>FLT_EPSILON) {
float invD = 1.0f/d;
float x = (d*d - r*r + R*R)*invD*.5f;
float k = R*R - x*x;
if (k>0) {
float y = sqrt(k);
float vx = v.x*invD;
float vy = v.y*invD;
float vx_x = vx*x, vy_x = vy*x;
float vx_y = vx*y, vy_y = vy*y;
float angle;
angle = canonizeAngle(atan2(C2.y + vy_x + vx_y - P.y,
C2.x + vx_x - vy_y - P.x));
if (a1<angle && angle<a2) angles.push_back(angle);
angle = canonizeAngle(atan2(C2.y + vy_x - vx_y - P.y,
C2.x + vx_x + vy_y - P.x));
if (a1<angle && angle<a2) angles.push_back(angle);
}
}
}
sort(angles.begin(), angles.end());
angles.insert(angles.begin(), a1);
angles.push_back(a2);
for (unsigned int i=1; i<angles.size(); i++) {
float a1 = angles[i-1], a2 = angles[i];
float midA = (a1+a2)*.5f;
float inner = distToCurve(midA,0);
float outer = distToCurve(midA,1);
float d1, d2;
distToCircle(midA, C, r, &d1, &d2); // d1<=d2
if (d2<inner || d1>outer) {
regions->push_back(ScallopedSector(P, a1, a2, arcs[0].P, arcs[0].r, arcs[0].sign, arcs[1].P, arcs[1].r, arcs[1].sign));
} else {
if (inner<d1) {
regions->push_back(ScallopedSector(P, a1, a2, arcs[0].P, arcs[0].r, arcs[0].sign, C, r, -1));
}
if (d2<outer) {
regions->push_back(ScallopedSector(P, a1, a2, C, r, 1, arcs[1].P, arcs[1].r, arcs[1].sign));
}
}
}
}
///
ScallopedRegion::ScallopedRegion(Vec2 &P, float r1, float r2, float _minArea) :
minArea(_minArea)
{
regions = new std::vector<ScallopedSector>;
regions->push_back(ScallopedSector(P, 0, kTwoPi, P, r1, 1, P, r2, 1));
area = (*regions)[0].area;
}
ScallopedRegion::~ScallopedRegion()
{
delete regions;
}
void ScallopedRegion::subtractDisk(Vec2 C, float r)
{
std::vector<ScallopedSector> *newRegions = new std::vector<ScallopedSector>;
area = 0;
for (unsigned int i=0; i<regions->size(); i++) {
ScallopedSector &ss = (*regions)[i];
std::vector<ScallopedSector> *tmp = new std::vector<ScallopedSector>;
ss.subtractDisk(C, r, tmp);
for (unsigned int j=0; j<tmp->size(); j++) {
ScallopedSector &nss = (*tmp)[j];
if (nss.area>minArea) {
area += nss.area;
if (newRegions->size()) {
ScallopedSector &last = (*newRegions)[newRegions->size()-1];
if (last.a2==nss.a1 && (last.arcs[0].P==nss.arcs[0].P && last.arcs[0].r==nss.arcs[0].r && last.arcs[0].sign==nss.arcs[0].sign) &&
(last.arcs[1].P==nss.arcs[1].P && last.arcs[1].r==nss.arcs[1].r && last.arcs[1].sign==nss.arcs[1].sign)) {
last.a2 = nss.a2;
last.area = last.calcAreaToAngle(last.a2);
continue;
}
}
newRegions->push_back(nss);
}
}
delete tmp;
}
delete regions;
regions = newRegions;
}
Vec2 ScallopedRegion::sample(RNG &rng)
{
if (!regions->size()) {
printf("Fatal error, sampled from empty region.");
exit(1);
return Vec2(0,0);
} else {
float a = area*rng.getFloatL();
ScallopedSector &ss = (*regions)[0];
for (unsigned int i=0; i<regions->size(); i++) {
ss = (*regions)[i];
if (a<ss.area)
break;
a -= ss.area;
}
return ss.sample(rng);
}
}

View file

@ -0,0 +1,51 @@
// $Id: ScallopedSector.h,v 1.3 2006/07/06 23:30:24 zr Exp $
#include <vector>
typedef struct {
Vec2 P;
float r, sign, d, theta, integralAtStart;
float rSqrd, dSqrd;
} ArcData;
class ScallopedSector
{
public:
Vec2 P;
float a1, a2, area;
ArcData arcs[2];
public:
ScallopedSector(Vec2 &_Pt, float _a1, float _a2, Vec2 &P1, float r1, float sign1, Vec2 &P2, float r2, float sign2);
float calcAreaToAngle(float angle);
float calcAngleForArea(float area, RNG &rng);
Vec2 sample(RNG &rng);
float distToCurve(float angle, int index);
void subtractDisk(Vec2 &C, float r, std::vector<ScallopedSector> *regions);
private:
float canonizeAngle(float angle);
void distToCircle(float angle, Vec2 &C, float r, float *d1_out, float *d2_out);
};
class ScallopedRegion
{
public:
std::vector<ScallopedSector> *regions;
float minArea;
float area;
public:
ScallopedRegion(Vec2 &P, float r1, float r2, float minArea=.00000001);
~ScallopedRegion();
bool isEmpty() { return regions->size()==0; }
void subtractDisk(Vec2 C, float r);
Vec2 sample(RNG &rng);
};

View file

@ -0,0 +1,313 @@
// $Id: WeightedDiscretePDF.cpp,v 1.4 2006/07/07 05:54:31 zr Exp $
#include <stdexcept>
#include "WeightedDiscretePDF.h"
//
template <class T>
WDPDF_Node<T>::WDPDF_Node(T key_, float weight_, WDPDF_Node<T> *parent_)
{
m_mark = false;
key = key_;
weight = weight_;
sumWeights = 0;
left = right = 0;
parent = parent_;
}
template <class T>
WDPDF_Node<T>::~WDPDF_Node()
{
if (left) delete left;
if (right) delete right;
}
//
template <class T>
WeightedDiscretePDF<T>::WeightedDiscretePDF()
{
m_root = 0;
}
template <class T>
WeightedDiscretePDF<T>::~WeightedDiscretePDF()
{
if (m_root) delete m_root;
}
template <class T>
void WeightedDiscretePDF<T>::insert(T item, float weight)
{
WDPDF_Node<T> *p=0, *n=m_root;
while (n) {
if (n->leftIsRed() && n->rightIsRed())
split(n);
p = n;
if (n->key==item) {
throw std::domain_error("insert: argument(item) already in tree");
} else {
n = (item<n->key)?n->left:n->right;
}
}
n = new WDPDF_Node<T>(item, weight, p);
if (!p) {
m_root = n;
} else {
if (item<p->key) {
p->left = n;
} else {
p->right = n;
}
split(n);
}
propogateSumsUp(n);
}
template <class T>
void WeightedDiscretePDF<T>::remove(T item)
{
WDPDF_Node<T> **np = lookup(item, 0);
WDPDF_Node<T> *child, *n = *np;
if (!n) {
throw std::domain_error("remove: argument(item) not in tree");
} else {
if (n->left) {
WDPDF_Node<T> **leftMaxp = &n->left;
while ((*leftMaxp)->right)
leftMaxp = &(*leftMaxp)->right;
n->key = (*leftMaxp)->key;
n->weight = (*leftMaxp)->weight;
np = leftMaxp;
n = *np;
}
// node now has at most one child
child = n->left?n->left:n->right;
*np = child;
if (child) {
child->parent = n->parent;
if (n->isBlack()) {
lengthen(child);
}
}
propogateSumsUp(n->parent);
n->left = n->right = 0;
delete n;
}
}
template <class T>
void WeightedDiscretePDF<T>::update(T item, float weight)
{
WDPDF_Node<T> *n = *lookup(item, 0);
if (!n) {
throw std::domain_error("update: argument(item) not in tree");
} else {
float delta = weight - n->weight;
n->weight = weight;
for (; n; n=n->parent) {
n->sumWeights += delta;
}
}
}
template <class T>
T WeightedDiscretePDF<T>::choose(float p)
{
if (p<0.0 || p>=1.0) {
throw std::domain_error("choose: argument(p) outside valid range");
} else if (!m_root) {
throw std::logic_error("choose: choose() called on empty tree");
} else {
float w = m_root->sumWeights * p;
WDPDF_Node<T> *n = m_root;
while (1) {
if (n->left) {
if (w<n->left->sumWeights) {
n = n->left;
continue;
} else {
w -= n->left->sumWeights;
}
}
if (w<n->weight || !n->right) {
break; // !n->right condition shouldn't be necessary, just sanity check
}
w -= n->weight;
n = n->right;
}
return n->key;
}
}
template <class T>
bool WeightedDiscretePDF<T>::inTree(T item)
{
WDPDF_Node<T> *n = *lookup(item, 0);
return !!n;
}
//
template <class T>
WDPDF_Node<T> **WeightedDiscretePDF<T>::lookup(T item, WDPDF_Node<T> **parent_out)
{
WDPDF_Node<T> *n, *p=0, **np=&m_root;
while ((n = *np)) {
if (n->key==item) {
break;
} else {
p = n;
if (item<n->key) {
np = &n->left;
} else {
np = &n->right;
}
}
}
if (parent_out)
*parent_out = p;
return np;
}
template <class T>
void WeightedDiscretePDF<T>::split(WDPDF_Node<T> *n)
{
if (n->left) n->left->markBlack();
if (n->right) n->right->markBlack();
if (n->parent) {
WDPDF_Node<T> *p = n->parent;
n->markRed();
if (p->isRed()) {
p->parent->markRed();
// not same direction
if (!( (n==p->left && p==p->parent->left) ||
(n==p->right && p==p->parent->right))) {
rotate(n);
p = n;
}
rotate(p);
p->markBlack();
}
}
}
template <class T>
void WeightedDiscretePDF<T>::rotate(WDPDF_Node<T> *n)
{
WDPDF_Node<T> *p=n->parent, *pp=p->parent;
n->parent = pp;
p->parent = n;
if (n==p->left) {
p->left = n->right;
n->right = p;
if (p->left) p->left->parent = p;
} else {
p->right = n->left;
n->left = p;
if (p->right) p->right->parent = p;
}
n->setSum();
p->setSum();
if (!pp) {
m_root = n;
} else {
if (p==pp->left) {
pp->left = n;
} else {
pp->right = n;
}
}
}
template <class T>
void WeightedDiscretePDF<T>::lengthen(WDPDF_Node<T> *n)
{
if (n->isRed()) {
n->markBlack();
} else if (n->parent) {
WDPDF_Node<T> *sibling = n->sibling();
if (sibling && sibling->isRed()) {
n->parent->markRed();
sibling->markBlack();
rotate(sibling); // node sibling is now old sibling child, must be black
sibling = n->sibling();
}
// sibling is black
if (!sibling) {
lengthen(n->parent);
} else if (sibling->leftIsBlack() && sibling->rightIsBlack()) {
if (n->parent->isBlack()) {
sibling->markRed();
lengthen(n->parent);
} else {
sibling->markRed();
n->parent->markBlack();
}
} else {
if (n==n->parent->left && sibling->rightIsBlack()) {
rotate(sibling->left); // sibling->left must be red
sibling->markRed();
sibling->parent->markBlack();
sibling = sibling->parent;
} else if (n==n->parent->right && sibling->leftIsBlack()) {
rotate(sibling->right); // sibling->right must be red
sibling->markRed();
sibling->parent->markBlack();
sibling = sibling->parent;
}
// sibling is black, and sibling's far child is red
rotate(sibling);
if (n->parent->isRed()) sibling->markRed();
sibling->left->markBlack();
sibling->right->markBlack();
}
}
}
template <class T>
void WeightedDiscretePDF<T>::propogateSumsUp(WDPDF_Node<T> *n)
{
for (; n; n=n->parent)
n->setSum();
}

View file

@ -0,0 +1,57 @@
// $Id: WeightedDiscretePDF.h,v 1.4 2006/07/07 05:54:31 zr Exp $
template <class T>
class WDPDF_Node
{
private:
bool m_mark;
public:
WDPDF_Node<T> *parent, *left, *right;
T key;
float weight, sumWeights;
public:
WDPDF_Node(T key_, float weight_, WDPDF_Node<T> *parent_);
~WDPDF_Node();
WDPDF_Node<T> *sibling() { return this==parent->left?parent->right:parent->left; }
void markRed() { m_mark = true; }
void markBlack() { m_mark = false; }
bool isRed() { return m_mark; }
bool isBlack() { return !m_mark; }
bool leftIsBlack() { return !left || left->isBlack(); }
bool rightIsBlack() { return !right || right->isBlack(); }
bool leftIsRed() { return !leftIsBlack(); }
bool rightIsRed() { return !rightIsBlack(); }
void setSum() { sumWeights = weight + (left?left->sumWeights:0) + (right?right->sumWeights:0); }
};
template <class T>
class WeightedDiscretePDF
{
private:
WDPDF_Node<T> *m_root;
public:
WeightedDiscretePDF();
~WeightedDiscretePDF();
void insert(T item, float weight);
void update(T item, float newWeight);
void remove(T item);
bool inTree(T item);
/* pick a tree element according to its
* weight. p should be in [0,1).
*/
T choose(float p);
private:
WDPDF_Node<T> **lookup(T item, WDPDF_Node<T> **parent_out);
void split(WDPDF_Node<T> *node);
void rotate(WDPDF_Node<T> *node);
void lengthen(WDPDF_Node<T> *node);
void propogateSumsUp(WDPDF_Node<T> *n);
};