-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathpolygon.lua
226 lines (197 loc) · 6.79 KB
/
polygon.lua
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
-- https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm#C++_implementation
-- Converted from C++ to Lua
-- License: CC BY-SA https://creativecommons.org/licenses/by-sa/4.0/
local function ray_intersects_triangle(ray_origin, ray_vector, vertex_a, vertex_b, vertex_c)
local epsilon = 0.0000001
local edge1 = vector.subtract(vertex_b, vertex_a)
local edge2 = vector.subtract(vertex_c, vertex_a)
local ray_cross_e2 = vector.cross(ray_vector, edge2)
local det = vector.dot(edge1, ray_cross_e2)
if det > -epsilon and det < epsilon then
return -- This ray is parallel to this triangle.
end
local inv_det = 1.0 / det
local s = vector.subtract(ray_origin, vertex_a)
local u = inv_det * vector.dot(s, ray_cross_e2)
if u < 0 or u > 1 then return end
local s_cross_e1 = vector.cross(s, edge1)
local v = inv_det * vector.dot(ray_vector, s_cross_e1)
if v < 0 or u + v > 1 then return end
-- At this stage we can compute t to find out where the intersection point is on the line.
local t = inv_det * vector.dot(edge2, s_cross_e1)
if t > epsilon then -- ray intersection
return vector.add(ray_origin, vector.multiply(ray_vector, t))
else -- This means that there is a line intersection but not a ray intersection.
return
end
end
function edit.calculate_triangle_points(a, b, c)
local bounding_box_min = table.copy(a)
local bounding_box_max = table.copy(a)
for index, axis in pairs({"x", "y", "z"}) do
bounding_box_min[axis] = math.min(a[axis], b[axis], c[axis])
bounding_box_max[axis] = math.max(a[axis], b[axis], c[axis])
end
-- Calculate normal
local u = vector.subtract(b, a)
local v = vector.subtract(c, a)
local normal = vector.new(
u.y * v.z - u.z * v.y,
u.z * v.x - u.x * v.z,
u.x * v.y - u.y * v.x
)
local selected_axis = "y"
local longest_length = 0
for axis, length in pairs(normal) do
length = math.abs(length)
if length > longest_length then
longest_length = length
selected_axis = axis
end
end
-- Switch from local to global coordinate system.
-- Also works the same to convert local to global coordinate system.
local function swap_coord_sys(v)
v = table.copy(v)
local old_selected = v[selected_axis]
v[selected_axis] = v.y
v.y = old_selected
return v
end
local bounding_box_min_local = swap_coord_sys(bounding_box_min)
local bounding_box_max_local = swap_coord_sys(bounding_box_max)
local a_local = swap_coord_sys(a)
local b_local = swap_coord_sys(b)
local c_local = swap_coord_sys(c)
local results = {}
for x = bounding_box_min_local.x, bounding_box_max_local.x do
for z = bounding_box_min_local.z, bounding_box_max_local.z do
local intersection = ray_intersects_triangle(vector.new(x, 30928, z), vector.new(0, -1, 0), a_local, b_local, c_local)
if intersection then
table.insert(results, vector.round(swap_coord_sys(intersection)))
end
end
end
return results
end
local function place_polygon(player, item_name)
local player_data = edit.player_data[player]
if not player_data then return end
if not item_name or #player_data.polygon_markers < 2 then
player_data.polygon_markers.object:remove()
return
end
local markers = player_data.polygon_markers
local inf = 1 / 0
local bounding_box_min = vector.new(inf, inf, inf)
local bounding_box_max = vector.new(-inf, -inf, -inf)
for index, axis in pairs({"x", "y", "z"}) do
for i, marker in ipairs(markers) do
bounding_box_min[axis] = math.min(bounding_box_min[axis], marker._pos[axis])
bounding_box_max[axis] = math.max(bounding_box_max[axis], marker._pos[axis])
end
end
local volume = vector.add(vector.subtract(bounding_box_max, bounding_box_min), vector.new(1, 1, 1))
if volume.x * volume.y * volume.z > edit.max_operation_volume then
edit.display_size_error(player)
player_data.polygon_markers.object:remove()
return
end
player_data.undo_schematic = edit.schematic_from_map(bounding_box_min, volume)
local points = {}
for i = 3, #markers do
table.insert_all(
points,
edit.calculate_triangle_points(
markers[i]._pos,
markers[i - 1]._pos,
markers[1]._pos
)
)
end
local item = {name = item_name}
edit.place_item_like_player(player, item, markers[1]._pos)
item.param2 = minetest.get_node(markers[1]._pos).param2
for i, pos in pairs(points) do
edit.place_item_like_player(player, item, pos)
end
player_data.polygon_markers.object:remove()
end
minetest.register_entity("edit:polygon", {
initial_properties = {
visual = "cube",
visual_size = { x = 1.1, y = 1.1 },
physical = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
hp_max = 1,
textures = {
"edit_polygon.png",
"edit_polygon.png",
"edit_polygon.png",
"edit_polygon.png",
"edit_polygon.png",
"edit_polygon.png",
},
},
on_deactivate = function(self)
local player_data = edit.player_data[self._placer]
if player_data then
local index = table.indexof(player_data.polygon_markers, self)
table.remove(player_data.polygon_markers, index)
local marker = player_data.polygon_markers[1]
if index == 1 and marker then
local textures = marker.object:get_properties().textures
for i, texture in pairs(textures) do
textures[i] = texture .. "^[multiply:green"
end
marker.object:set_properties({textures = textures})
end
end
player_data.old_pointed_pos = nil
end,
})
local function polygon_on_place(itemstack, player, pointed_thing)
if not edit.on_place_checks(player) then return end
if not pointed_thing.above then
pointed_thing = edit.get_pointed_thing_node(player)
end
local pos = edit.pointed_thing_to_pos(pointed_thing)
if not pos then return end
local player_data = edit.player_data[player]
if not player_data.polygon_markers then
player_data.polygon_markers = {}
player_data.polygon_markers.object = player_data.polygon_markers
player_data.polygon_markers.object.remove = function(self)
for i, luaentity in ipairs(table.copy(self)) do
luaentity.object:remove()
end
end
end
if player_data.polygon_markers[1] and vector.equals(player_data.polygon_markers[1]._pos, pos) then
edit.player_select_item(player, "Select item to fill the polygon", place_polygon)
return
end
local marker = edit.add_marker("edit:polygon", pos, player)
if not marker then return end
table.insert(player_data.polygon_markers, marker)
if marker == player_data.polygon_markers[1] then
local textures = marker.object:get_properties().textures
for i, texture in pairs(textures) do
textures[i] = texture .. "^[multiply:green"
end
marker.object:set_properties({textures = textures})
end
end
minetest.register_tool("edit:polygon", {
description = "Edit Polygon",
tiles = {"edit_polygon.png"},
inventory_image = "edit_polygon.png",
range = 10,
groups = {edit_place_preview = 1,},
on_place = polygon_on_place,
on_secondary_use = polygon_on_place,
})