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.
128 lines
4.9 KiB
128 lines
4.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 = {} |
|
|
|
local rotatable_registry = {} |
|
|
|
-- 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 |
|
|
|
local function register_rotatable(name, facedir_lookup) |
|
local def = minetest.registered_nodes[name] |
|
if not def then error("Unknown node '" .. name .. "'") end |
|
if def.paramtype2 ~= "facedir" then error("Node '" .. name .. "' must be 'facedir'") end |
|
rotatable_registry[name] = { lookup = facedir_lookup } |
|
end |
|
rotate.register_rotatable = register_rotatable |
|
|
|
-- Returns whether the specified `node` is rotatable by this mod. |
|
local function is_rotatable(node) |
|
local name = node and node.name or "" |
|
return not not rotatable_registry[name] |
|
end |
|
rotate.is_rotatable = is_rotatable |
|
|
|
-- Fixes facedir for the specified `node` when rotated to `facedir`. |
|
-- * Returns `nil` if the node can't rotate this way. |
|
-- * Returns `facedir` when the node can rotate this way. |
|
-- * Returns another facedir when the node should rotate another equivalent way. |
|
local function fix_rotatable_facedir(node, facedir) |
|
local name = node and node.name or "" |
|
local entry = rotatable_registry[name] |
|
if not entry then return nil end |
|
if not entry.lookup then return facedir end |
|
return entry.lookup[facedir] |
|
end |
|
rotate.fix_rotatable_facedir = fix_rotatable_facedir |
|
|
|
-- Returns the specified `facedir` rotated around `axis` by `degrees` counter-clickwise. |
|
local function rotate_facedir(facedir, 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 " .. tostring(axis) .. " is not an axis vector") end |
|
|
|
local up = AXIS_LOOKUP[1 + math_floor(facedir / 4)] |
|
local back = minetest.facedir_to_dir(facedir) |
|
|
|
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) |
|
|
|
return FACEDIR_LOOKUP[up_index][back_index] |
|
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
|
|
|