Rotate optics and doors from NodeCore with ease.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

106 lines
3.9 KiB

local ipairs, unpack
= ipairs, unpack
local math_min, math_max, math_floor, math_rad
= math.min, math.max, math.floor, math.rad
local minetest, vector
= minetest, vector
local rotate = {}
-- Axis lookup table based on how Minetest's `facedir` operates.
local AXIS_LOOKUP = {
vector.new( 0, 1, 0), -- +Y
vector.new( 0, 0, 1), -- +Z
vector.new( 0, 0, -1), -- -Z
vector.new( 1, 0, 0), -- +X
vector.new(-1, 0, 0), -- -X
vector.new( 0, -1, 0), -- -Y
}
-- Lookup table to find out how to rotate for each `facedir` axis.
local AXIS_ROTATION = {
nil, -- No rotation.
{ vector.new(1, 0, 0), 90 },
{ vector.new(1, 0, 0), -90 },
{ vector.new(0, 0, 1), -90 },
{ vector.new(0, 0, 1), 90 },
{ vector.new(0, 0, 1), 180 },
}
-- Takes an axis vector and returns its index in the `AXIS_LOOKUP` table.
-- Returns `nil` for any vector that is not a valid axis vector.
local function axis_vector_to_index(vec)
if vec.y == 1 then if vec.x == 0 and vec.z == 0 then return 1 end
elseif vec.z == 1 then if vec.x == 0 and vec.y == 0 then return 2 end
elseif vec.z == -1 then if vec.x == 0 and vec.y == 0 then return 3 end
elseif vec.x == 1 then if vec.y == 0 and vec.z == 0 then return 4 end
elseif vec.x == -1 then if vec.y == 0 and vec.z == 0 then return 5 end
elseif vec.y == -1 then if vec.x == 0 and vec.z == 0 then return 6 end
else return nil end
end
local FACEDIR_LOOKUP = {}
for up_index, up in ipairs(AXIS_LOOKUP) do
FACEDIR_LOOKUP[up_index] = {}
for rot = 0, 3 do
local facedir = (up_index - 1) * 4 + rot
local back = minetest.facedir_to_dir(facedir)
local back_index = axis_vector_to_index(back)
FACEDIR_LOOKUP[up_index][back_index] = facedir
end
end
-- Returns whether the specified `node` is rotatable by this mod.
local function is_rotatable(node)
local name = node and node.name
local def = minetest.registered_nodes[name]
return def and def.paramtype2 == "facedir"
end
rotate.is_rotatable = is_rotatable
local function rotate_box_by_facedir(box, facedir)
if facedir == 0 then return end
local axis_index = math_floor(facedir / 4)
if axis_index ~= 0 then
local axis, degrees = unpack(AXIS_ROTATION[1 + axis_index])
box.min = box.min:rotate_around_axis(axis, math_rad(degrees))
box.max = box.max:rotate_around_axis(axis, math_rad(degrees))
end
local axis_rot = facedir % 4
if axis_rot ~= 0 then
local axis = AXIS_LOOKUP[1 + axis_index]
local degrees = axis_rot * 90
box.min = box.min:rotate_around_axis(axis, math_rad(degrees))
box.max = box.max:rotate_around_axis(axis, math_rad(degrees))
end
-- Recalculate the proper minimum and maximum bounds, since we just rotated these vectors.
box.min.x, box.max.x = math_min(box.min.x, box.max.x), math_max(box.min.x, box.max.x)
box.min.y, box.max.y = math_min(box.min.y, box.max.y), math_max(box.min.y, box.max.y)
box.min.z, box.max.z = math_min(box.min.z, box.max.z), math_max(box.min.z, box.max.z)
end
rotate.rotate_box_by_facedir = rotate_box_by_facedir
-- Rotates `node` at the specified `pos` around `axis` by `degrees` counter-clockwise.
local function rotate_node(pos, node, axis, degrees)
if degrees % 90 ~= 0 then error("degrees must be divisible by 90") end
if axis_vector_to_index(axis) == nil then error("axis must be an axis vector") end
if not is_rotatable(node) then error("node is not rotatable") end
local up = AXIS_LOOKUP[1 + math_floor(node.param2 / 4)]
local back = minetest.facedir_to_dir(node.param2)
up = up :rotate_around_axis(axis, math_rad(degrees)):round()
back = back:rotate_around_axis(axis, math_rad(degrees)):round()
local up_index = axis_vector_to_index(up)
local back_index = axis_vector_to_index(back)
node.param2 = FACEDIR_LOOKUP[up_index][back_index]
minetest.set_node(pos, node)
end
rotate.rotate_node = rotate_node
return rotate