Skip to content
Snippets Groups Projects
init.lua 6.12 KiB
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"},
	}
})