<canvas id="perlin"></canvas>
html, body {
margin: 0;
padding: 0;
border: 0;
background: #111;
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: small;
}
canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
View Compiled
# i'd like to apologize for using coffeescript here.
# it was a weird time in my life.
# don't judge me. or do, whatever.
$(->
perlin.init()
gui.init(perlin)
)
if !window.requestAnimationFrame
window.requestAnimationFrame = window.msRequestAnimationFrame or window.mozRequestAnimationFrame or window.webkitRequestAnimationFrame or window.oRequestAnimationFrame
if !window.requestAnimationFrame
throw new Error 'Unable to use this map without support for requestAnimationFrame'
gui =
init: (target)->
gui = new dat.GUI()
timeshift = gui.addFolder('Timeshift')
timeshift.add(target, 'timeshift')
timeshift.add(target, 'timeshiftSpeed',
Fast: 0.1
Normal: 0.01
Slow: 0.001
)
.onFinishChange((value)->
target.timeshiftSpeed = parseFloat(target.timeshiftSpeed)
)
timeshift.open()
dimensions = gui.addFolder('Dimensions')
dimensions.add(target, 'cubeSize', 8, 32)
.step 2
.onChange ->
target.generateCubes()
target.generatePoints()
dimensions.add(target, 'mapSize', 25, 200)
.step 5
.onChange ->
target.generatePoints()
dimensions.add(target, 'zAmplify', 0, 3)
.step 0.1
.onChange ->
target.generatePoints()
dimensions.add(target, 'noiseScale', 5, 50)
.step 1
.onChange ->
target.generatePoints()
dimensions.open()
gui.add(target, 'regenerate')
perlin =
init: ->
@cubeSize = 16
@mapSize = 50
@timeshift = false
@timeshiftSpeed = 0.01
@zAmplify = 0.5
@noiseScale = 20
@canvas = $ '#perlin'
@context = @canvas[0].getContext('2d')
@setContextSize()
@setResizeHandler()
@origin = new obelisk.Point(@canvas.width()/2, @cubeSize*3)
@pixelView = new obelisk.PixelView(@canvas, @origin)
# @color = new obelisk.CubeColor().getByHorizontalColor(
# obelisk.ColorPattern.GRAY
# )
@generateNoiseMachine()
@generateCubes()
@generatePoints()
@setKeyListener()
@setDraggable(true, @canvas)
@animateLoop()
regenerate: ->
@generateNoiseMachine()
@generatePoints()
generateNoiseMachine: ->
#@noise = new noiseMachine @mapSize
#@noise.setScale 1024
noise.seed(Math.random())
time: 0
animateLoop: ->
window.requestAnimationFrame @animateLoop.bind(@)
if @timeshift
@time += @timeshiftSpeed
@generatePoints()
@drawMap()
setContextSize: ->
canvSize =
width: @canvas.width()
height: @canvas.height()
@context.canvas.width = canvSize.width
@context.canvas.height = canvSize.height
setKeyListener: ->
$(window).on 'keydown', (event)=>
switch event.keyCode
when 37 then @origin.x += 10 #left
when 39 then @origin.x -= 10 #right
when 40 then @origin.y -= 10 #down
when 38 then @origin.y += 10 #up
setResizeHandler: ->
if @resizeTimer
clearTimeout @resizeTimer
@resizeTimer = null
$(window)
.off 'resize.perlin'
.on 'resize.perlin', =>
clearTimeout @resizeTimer
@resizeTimer = setTimeout (=>
@setContextSize()
), 50
getLevelAt: (x, y)->
# noiseVal = @noise.noiseAt(x, y, 0)
noiseVal = (noise.simplex3(x/@noiseScale, y/@noiseScale, @time) + 1)/2
Math.floor( (@cubes.length-1) * noiseVal)
getZForLevel: (level)->
level*(@cubeSize*@zAmplify)
generatePoints: ->
@points = []
for x in [0...@mapSize]
@points[x] = []
for y in [0...@mapSize]
level = @getLevelAt(x, y)
@points[x][y] = new obelisk.Point3D(x*@cubeSize, y*@cubeSize, @getZForLevel(level))
generateCubes: ->
rawColors = [
0x3438FF
0x3349F3
0x3349F3 #^^ water
0xA88E6F #dark sand
0xCEAE88 #light sand
0x30C4A3 #vv grass
0x2FD697
0x2FE88C
]
@dimension = new obelisk.CubeDimension(@cubeSize, @cubeSize, @cubeSize*3)
@cubes = []
for color in rawColors
@cubes.push new obelisk.Cube(
@dimension
new obelisk.CubeColor().getByHorizontalColor(color)
false
)
drawMap: ->
@pixelView.clear();
for x in [0...@points.length]
for y in [0...@points[x].length]
level = @getLevelAt(x, y)
@pixelView.renderObject(
@cubes[level]
@points[x][y]
);
setDraggable: (bool, $target)->
if !bool and @scopedDrag
@scopedDrag.cleanup()
else
originPoint = @origin
@scopedDrag =
mousedown: (event)->
event.preventDefault()
@startX = event.pageX
@startY = event.pageY
@win
.on 'mouseup.isoDrag', @mouseup.bind(@)
.on 'mousemove.isoDrag', @mousemove.bind(@)
mouseup: (event)->
event.preventDefault()
@win.off '.isoDrag'
delete @startX
delete @startY
mousemove: (event)->
event.preventDefault()
if @startX? and @startX != event.pageX
originPoint.x -= @startX - event.pageX
if @startY? and @startY != event.pageY
originPoint.y -= @startY - event.pageY
@startX = event.pageX
@startY = event.pageY
init: ->
@win = $(window)
$target.on 'mousedown.isoDrag', @mousedown.bind(@)
cleanup: ->
$target.off '.isoDrag'
@win.off '.isoDrag'
delete @win
@scopedDrag.init()
###
noiseMachine = (size, seed)->
@seedLen = 1024 ## for 1024, the max (before wrapping) is 10240
@seed = if seed then seed.split(',') else @generateSeed()
if @seed.length != @seedLen
throw new Error 'Seed length does not match given noise map size.'
if size < 32 or size > 10240
throw new Error 'Maximum map sizes are between 32 and 10240.'
@_maxSize = size;
@setScale( this.seedLen / 2 );
@setNormalize( this.seedLen * 5 );
noiseMachine.prototype =
generateSeed: ->
## Fisher-Yates algorithm ## http:##en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
## SUGGESTION FROM: http:##sroucheray.org/blog/2009/11/array-sort-should-not-be-used-to-shuffle-an-array/
seed = []
for i in [0...@seedLen]
seed.push(i+1);
i = seed.length
j = null
temp = null
while --i
j = Math.floor( Math.random() * ( i + 1 ) )
temp = seed[i]
seed[i] = seed[j]
seed[j] = temp
return seed
toString: ->
@seed.join(',');
setScale: (scale)->
@_noiseScale = scale;
setNormalize: (norm)->
@_normalize = norm
fade: (t)->
return t * t * t * (t * (t * 6 - 15) + 10)
lerp: ( t, a, b)->
return a + t * (b - a)
grad: (hash, x, y, z)->
h = hash & 15 ## CONVERT LO 4 BITS OF HASH CODE
u = h<8 ? x : y ## INTO 12 @gradIENT DIRECTIONS.
v = h<4 ? y : h==12||h==14 ? x : z
return ((h&1) == 0 ? u : 0-u) + ((h&2) == 0 ? v : 0-v)
scale: (n)->
return (1 + n)/2
noiseAt: (orig_x, orig_y, orig_z)->
orig_z = if !orig_z then 0 else orig_z;
if orig_x < 0 or orig_y < 0 or orig_x > @_maxSize or orig_y > @_maxSize
return 0
## this sets boundaries so the map doesn't wrap
x = ( @_noiseScale * ( orig_x / @_normalize ) )
y = ( @_noiseScale * ( orig_y / @_normalize ) )
z = ( @_noiseScale * ( orig_z / @_normalize ) )
seed = @seed.slice(0);
seed = seed.concat(seed);
X = Math.floor(x) & (@seedLen-1) ## FIND UNIT CUBE THAT
Y = Math.floor(y) & (@seedLen-1) ## CONTAINS POINT.
Z = Math.floor(z) & (@seedLen-1);
x -= Math.floor(x) ## FIND RELATIVE X,Y,Z
y -= Math.floor(y) ## OF POINT IN CUBE.
z -= Math.floor(z)
u = @fade(x) ## COMPUTE @fade CURVES
v = @fade(y) ## FOR EACH OF X,Y,Z.
w = @fade(z);
A = seed[X]+Y
AA = seed[A]+Z
AB = seed[A+1]+Z ## HASH COORDINATES OF
B = seed[X+1]+Y
BA = seed[B]+Z
BB = seed[B+1]+Z ## THE 8 CUBE CORNERS,
val = @scale(@lerp(w, @lerp(v, @lerp(u, @grad(seed[AA ], x , y , z ), ## AND ADD
@grad(seed[BA ], x-1, y , z )), ## BLENDED
@lerp(u, @grad(seed[AB ], x , y-1, z ), ## RESULTS
@grad(seed[BB ], x-1, y-1, z ))),## FROM 8
@lerp(v, @lerp(u, @grad(seed[AA+1], x , y , z-1 ), ## CORNERS
@grad(seed[BA+1], x-1, y , z-1 )), ## OF CUBE
@lerp(u, @grad(seed[AB+1], x , y-1, z-1 ),
@grad(seed[BB+1], x-1, y-1, z-1 )))))
## ## THE FOLLOWING FADES the land into the water
edgefade = Math.floor( @_maxSize / 2 ) ## 10
x_dist = if orig_x <= edgefade then orig_x else if ( this._maxSize - orig_x ) <= edgefade then this._maxSize - orig_x else 0
y_dist = if orig_y <= edgefade then orig_y else if ( this._maxSize - orig_y ) <= edgefade then this._maxSize - orig_y else 0
if orig_x == 0 or orig_x == @_maxSize or ( x_dist > 0 && x_dist < edgefade )
val -= ( 1 / x_dist )
if orig_y == 0 or orig_y == this._maxSize or ( y_dist > 0 && y_dist < edgefade )
val -= ( 1 / y_dist )
if val < 0
val = 0
## ## THE PRECEDING FADES the land into the water
return val
###
View Compiled
This Pen doesn't use any external CSS resources.