- Move functionality to init.lua: - Player step event handling - Rotation state is stored in rotating_state - Calls hud.update_player_hud - Punch node event uses rotating_state - Add utility functions: - Rotate box by a specified facedir value - Find node's current (rotated) selection box - Find pointed-at node's closest edgemain
parent
ea4b9c49c1
commit
5115aedcb7
6 changed files with 194 additions and 69 deletions
@ -1,44 +1,37 @@ |
|||||||
local ipairs |
local nodecore |
||||||
= ipairs |
= nodecore |
||||||
|
|
||||||
local minetest, nodecore |
|
||||||
= minetest, nodecore |
|
||||||
|
|
||||||
local rotate = include("rotate") |
|
||||||
|
|
||||||
local LABEL_ROTATION_HINT = "rotation hint" |
local LABEL_ROTATION_HINT = "rotation hint" |
||||||
|
|
||||||
local TEX_ROTATE_CLOCKWISE = "nc_extended_rotating_hud_rotate_clockwise.png" |
local TEX_ROTATE_FACE = "nc_extended_rotating_hud_rotate_face.png" |
||||||
local TEX_ROTATE_COUNTER_CLOCKWISE = "nc_extended_rotating_hud_rotate_counter_clockwise.png" |
local TEX_ROTATE_EDGE = "nc_extended_rotating_hud_rotate_edge.png" |
||||||
|
|
||||||
local function do_player_rotating_checks(player, data) |
|
||||||
local pt = data.raycast() |
|
||||||
local node = pt and pt.type == "node" and minetest.get_node(pt.under) |
|
||||||
|
|
||||||
if node and rotate.is_rotatable(node) then |
local hud = {} |
||||||
local is_sneaking = player:get_player_control().sneak |
|
||||||
local texture = is_sneaking and TEX_ROTATE_COUNTER_CLOCKWISE or TEX_ROTATE_CLOCKWISE |
|
||||||
|
|
||||||
nodecore.hud_set(player, { |
local function crosshair_hud_element(texture) |
||||||
|
return { |
||||||
label = LABEL_ROTATION_HINT, |
label = LABEL_ROTATION_HINT, |
||||||
hud_elem_type = "image", |
hud_elem_type = "image", |
||||||
text = texture .. "^[opacity:" .. 192, |
text = texture, |
||||||
position = { x = 0.5, y = 0.5 }, |
position = { x = 0.5, y = 0.5 }, |
||||||
offset = { x = 0, y = 0 }, |
offset = { x = 0, y = 0 }, |
||||||
alignment = { x = 0, y = 0 }, |
alignment = { x = 0, y = 0 }, |
||||||
scale = { x = 1, y = 1 }, |
scale = { x = 1, y = 1 }, |
||||||
quick = true |
quick = true |
||||||
}) |
} |
||||||
|
end |
||||||
|
|
||||||
|
local function update_player_hud(player, state) |
||||||
|
local mode = state and state.mode |
||||||
|
if mode == "face" then |
||||||
|
local texture = TEX_ROTATE_FACE |
||||||
|
if state.invert then texture = texture .. "^[transformFX" end |
||||||
|
texture = texture .. "^[opacity:" .. 192 |
||||||
|
nodecore.hud_set(player, crosshair_hud_element(texture)) |
||||||
else |
else |
||||||
nodecore.hud_set(player, { |
nodecore.hud_set(player, { label = LABEL_ROTATION_HINT, ttl = 0 }) |
||||||
label = LABEL_ROTATION_HINT, |
|
||||||
ttl = 0 |
|
||||||
}) |
|
||||||
end |
end |
||||||
end |
end |
||||||
|
hud.update_player_hud = update_player_hud |
||||||
|
|
||||||
nodecore.register_playerstep({ |
return hud |
||||||
label = "crosshair", |
|
||||||
priority = -101, |
|
||||||
action = do_player_rotating_checks, |
|
||||||
}) |
|
||||||
|
@ -1,21 +1,66 @@ |
|||||||
local minetest, vector, include |
local minetest, vector, nodecore, include |
||||||
= minetest, vector, include |
= minetest, vector, nodecore, include |
||||||
|
|
||||||
local rotate = include("rotate") |
|
||||||
local hud = include("hud") |
local hud = include("hud") |
||||||
|
local rotate = include("rotate") |
||||||
|
local utility = include("utility") |
||||||
|
|
||||||
minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing) |
-- TODO: Add crosshair indicators for rotating around edges. |
||||||
local def = minetest.registered_nodes[node.name] |
-- TODO: Register only the nodes that should be rotatable. |
||||||
if def.paramtype2 ~= "facedir" then return end |
-- TODO: Replace `on_rightclick` of intended nodes. |
||||||
|
-- TODO: Add some more comments. |
||||||
|
|
||||||
|
-- Distance at which we want to rotate by "pushing" on an edge. |
||||||
|
local EDGE_DISTANCE = 1.5 / 16 -- 1.5 texels (at 16² texture resolution) |
||||||
|
|
||||||
|
local rotating_state = {} |
||||||
|
|
||||||
|
nodecore.register_playerstep({ |
||||||
|
label = "extended rotating update", |
||||||
|
action = function(player, player_data) |
||||||
|
local name = player:get_player_name() |
||||||
|
local state = {}; rotating_state[name] = state |
||||||
|
|
||||||
|
local pointed_thing = player_data.raycast() |
||||||
|
if (not pointed_thing) or pointed_thing.type ~= "node" then return nil end |
||||||
|
|
||||||
|
state.pos = pointed_thing.under |
||||||
|
state.node = minetest.get_node(state.pos) |
||||||
|
if not rotate.is_rotatable(state.node) then return end |
||||||
|
|
||||||
-- Vector that points away from the punched face. |
if vector.equals(pointed_thing.above, pointed_thing.under) then return end |
||||||
if (not pointed_thing.above) or (not pointed_thing.under) then return end |
state.face = pointed_thing.above - pointed_thing.under |
||||||
local axis = vector.subtract(pointed_thing.above, pointed_thing.under) |
|
||||||
|
local edge, distance = utility.find_closest_edge(state.node, pointed_thing) |
||||||
|
if edge and distance > EDGE_DISTANCE then |
||||||
|
state.axis = state.face |
||||||
|
state.mode = "face" |
||||||
|
else |
||||||
|
state.axis = state.face:cross(edge):normalize():round() |
||||||
|
state.mode = "edge" |
||||||
|
end |
||||||
|
|
||||||
|
state.invert = player:get_player_control().sneak |
||||||
|
hud.update_player_hud(player, state) |
||||||
|
end, |
||||||
|
}) |
||||||
|
|
||||||
|
minetest.register_on_leaveplayer(function(player) |
||||||
|
local name = player:get_player_name() |
||||||
|
rotating_state[name] = nil |
||||||
|
end) |
||||||
|
|
||||||
|
|
||||||
|
minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing) |
||||||
|
if not minetest.is_player(puncher) then return end |
||||||
|
local name = puncher:get_player_name() |
||||||
|
local state = rotating_state[name] |
||||||
|
if not state then return end |
||||||
|
|
||||||
-- Rotate clockwise by default. |
-- Make sure we're still the same node that we raycasted in `playerstep`. |
||||||
local degrees = -90 |
if not vector.equals(pos, state.pos) then return end |
||||||
-- If player is sneaking, reverse the rotation. |
if node.name ~= state.node.name or node.param2 ~= state.node.param2 then return end |
||||||
if minetest.is_player(puncher) and puncher:get_player_control().sneak then degrees = -degrees end |
|
||||||
|
|
||||||
rotate.rotate_node(pos, node, axis, degrees) |
local degrees = state.invert and 90 or -90 |
||||||
|
rotate.rotate_node(state.pos, state.node, state.axis, degrees) |
||||||
end) |
end) |
||||||
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
@ -1,16 +1,67 @@ |
|||||||
local ipairs, tostring |
|
||||||
= ipairs, tostring |
|
||||||
|
|
||||||
local minetest |
local math_abs, math_sign |
||||||
= minetest |
= math.abs, math.sign |
||||||
|
local minetest, vector, include |
||||||
|
= minetest, vector, include |
||||||
|
|
||||||
|
local rotate = include("rotate") |
||||||
|
|
||||||
local utility = {} |
local utility = {} |
||||||
|
|
||||||
local function debug_tell(...) |
-- Default selection boxes for a node that doesn't have one them explicitly. |
||||||
local text = "" |
local DEFAULT_SELECTION_BOX = { min = vector.new(-0.5, -0.5, -0.5), |
||||||
for _, v in ipairs({...}) do text = text .. tostring(v) end |
max = vector.new( 0.5, 0.5, 0.5) } |
||||||
minetest.chat_send_all(text) |
|
||||||
|
-- Gets the active `selection_box` for the specified `node` which |
||||||
|
-- has a `paramtype2` of `facedir`, based on its `param2` value. |
||||||
|
local function get_node_active_selection_box(node) |
||||||
|
local def = minetest.registered_nodes[node.name] |
||||||
|
local box = def.selection_box and def.selection_box.fixed |
||||||
|
-- No need to rotate the default selection box. |
||||||
|
if not box then return DEFAULT_SELECTION_BOX end |
||||||
|
-- If node definition specifies multiple selection boxes, just pick the first (for now). |
||||||
|
if type(box[1]) == "table" then box = box[1] end |
||||||
|
-- Transform the `{ x1, y1, z1, x2, y2, z2 }` box to a `{ min, max }` one. |
||||||
|
box = { min = vector.new(box[1], box[2], box[3]), |
||||||
|
max = vector.new(box[4], box[5], box[6]) } |
||||||
|
-- Rotate the box to face `facedir`. |
||||||
|
rotate.rotate_box_by_facedir(box, node.param2) |
||||||
|
return box |
||||||
|
end |
||||||
|
|
||||||
|
-- Finds the closest edge of the node the player is looking at as |
||||||
|
-- a vector pointing away from the center, and the distance to it. |
||||||
|
local function find_closest_edge(node, pointed_thing) |
||||||
|
-- For this math to work, we assume that selection box is centered. |
||||||
|
local max = get_node_active_selection_box(node).max |
||||||
|
-- Point relative to the collision box we're pointing at. |
||||||
|
local point = pointed_thing.intersection_point - pointed_thing.under |
||||||
|
|
||||||
|
-- Find the edge we're closest to. |
||||||
|
local vec = vector.zero() |
||||||
|
if math_abs(point.y / point.x) > max.x / max.y then |
||||||
|
vec.y = math_sign(point.y) |
||||||
|
if math_abs(point.z / point.x) > max.x / max.z |
||||||
|
then vec.z = math_sign(point.z) |
||||||
|
else vec.x = math_sign(point.x) |
||||||
|
end |
||||||
|
else |
||||||
|
vec.x = math_sign(point.x) |
||||||
|
if math_abs(point.z / point.y) > max.y / max.z |
||||||
|
then vec.z = math_sign(point.z) |
||||||
|
else vec.y = math_sign(point.y) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
local d = 1 |
||||||
|
local v = max - point:multiply(vec):apply(math_abs) |
||||||
|
local EPSILON = tonumber("1.19e-07") |
||||||
|
if math_abs(v.x) > EPSILON and v.x < d then d = v.x end |
||||||
|
if math_abs(v.y) > EPSILON and v.y < d then d = v.y end |
||||||
|
if math_abs(v.z) > EPSILON and v.z < d then d = v.z end |
||||||
|
|
||||||
|
return vec, d |
||||||
end |
end |
||||||
utility.debug_tell = debug_tell |
utility.find_closest_edge = find_closest_edge |
||||||
|
|
||||||
return utility |
return utility |
||||||
|
Loading…
Reference in new issue