diff --git a/app/styles/terrain_randomise.sass b/app/styles/terrain_randomise.sass
new file mode 100644
index 000000000..e6cf29bec
--- /dev/null
+++ b/app/styles/terrain_randomise.sass
@@ -0,0 +1,128 @@
+#terrain-randomise-modal
+
+  .choose-option
+    margin-bottom: 15px
+    width: 100%
+    height: 100px
+    overflow: hidden
+    background: white
+    border: 1px solid #333
+    position: relative
+
+    -webkit-transition: opacity 0.3s ease-in-out
+    -moz-transition: opacity 0.3s ease-in-out
+    -ms-transition: opacity 0.3s ease-in-out
+    -o-transition: opacity 0.3s ease-in-out
+    transition: opacity 0.3s ease-in-out
+    
+    opacity: 0.4
+    
+    border-radius: 5px
+    .only-one
+      -webkit-transition: opacity 0.3s ease-in-out
+      -moz-transition: opacity 0.3s ease-in-out
+      -ms-transition: opacity 0.3s ease-in-out
+      -o-transition: opacity 0.3s ease-in-out
+      transition: opacity 0.3s ease-in-out
+      opacity: 0
+    
+  .choose-option:hover
+    opacity: 1
+    .only-one
+      opacity: 1
+    
+  .my-icon
+    position: relative
+    left: 0
+    top: -10px
+    z-index: 1
+    
+  .my-team-icon
+    height: 60px
+    position: relative
+    top: -10px
+    left: 10px
+    z-index: 0
+    
+  .opponent-team-icon
+    height: 60px
+    position: relative
+    top: 10px
+    right: 10px
+    z-index: 0
+    float: right
+    -moz-transform: scaleX(-1)
+    -o-transform: scaleX(-1)
+    -webkit-transform: scaleX(-1)
+    transform: scaleX(-1)
+    filter: FlipH
+    -ms-filter: "FlipH"
+    
+  .opponent-icon
+    position: relative
+    float: right
+    right: 0
+    top: -10px
+    -moz-transform: scaleX(-1)
+    -o-transform: scaleX(-1)
+    -webkit-transform: scaleX(-1)
+    transform: scaleX(-1)
+    filter: FlipH
+    -ms-filter: "FlipH"
+    z-index: 1
+    
+  .name-label
+    border-bottom: 20px solid lightslategray
+    height: 0
+    width: 40%
+    position: absolute
+    bottom: 0
+    color: black
+    font-weight: bold
+    text-align: center
+    z-index: 2
+    
+    span
+      position: relative
+      top: 1px
+      
+  .my-name
+    border-right: 15px solid transparent
+    left: 0
+    span
+      left: 3px
+    
+  .preset-size
+    border-left: 15px solid transparent
+    right: 0
+    //text-align: right
+    span
+      right: 3px
+  
+  .preset-name
+    border-top: 25px solid darkgray
+    border-left: 20px solid transparent
+    border-right: 20px solid transparent
+    height: 0
+    width: 30%
+    position: absolute
+    left: 35%
+    top: 0
+    color: black
+    text-align: center
+    font-size: 18px
+    font-weight: bold
+
+    span
+      position: relative
+      top: -25px
+      
+  .easy-option .preset-name
+    border-top: 25px solid limegreen
+      
+  .medium-option .preset-name
+    border-top: 25px solid darkorange
+    
+  .hard-option .preset-name
+    border-top: 25px solid black
+    color: white
\ No newline at end of file
diff --git a/app/templates/editor/level/edit.jade b/app/templates/editor/level/edit.jade
index d04b8f4bd..e446f7613 100644
--- a/app/templates/editor/level/edit.jade
+++ b/app/templates/editor/level/edit.jade
@@ -79,6 +79,8 @@ block header
               a(data-i18n="common.fork")#fork-level-start-button Fork
             li(class=anonymous ? "disabled": "")
               a(data-toggle="coco-modal", data-target="modal/revert", data-i18n="editor.revert")#revert-button Revert
+            li(class=anonymous ? "disabled": "")
+              a(data-toggle="coco-modal", data-target="modal/terrain_randomise", data-i18n="editor.randomise")#randomise-button Randomise
             li(class=anonymous ? "disabled": "")
               a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n
             li.divider
diff --git a/app/templates/modal/terrain_randomise.jade b/app/templates/modal/terrain_randomise.jade
new file mode 100644
index 000000000..680af187c
--- /dev/null
+++ b/app/templates/modal/terrain_randomise.jade
@@ -0,0 +1,14 @@
+extends /templates/modal/modal_base
+
+block modal-header-content
+  h3(data-i18n="editor.pick_a_terrain") Pick a Terrain
+
+block modal-body-content
+  div#normal-view
+    a(href="#")
+        div.choose-option(data-preset-type="grassy", data-preset-size="small")
+          div.preset-size.name-label
+            span(data-i18n="ladder.small") Small
+          div.preset-name
+            span(data-i18n="ladder.grassy") Grassy
+    //- for model in models
\ No newline at end of file
diff --git a/app/views/editor/level/thangs_tab_view.coffee b/app/views/editor/level/thangs_tab_view.coffee
index 8e905654a..010b38b50 100644
--- a/app/views/editor/level/thangs_tab_view.coffee
+++ b/app/views/editor/level/thangs_tab_view.coffee
@@ -43,6 +43,7 @@ module.exports = class ThangsTabView extends View
     'sprite:mouse-up': 'onSpriteMouseUp'
     'sprite:double-clicked': 'onSpriteDoubleClicked'
     'surface:stage-mouse-up': 'onStageMouseUp'
+    'randomise:terrain-generated': 'onRandomiseTerrain'
 
   events:
     'click #extant-thangs-filter button': 'onFilterExtantThangs'
@@ -57,6 +58,8 @@ module.exports = class ThangsTabView extends View
     'delete, del, backspace': 'deleteSelectedExtantThang'
     'left': -> @moveAddThangSelection -1
     'right': -> @moveAddThangSelection 1
+    'ctrl+z': 'undoAction'
+    'ctrl+shift+z': 'redoAction'
 
   constructor: (options) ->
     super options
@@ -221,6 +224,11 @@ module.exports = class ThangsTabView extends View
     return unless e.thang
     @editThang thangID: e.thang.id
 
+  onRandomiseTerrain: (e) ->
+    for thang in e.thangs
+      @selectAddThangType thang.id
+      @addThang @addThangType, thang.pos
+
   # TODO: figure out a good way to have all Surface clicks and Treema clicks just proxy in one direction, so we can maintain only one way of handling selection and deletion
   onExtantThangSelected: (e) ->
     @selectedExtantSprite?.setNameLabel? null unless @selectedExtantSprite is e.sprite
@@ -450,6 +458,12 @@ module.exports = class ThangsTabView extends View
     $('#add-thangs-column').toggle()
     @onWindowResize e
 
+  undoAction: (e) ->
+    @thangsTreema.undo()
+
+  redoAction: (e) ->
+    @thangsTreema.redo()
+
 class ThangsNode extends TreemaNode.nodeMap.array
   valueClass: 'treema-array-replacement'
   getChildren: ->
