<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
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. //cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js
  2. //cdn.rawgit.com/nosir/obelisk.js/master/build/obelisk.js
  3. //cdn.rawgit.com/josephg/noisejs/master/perlin.js
  4. //cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js