|
|
|
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
|
|
|
|
|
|
|
|
-- Rotates (or "spins") the specified `node` to face `facedir`,
|
|
|
|
-- emulating the behavior of NodeCore's spin functionality.
|
|
|
|
local function rotate_node(pos, node, facedir)
|
|
|
|
local def = minetest.registered_nodes[node.name]
|
|
|
|
if (not def) or def.paramtype2 ~= "facedir" then error("Node's paramtype2 must be 'facedir'") end
|
|
|
|
minetest.swap_node(pos, nodecore.underride({ param2 = facedir }, node))
|
|
|
|
nodecore.node_sound(pos, "place")
|
|
|
|
if def.on_spin then def.on_spin(pos, node) end
|
|
|
|
end
|
|
|
|
rotate.rotate_node = rotate_node
|
|
|
|
|
|
|
|
-- Returns the `up` and `back` vectors that make up the specified `facedir`.
|
|
|
|
local function vectors_from_facedir(facedir)
|
|
|
|
local up = AXIS_LOOKUP[1 + math_floor(facedir / 4)]
|
|
|
|
local back = minetest.facedir_to_dir(facedir)
|
|
|
|
return up, back
|
|
|
|
end
|
|
|
|
rotate.vectors_from_facedir = vectors_from_facedir
|
|
|
|
|
|
|
|
-- Returns a `facedir` constructed from the specified `up` and `back` vectors.
|
|
|
|
local function facedir_from_vectors(up, back)
|
|
|
|
local up_index = axis_vector_to_index(up)
|
|
|
|
local back_index = axis_vector_to_index(back)
|
|
|
|
return FACEDIR_LOOKUP[up_index][back_index]
|
|
|
|
end
|
|
|
|
rotate.facedir_from_vectors = facedir_from_vectors
|
|
|
|
|
|
|
|
-- Returns the specified `facedir` rotated around `vec` by `degrees` counter-clickwise.
|
|
|
|
local function rotate_facedir(facedir, vec, degrees)
|
|
|
|
-- NOTE: Removed the axis requirement so we can use this to rotate around corners.
|
|
|
|
-- However, since we `round` the result vectors this might ignore errors.
|
|
|
|
-- if degrees % 90 ~= 0 then error("degrees must be divisible by 90") end
|
|
|
|
-- if axis_vector_to_index(vec) == nil then error("axis is not an axis vector") end
|
|
|
|
local up, back = vectors_from_facedir(facedir)
|
|
|
|
up = up :rotate_around_axis(vec, math_rad(degrees)):round()
|
|
|
|
back = back:rotate_around_axis(vec, math_rad(degrees)):round()
|
|
|
|
return facedir_from_vectors(up, back)
|
|
|
|
end
|
|
|
|
rotate.rotate_facedir = rotate_facedir
|
|
|
|
|
|
|
|
-- Rotates the specified default orientation `{ min, max }` box to
|
|
|
|
-- one matching Minetest's rotation algorithm pointing to `facedir`.
|
|
|
|
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
|
|
|
|
|
|
|
|
return rotate
|