z_mirror/PipeVisualizer

git clone https://git.raiguard.me/z_mirror/PipeVisualizer

Caleb HeuerStore pipe connections in global, split and clean up code9371b54

2.0
6.1 KiB198 linesraw
1local flib_bounding_box = require("__flib__/bounding-box")
2local flib_position = require("__flib__/position")
3
4--- @alias RenderObjectID uint64
5
6--- @param id RenderObjectID
7local function destroy_if_valid(id)
8  if rendering.is_valid(id) then
9    rendering.destroy(id)
10  end
11end
12
13local layers = {
14  arrow = "195",
15  line = "194",
16  underground = "193",
17  entity = "192",
18}
19
20local pipe_types = {
21  ["infinity-pipe"] = true,
22  ["pipe-to-ground"] = true,
23  ["pipe"] = true,
24}
25
26local encoded_directions = {
27  [defines.direction.north] = 1,
28  [defines.direction.east] = 2,
29  [defines.direction.south] = 4,
30  [defines.direction.west] = 8,
31}
32
33--- @type Color
34local default_color = { r = 0.32, g = 0.32, b = 0.32, a = 0.4 }
35
36local renderer = {}
37
38--- @param it Iterator
39--- @param entity_data EntityData
40function renderer.draw(it, entity_data)
41  local is_complex_type = not pipe_types[entity_data.entity.type]
42  -- TODO: Build and cache boxes for all entities ahead of time
43  if is_complex_type then
44    local box = flib_bounding_box.resize(entity_data.entity.selection_box, -0.1)
45    entity_data.shape = rendering.draw_sprite({
46      sprite = "pv-entity-box",
47      tint = default_color,
48      x_scale = flib_bounding_box.width(box),
49      y_scale = flib_bounding_box.height(box),
50      render_layer = layers.entity,
51      target = entity_data.entity.position,
52      surface = entity_data.entity.surface_index,
53      players = { it.player_index },
54    })
55  else
56    local box = flib_bounding_box.ceil(entity_data.entity.selection_box)
57    entity_data.shape = rendering.draw_sprite({
58      sprite = "pv-pipe-connections-0",
59      tint = default_color,
60      x_scale = flib_bounding_box.width(box),
61      y_scale = flib_bounding_box.height(box),
62      render_layer = layers.line,
63      target = entity_data.entity.position,
64      surface = entity_data.entity.surface_index,
65      players = { it.player_index },
66    })
67  end
68  --- @type Color?
69  local shape_color
70  local highest_id = 0
71  local encoded_connections = 0
72  for id, connections in pairs(entity_data.connections) do
73    local color = it.systems[id]
74    if not color then
75      goto continue
76    end
77    if id > highest_id then
78      shape_color = color
79      highest_id = id
80    end
81    local objects = entity_data.connection_objects[id]
82    if not objects then
83      objects = {}
84      entity_data.connection_objects[id] = objects
85    end
86    for _, connection in pairs(connections) do
87      if not connection.target then
88        goto inner_continue
89      end
90
91      local direction = connection.direction
92      if is_complex_type then
93        if connection.flow_direction == "input" then
94          direction = (direction + 4) % 8 -- Opposite
95        end
96        local sprite = "pv-fluid-arrow-" .. connection.flow_direction
97        if connection.flow_direction ~= "input-output" and not pipe_types[connection.target_owner.type] then
98          sprite = "pv-fluid-arrow"
99        end
100        objects[#objects + 1] = rendering.draw_sprite({
101          sprite = sprite,
102          tint = color,
103          render_layer = layers.arrow,
104          orientation = direction / 8,
105          target = connection.shape_position,
106          surface = entity_data.entity.surface_index,
107          players = { it.player_index },
108        })
109      else
110        encoded_connections = bit32.bor(encoded_connections, encoded_directions[direction])
111      end
112
113      -- TODO: Investigate using a beam entity to avoid making N render objects
114      if connection.connection_type == "underground" then
115        if it.entities[connection.target_owner.unit_number] then
116          goto inner_continue
117        end
118        local target_connection_data =
119          connection.target.get_pipe_connections(connection.target_fluidbox_index)[connection.target_pipe_connection_index]
120        local target_position = target_connection_data.position
121        local distance = flib_position.distance(connection.position, target_connection_data.position)
122        if distance > 1 then
123          for i = 1, distance - 1 do
124            local target = flib_position.lerp(connection.position, target_position, i / distance)
125            objects[#objects + 1] = rendering.draw_sprite({
126              sprite = "pv-underground-connection",
127              tint = color,
128              render_layer = layers.underground,
129              orientation = direction / 8,
130              target = target,
131              surface = entity_data.entity.surface_index,
132              players = { it.player_index },
133            })
134          end
135          break
136        end
137      end
138
139      ::inner_continue::
140    end
141
142    ::continue::
143  end
144  if entity_data.shape and shape_color then
145    rendering.set_color(entity_data.shape, shape_color)
146  end
147  if encoded_connections > 0 then
148    rendering.set_sprite(entity_data.shape, "pv-pipe-connections-" .. encoded_connections)
149  end
150end
151
152--- @param entity_data EntityData
153function renderer.clear(entity_data)
154  destroy_if_valid(entity_data.shape)
155  entity_data.shape = nil
156  for _, objects in pairs(entity_data.connection_objects) do
157    for _, id in pairs(objects) do
158      destroy_if_valid(id)
159    end
160  end
161  entity_data.connection_objects = {}
162end
163
164--- @param iterator Iterator
165--- @param entity_data EntityData
166--- @param fluid_system_id FluidSystemID
167--- @return boolean should_remove
168function renderer.clear_system(iterator, entity_data, fluid_system_id)
169  local objects = entity_data.connection_objects[fluid_system_id]
170  if objects then
171    for _, id in pairs(objects) do
172      destroy_if_valid(id)
173    end
174    entity_data.connection_objects[fluid_system_id] = nil
175  end
176  local should_remove = not next(entity_data.connection_objects)
177  if should_remove then
178    destroy_if_valid(entity_data.shape)
179    entity_data.shape = nil
180  else
181    renderer.update_shape_color(iterator, entity_data)
182  end
183  return should_remove
184end
185
186--- @param iterator Iterator
187--- @param entity_data EntityData
188function renderer.update_shape_color(iterator, entity_data)
189  local highest_id = 0
190  for fluid_system_id in pairs(entity_data.connection_objects) do
191    if fluid_system_id > highest_id then
192      highest_id = fluid_system_id
193    end
194  end
195  rendering.set_color(entity_data.shape, highest_id > 0 and iterator.systems[highest_id] or default_color)
196end
197
198return renderer