Skip to content

Instantly share code, notes, and snippets.

@mpmckenna8
Last active April 20, 2023 06:14
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mpmckenna8/0d582d86a59444e3f0411030f9ef2f4f to your computer and use it in GitHub Desktop.
Save mpmckenna8/0d582d86a59444e3f0411030f9ef2f4f to your computer and use it in GitHub Desktop.
Poisson disc distribution image sample of a tacocat
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.7.4/d3.js"></script>
</head>
<body>
<div id="heap">
</div>
<script src="main.js"></script>
</body>
</html>
// needed if using a build tool like browserify
// var d3 = require('d3');
console.log('ready to get sampling')
var width = 300
let height = 400
var sample = poissonDiscSampler(width, height, 3.45)
let samples = []
let s
while (s = sample()) samples.push(s)
console.log(samples)
var voronoi = d3.voronoi()
.extent([[0, 0], [width, height]])
var canvas = d3.select('body').append('canvas')
.attr('width', width)
.attr('height', height)
var context = canvas.node().getContext('2d')
var image = new Image()
image.src = 'tacocatlit.jpg'
image.onload = start
function start () {
context.drawImage(image, 0, 0)
image = context.getImageData(0, 0, width, height)
var diagram = voronoi(samples)
var links = diagram.links()
polygons = diagram.polygons()
console.log('diagram, ', diagram)
context.fillStyle = 'green' // "#f00"; context.fill()
for (var i = 0, n = polygons.length; i < n; ++i) {
context.beginPath()
drawCell(polygons[i])
var x = Math.floor(polygons[i].data[0]),
y = Math.floor(polygons[i].data[1]),
q = (y * width + x) << 2
var color = d3.rgb(image.data[q + 0], image.data[q + 1], image.data[q + 2]) + ''
// console.log(color)
context.fillStyle = color
context.fill()
}
}
// Based on https://www.jasondavies.com/poisson-disc/
function poissonDiscSampler (width, height, radius) {
var k = 30, // maximum number of samples before rejection
radius2 = radius * radius,
R = 3 * radius2,
cellSize = radius * Math.SQRT1_2,
gridWidth = Math.ceil(width / cellSize),
gridHeight = Math.ceil(height / cellSize),
grid = new Array(gridWidth * gridHeight),
queue = [],
queueSize = 0,
sampleSize = 0
return function () {
if (!sampleSize) return sample(Math.random() * width, Math.random() * height)
// Pick a random existing sample and remove it from the queue.
while (queueSize) {
var i = Math.random() * queueSize | 0,
s = queue[i]
// Make a new candidate between [radius, 2 * radius] from the existing sample.
for (var j = 0; j < k; ++j) {
var a = 2 * Math.PI * Math.random(),
r = Math.sqrt(Math.random() * R + radius2),
x = s[0] + r * Math.cos(a),
y = s[1] + r * Math.sin(a)
// Reject candidates that are outside the allowed extent,
// or closer than 2 * radius to any existing sample.
if (x >= 0 && x < width && y >= 0 && y < height && far(x, y)) return sample(x, y)
}
queue[i] = queue[--queueSize]
queue.length = queueSize
}
}
function far (x, y) {
var i = x / cellSize | 0,
j = y / cellSize | 0,
i0 = Math.max(i - 2, 0),
j0 = Math.max(j - 2, 0),
i1 = Math.min(i + 3, gridWidth),
j1 = Math.min(j + 3, gridHeight)
for (j = j0; j < j1; ++j) {
var o = j * gridWidth
for (i = i0; i < i1; ++i) {
if (s = grid[o + i]) {
var s,
dx = s[0] - x,
dy = s[1] - y
if (dx * dx + dy * dy < radius2) return false
}
}
}
return true
}
function sample (x, y) {
var s = [x, y]
queue.push(s)
grid[gridWidth * (y / cellSize | 0) + (x / cellSize | 0)] = s
++sampleSize
++queueSize
return s
}
}
function drawSite (site) {
context.moveTo(site[0] + 2.5, site[1])
context.arc(site[0], site[1], 2.5, 0, 2 * Math.PI, false)
}
function drawLink (link) {
context.moveTo(link.source[0], link.source[1])
context.lineTo(link.target[0], link.target[1])
}
function drawCell (cell) {
if (!cell) return false
context.moveTo(cell[0][0], cell[0][1])
for (var j = 1, m = cell.length; j < m; ++j) {
context.lineTo(cell[j][0], cell[j][1])
}
context.closePath()
return true
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment