File: hyper_paint.lua

package info (click to toggle)
crawl 2%3A0.34.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 100,204 kB
  • sloc: cpp: 363,900; ansic: 27,765; javascript: 9,516; python: 8,463; perl: 3,293; java: 3,132; xml: 2,380; makefile: 1,835; sh: 611; objc: 250; cs: 15; sed: 9; lisp: 3
file content (157 lines) | stat: -rw-r--r-- 6,753 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
------------------------------------------------------------------------------
-- hyper_paint.lua:
--
-- Functions for painting onto usage grids (for whole layouts and for
-- smaller code rooms).
--
-- TODO: Loads of this file is redundant with procedural primitives. Really
-- we just need some sensible mappings of procedurals to paint shapes and
-- it'll be a lot more flexible.
------------------------------------------------------------------------------

hyper.paint = {}

function hyper.paint.paint_grid(paint, options, usage_grid)

  for i,item in ipairs(paint) do
    local feature,feature_type
    if item.type == "floor" then
      feature_type = "floor"
    elseif item.type == "wall" then
      feature_type = "wall"
    elseif item.type == "space" or item.type == "proc" then
      feature_type = "space"
    elseif item.feature ~= nil then
      feature_type = item.feature
    end

    -- Check which shape to paint
    local shape_type = "quad"
    if item.shape ~= nil then shape_type = item.shape end

    -- Get information about the feature
    local feature,space,solid,wall = hyper.paint.feature_flags(feature_type,options)
    local feature_usage = item.usage

    -- These three flags are in addition to the feature and affect how the builder treats the squares during
    -- placement and subsequently.
    local open = (item.open ~= nil and item.open) -- Further rooms can be placed in this area. Be careful (for now) to leave a border around this so as not to block connections
    local exit = (item.exit ~= nil and item.exit) -- Same as using @ in vaults, allows this square to have connections adjacent, blocks any non-exit squares from being used as such

    if item.corner1 == nil then item.corner1 = { x = 0, y = 0 } end
    if item.corner2 == nil then item.corner2 = { x = usage_grid.width-1, y = usage_grid.height-1 } end

    -- Paint features onto grid
    -- PERF: Can slightly optimise this loop (which could be important since it gets called a lot)
    -- by deciding which function we're going to use first rather than have this unwieldy if statement
    if shape_type == "quad" or shape_type == "ellipse" or shape_type == "trapese" or item.type == "proc" then
      -- Set layout details in the painted area
      for x = item.corner1.x, item.corner2.x, 1 do
        for y = item.corner1.y, item.corner2.y, 1 do
          if item.type == "proc" or shape_type == "quad"
            or (shape_type == "ellipse" and hyper.paint.inside_oval(x,y,item))
            or (shape_type == "trapese" and hyper.paint.inside_trapese(x,y,item))
            or (type(shape_type) == "function" and hyper.paint.inside_custom(x,y,item)) then

            -- For procedural painting we get the feature cell-by-cell
            if item.type == "proc" then
              local mx,my = hyper.paint.map_to_unit(x,y,item)
              -- TODO: Allow proc paint to return an additional parameter containing other properties e.g. tile, monsters, clouds, items, stuff...
              --       (Although often we just want to return generic floor shapes and then tile and populate them later)
              feature_type,feature_usage = item.callback(x,y,mx,my)
              if feature_type ~= nil then
                feature,space,solid,wall = hyper.paint.feature_flags(feature_type,options)
              else
                feature = nil
              end
            end

            -- Work out where to actually paint
            if feature ~= nil then
              local ax,ay = x,y
              if item.wrap then
                ax = x % usage_grid.width
                ay = y % usage_grid.height
              end
              local usage = { solid = solid, feature = feature, space = space, open = open, wall = wall }
              if feature_usage ~= nil then
                hyper.merge_options(usage,feature_usage)
              end
              hyper.usage.set_usage(usage_grid,ax,ay, usage)
            end
          end
        end
      end
    elseif shape_type == "plot" then
      if item.points ~= nil then
        for i,pos in ipairs(item.points) do
          hyper.usage.set_usage(layout_grid, pos.x, pos.y,{ solid = solid, feature = feature, space = space, open = open, wall = wall })
        end
      else
        hyper.usage.set_usage(layout_grid, item.x, item.y, { solid = solid, feature = feature, space = space, open = open, wall = wall })
      end
    end
  end

end

function hyper.paint.feature_flags(feature_type,options)
  local feature
  if feature_type == "floor" then
    feature = options.layout_floor_type
  elseif feature_type == "wall" then
    feature = options.layout_wall_type or "rock_wall"
  elseif feature_type == "space" then
    feature = "space"
  end

  local space = (feature == "space")
  local solid = not space and not (feat.has_solid_floor(feature) or feat.is_door(feature))
  local wall = feat.is_wall(feature)
  return feature,space,solid,wall
end

-- Maps draw positions onto a unit square <0,0> to <1,1>
-- TODO: Something is going wrong with this algorithm because a 4x4 circle shouldn't look
--       like a square, but I don't see anything wrong with the math.
function hyper.paint.map_to_unit(x,y,item)

  local sx,sy = item.corner2.x - item.corner1.x,item.corner2.y - item.corner1.y

  -- Slightly scale down the input grid to add 0.5 squares padding on all sides. This should solve accuracy errors
  -- we otherwise get at the edges of shapes from attempting to draw a continuous geometric shape onto a discrete grid.
  local rx,ry = (x - item.corner1.x) * (sx-1)/sx + 0.5,(y - item.corner1.y) * (sy-1)/sy + 0.5

  -- Finally map onto the unit square
  return rx/sx,ry/sy
end

-- Determine if a point is inside an oval
-- TODO: http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
--       http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
--       Using a variant of that algorithm and adapt to ovals might give more reliable results and be quicker
function hyper.paint.inside_oval(x,y,item)
  -- Circle test
  local ax,ay = hyper.paint.map_to_unit(x,y,item)
  return (ax * 2 - 1) ^ 2 + (ay * 2 - 1) ^ 2 <= 1
end

-- Determine if a point is inside a simple trapeze
-- TODO: Handle rotation and allow offsetting the top and bottom on the x axis for more complex shapes
function hyper.paint.inside_trapese(x,y,item)

  local width1 = item.width1
  local width2 = item.width2
  if width1 == nil then width1 = 0 end
  if width2 == nil then width2 = 1 end

  local ax,ay = hyper.paint.map_to_unit(x,y,item)
  local expected_width = ay * width2 + (1-ay) * width1
  return math.abs(ax-0.5) < (expected_width / 2)

end

function hyper.paint.inside_custom(x,y,item)
  local ax,ay = hyper.paint.map_to_unit(x,y,item)
  return item.shape_type(x,y,ax,ay,item)
end