diff --git a/app/views/modal/terrain_randomise_modal.coffee b/app/views/modal/terrain_randomise_modal.coffee
new file mode 100644
index 000000000..94b4676fb
--- /dev/null
+++ b/app/views/modal/terrain_randomise_modal.coffee
@@ -0,0 +1,187 @@
+ModalView = require 'views/kinds/ModalView'
+template = require 'templates/modal/terrain_randomise'
+CocoModel = require 'models/CocoModel'
+
+clusters = {
+  'rocks': ['Rock 1', 'Rock 2', 'Rock 3', 'Rock 4', 'Rock 5', 'Rock Cluster 1', 'Rock Cluster 2', 'Rock Cluster 3']
+  'trees': ['Tree 1', 'Tree 2', 'Tree 3', 'Tree 4']  
+  'shrubs': ['Shrub 1', 'Shrub 2', 'Shrub 3']
+  'houses': ['House 1', 'House 2', 'House 3', 'House 4']
+  'animals': ['Cow', 'Horse']
+  'wood': ['Firewood 1', 'Firewood 2', 'Firewood 3', 'Barrel']
+  'farm': ['Farm']
+}
+
+presets = {
+  # 'dungeon': {
+  #   'type':'dungeon'
+  #   'borders':['Dungeon Wall']
+  #   'floors':['Dungeon Floor']
+  #   'decorations':[]
+  # }
+  'grassy': {
+    'type':'grassy'
+    'borders':['Tree 1', 'Tree 2', 'Tree 3']
+    'floors':['Grass01', 'Grass02', 'Grass03']
+    'decorations': {
+      'house': {
+        'num':[1,2] #min-max
+        'width': 20
+        'height': 20
+        'clusters': {
+          'houses':[1,1]
+          'trees':[1,2]
+          'shrubs':[0,3]
+          'rocks':[1,2]
+        }
+      }
+      'farm': {
+        'num':[1,2] #min-max
+        'width': 20
+        'height': 20
+        'clusters': {
+          'farm':[1,1]
+          'shrubs':[2,3]
+          'wood':[2,4]
+          'animals':[2,3]
+        }
+      }
+    }
+  }
+}
+
+sizes = {
+  'small': {
+    'x':80
+    'y':68
+  }
+  'large': {
+    'x':160
+    'y':136
+  }
+  'floorSize': {
+    'x':20
+    'y':20
+  }
+  'borderSize': {
+    'x':4
+    'y':4
+  }
+}
+
+module.exports = class TerrainRandomiseModal extends ModalView
+  id: 'terrain-randomise-modal'
+  template: template
+  thangs = []
+
+  events:
+    'click .choose-option': 'onRandomise'
+
+  onRevertModel: (e) ->
+    id = $(e.target).val()
+    CocoModel.backedUp[id].revert()
+    $(e.target).closest('tr').remove()
+    @reloadOnClose = true
+
+  onRandomise: (e) ->
+    target = $(e.target)
+    presetType = target.attr 'data-preset-type'
+    presetSize = target.attr 'data-preset-size'
+    @randomiseThangs presetType, presetSize
+    Backbone.Mediator.publish('randomise:terrain-generated', 
+      'thangs': @thangs
+    )
+
+  randomiseThangs: (presetName, presetSize) ->
+    preset = presets[presetName]
+    presetSize = sizes[presetSize]
+    @thangs = []
+    @randomiseFloor preset, presetSize
+    @randomiseBorder preset, presetSize
+    @randomiseDecorations preset, presetSize
+
+  randomiseFloor: (preset, presetSize) ->
+    for i in _.range(0, presetSize.x, sizes.floorSize.x)
+      for j in _.range(0, presetSize.y, sizes.floorSize.y)
+        @thangs.push {
+          'id': @getRandomThang(preset.floors)
+          'pos': {
+            'x': i
+            'y': j
+          }
+        }
+
+  randomiseBorder: (preset, presetSize) ->
+    for i in _.range(0-sizes.floorSize.x/2+sizes.borderSize.x, presetSize.x-sizes.floorSize.x/2, sizes.borderSize.x)
+      @thangs.push {
+        'id': @getRandomThang(preset.borders)
+        'pos': {
+          'x': i
+          'y': 0-sizes.floorSize.x/2
+        }
+      }
+      @thangs.push {
+        'id': @getRandomThang(preset.borders)
+        'pos': {
+          'x': i
+          'y': presetSize.y - sizes.borderSize.y
+        }
+      }
+
+    for i in _.range(0-sizes.floorSize.y/2, presetSize.y-sizes.borderSize.y, sizes.borderSize.y)
+      @thangs.push {
+        'id': @getRandomThang(preset.borders)
+        'pos': {
+          'x': 0-sizes.floorSize.x/2+sizes.borderSize.x
+          'y': i
+        }
+      }
+      @thangs.push {
+        'id': @getRandomThang(preset.borders)
+        'pos': {
+          'x': presetSize.x - sizes.borderSize.x - sizes.floorSize.x/2
+          'y': i
+        }
+      }
+
+  randomiseDecorations: (preset, presetSize)->
+    for name, decoration of preset.decorations
+      for num in _.range(_.random(decoration.num[0], decoration.num[1]))
+        center = 
+        {
+          'x':_.random(decoration.width, presetSize.x - decoration.width), 
+          'y':_.random(decoration.height, presetSize.y - decoration.height)
+        }
+        min = 
+        {
+          'x':center.x - decoration.width/2
+          'y':center.y - decoration.height/2     
+        }
+        max = 
+        {
+          'x':center.x + decoration.width/2
+          'y':center.y + decoration.height/2     
+        }
+        for cluster, range of decoration.clusters
+          for i in _.range(_.random(range[0], range[1]))
+            @thangs.push {
+              'id':@getRandomThang(clusters[cluster])
+              'pos':{
+                'x':_.random(min.x, max.x)
+                'y':_.random(min.y, max.y)
+              }
+            }
+
+
+  getRandomThang: (thangList) ->
+    return thangList[_.random(0, thangList.length-1)]
+
+  getRenderData: ->
+    c = super()
+    models = _.values CocoModel.backedUp
+    models = (m for m in models when m.hasLocalChanges())
+    c.models = models
+    c
+
+  onHidden: ->
+    location.reload() if @reloadOnClose