displacement-map
Version:
Mid-point displacement map
223 lines (172 loc) • 4.2 kB
JavaScript
/**
* Worker thread
*/
/**
* @input Uint8Array map
* @input width
* @input height
* @output Uint8Array
*/
var map, height, width, roughness
function clamp( num ) {
return Math.min( Math.max( num, 0 ), 0xff )
}
function to1d( x, y ) {
return ( y * width ) + x
}
function wrapX( num ) {
var min = 0
var max = width - 1
if ( num > max ) {
return num % max
}
if ( num < min ) {
return max - ( ( min - num ) % ( max - min ) )
}
return num
}
function wrapY( num ) {
var min = 0
var max = height - 1
if ( num > max ) {
return num % max
}
if ( num < min ) {
return max - ( ( min - num ) % ( max - min ) )
}
return num
}
function get( x, y ) {
return map[ to1d( wrapX( x ), wrapY( y ) ) ]
}
function set( x, y, value ) {
map[ to1d( wrapX( x ), wrapY( y ) ) ] = value & 0xff
}
/**
* Returns +-127
*/
function variance() {
return -0x80 + ( Math.random() * 0xff )
}
/**
* Total variance * roughness * step
* Step decreases with each fold to produce a better result
*/
function getMidpointDisplacement( size ) {
return clamp( variance() * roughness * ( size / ( width - 1 ) ) )
}
/**
* Averages (mean) all values passed as args
*/
function getAvg() {
return Array.prototype.reduce.call( arguments, function( prev, num ) {
return prev + num
}) / arguments.length
}
/**
* Grabs the average of each edge point
*/
function getAvgEdge( x1, y1, x2, y2 ) {
var size = ~~( x2 - x1 ) / 2
return getAvg(
get( x1 + size, y1 ),
get( x1, y1 + size ),
get( x2, y1 + size ),
get( x1 + size, y2 )
)
}
/**
* Grabs the average from each corner point
*/
function getAvgCorner( x1, y1, x2, y2 ) {
return getAvg(
get( x1, y1 ),
get( x2, y1 ),
get( x1, y2 ),
get( x2, y2 )
)
}
/**
* Get average from a central point
*/
function getAvgPoint( x, y, size ) {
return getAvg(
get( x, y - size ),
get( x + size, y ),
get( x - size, y ),
get( x, y + size )
)
}
/**
* @param cb <function> iterator to call
* @param step <float> 0...1
*/
function iterate( cb, step ) {
var size = ( width - 1 ) * step
for ( var x = 0; x < width - 1; x += size ) {
for ( var y = 0; y < height - 1; y += size ) {
cb( x, y, size )
}
}
}
/**
* Performs the square step - generating a point in the center
* @param x <number> x component of top-left corner
* @param y <number> y component of top-left corner
* @param size <number> size of square to calculate mid-point of
*/
function performSquareStep( x, y, size ) {
var mid = size / 2
var avg = getAvgCorner( x, y, x + size, y + size )
set( x + mid, y + mid, avg + getMidpointDisplacement( size ) )
}
/**
* Performs the diamond step - generating points at the edges
* @param x <number> x component of top-left corner
* @param y <number> y component of top-left corner
* @param size <number> size of square to calculate edges of
*/
function performDiamondStep( x, y, size ) {
var mid = size / 2
var setCell = function( xx, yy ) {
var avg = getAvgPoint( xx, yy, mid )
//console.log( 'setting diamond point', xx, yy, avg, 'for', x, y, size )
set( xx, yy, avg + getMidpointDisplacement( size ) )
}
setCell( x + mid, y )
setCell( x + mid, y + size )
setCell( x, y + mid )
setCell( x + size, y + mid )
}
/**
* Starts the generation
* @private
* @param step <float:optional> the step value or 1
*/
function generate( step ) {
if ( !step ) {
step = 1
}
var size = ( width - 1 ) * step
iterate( performSquareStep, step )
iterate( performDiamondStep, step )
if ( size > 2 ) {
generate( step / 2 )
return
}
//this.emit( 'done' )
done()
}
self.addEventListener( 'message', function( event ) {
map = event.data.map
height = event.data.height
width = event.data.width
roughness = event.data.roughness || 1
//@TODO seed
generate( 1 )
})
function done() {
self.postMessage({
map: map
})
}