rhotator/init.lua

269 lines
6.1 KiB
Lua

rhotator = {}
rhotator.mod_path = minetest.get_modpath(minetest.get_current_modname())
local matrix = dofile(rhotator.mod_path .. "/lib/matrix.lua")
local enable_chat_notifications = true
-- constants
local POS = {}
local NEG = {}
POS.Y = 0
POS.Z = 1
NEG.Z = 2
POS.X = 3
NEG.X = 4
NEG.Y = 5
PRIMARY_BTN = 1
SECONDARY_BTN = 2
-- helper tables
local rot_matrices = {}
local dir_matrices = {}
-- init
local function init_transforms()
local rot = {}
local dir = {}
-- no rotation
rot[0] = matrix{{ 1, 0, 0},
{ 0, 1, 0},
{ 0, 0, 1}}
-- 90 degrees clockwise
rot[1] = matrix{{ 0, 0, 1},
{ 0, 1, 0},
{ -1, 0, 0}}
-- 180 degrees
rot[2] = matrix{{ -1, 0, 0},
{ 0, 1, 0},
{ 0, 0, -1}}
-- 270 degrees clockwise
rot[3] = matrix{{ 0, 0, -1},
{ 0, 1, 0},
{ 1, 0, 0}}
rot_matrices = rot
-- directions
-- Y+
dir[0] = matrix{{ 1, 0, 0},
{ 0, 1, 0},
{ 0, 0, 1}}
-- Z+
dir[1] = matrix{{ 1, 0, 0},
{ 0, 0, -1},
{ 0, 1, 0}}
-- Z-
dir[2] = matrix{{ 1, 0, 0},
{ 0, 0, 1},
{ 0, -1, 0}}
-- X+
dir[3] = matrix{{ 0, 1, 0},
{ -1, 0, 0},
{ 0, 0, 1}}
-- X-
dir[4] = matrix{{ 0, -1, 0},
{ 1, 0, 0},
{ 0, 0, 1}}
-- Y-
dir[5] = matrix{{ -1, 0, 0},
{ 0, -1, 0},
{ 0, 0, 1}}
dir_matrices = dir
rhotator._facedir_transform = {}
rhotator._matrix_to_facedir = {}
for facedir = 0, 23 do
local direction = math.floor(facedir / 4)
local rotation = facedir % 4
local transform = dir[direction] * rot[rotation]
rhotator._facedir_transform[facedir] = transform
rhotator._matrix_to_facedir[transform:tostring():gsub("%-0", "0")] = facedir
end
end
init_transforms()
-- helper functions
local function cross_product(a, b)
return vector.new(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
)
end
local function extract_main_axis(dir)
local axes = { "x", "y", "z" }
local axis = 1
local max = 0
for i = 1, 3 do
local abs = math.abs(dir[axes[i]])
if abs > max then
axis = i
max = abs
end
end
return axes[axis]
end
local function sign(num)
return (num < 0) and -1 or 1
end
local function extract_unit_vectors(player, pointed_thing)
assert(pointed_thing.type == "node")
local abs_face_pos = minetest.pointed_thing_to_face_pos(player, pointed_thing)
local pos = pointed_thing.under
local f = vector.subtract(abs_face_pos, pos)
local facedir = 0
local primary = 0
local m1, m2
local unit_direction = vector.new()
local unit_rotation = vector.new()
local rotation = vector.new()
if math.abs(f.y) == 0.5 then
unit_direction.y = sign(f.y)
rotation.x = f.x
rotation.z = f.z
elseif math.abs(f.z) == 0.5 then
unit_direction.z = sign(f.z)
rotation.x = f.x
rotation.y = f.y
else
unit_direction.x = sign(f.x)
rotation.y = f.y
rotation.z = f.z
end
local main_axis = extract_main_axis(rotation)
unit_rotation[main_axis] = sign(rotation[main_axis])
return {
back = unit_direction,
wrap = unit_rotation,
thumb = cross_product(unit_direction, unit_rotation),
}
end
local function get_facedir_transform(facedir)
return rhotator._facedir_transform[facedir] or rhotator._facedir_transform[0]
end
local function matrix_to_facedir(mtx)
local key = mtx:tostring():gsub("%-0", "0")
if not rhotator._matrix_to_facedir[key] then
error("Unsupported matrix:\n" .. key)
end
return rhotator._matrix_to_facedir[key]
end
local function notify(playername, message)
if enable_chat_notifications then
minetest.chat_send_player(playername, "[rhotator] " .. message)
end
end
local function vector_to_dir_index(vec)
local main_axis = extract_main_axis(vec)
if main_axis == "x" then return (vec.x > 0) and POS.X or NEG.X end
if main_axis == "z" then return (vec.z > 0) and POS.Z or NEG.Z end
return (vec.y > 0) and POS.Y or NEG.Y
end
-- rhotator main
local function interact(itemstack, player, pointed_thing, click)
if pointed_thing.type ~= "node" then
return
end
local node = minetest.get_node_or_nil(pointed_thing.under)
local def = minetest.registered_nodes[node.name]
if not node or not def then
notify(player:get_player_name(), "Unsupported node type: " .. node.name)
return
end
if def.paramtype2 ~= "facedir" then
notify(player:get_player_name(), "Cannot rotate node with paramtype2 == " .. def.paramtype2)
return
end
local unit = extract_unit_vectors(player, pointed_thing)
local transform = false
if click == PRIMARY_BTN then
transform = dir_matrices[vector_to_dir_index(unit.thumb)]
notify(player:get_player_name(), "Pushed closest edge (left click)")
else
transform = dir_matrices[vector_to_dir_index(unit.back)]
notify(player:get_player_name(), "Rotated pointed face (right click)")
end
local start = get_facedir_transform(node.param2)
local stop = transform * rot_matrices[1] * transform:invert() * start
minetest.set_node(pointed_thing.under,{
name = node.name,
param1 = node.param1,
param2 = matrix_to_facedir(stop),
})
end
minetest.register_tool("rhotator:screwdriver", {
description = "Rhotator Screwdriver (left-click pushes edge, right-click rotates face)",
inventory_image = "rhotator.png",
on_use = function(itemstack, player, pointed_thing)
interact(itemstack, player, pointed_thing, PRIMARY_BTN)
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
interact(itemstack, player, pointed_thing, SECONDARY_BTN)
return itemstack
end,
})
minetest.register_craft({
output = "rhotator:screwdriver",
recipe = {
{"default:copper_ingot"},
{"group:stick"}
}
})
minetest.register_node("rhotator:cube", {
drawtype = "mesh",
mesh = "rhotocube.obj",
tiles = { "rhotocube.png" },
paramtype2 = "facedir",
description = "Rhotator Testing Cube",
walkable = true,
groups = { snappy = 2, choppy = 2, oddly_breakable_by_hand = 3 },
})
minetest.register_craft({
output = "rhotator:cube",
recipe = {
{"group:wool"},
{"rhotator:screwdriver"},
}
})