- 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 | ||||
|     = ipairs | ||||
| 
 | ||||
| local minetest, nodecore | ||||
|     = minetest, nodecore | ||||
| 
 | ||||
| local rotate = include("rotate") | ||||
| local nodecore | ||||
|     = nodecore | ||||
| 
 | ||||
| local LABEL_ROTATION_HINT = "rotation hint" | ||||
| 
 | ||||
| local TEX_ROTATE_CLOCKWISE         = "nc_extended_rotating_hud_rotate_clockwise.png" | ||||
| local TEX_ROTATE_COUNTER_CLOCKWISE = "nc_extended_rotating_hud_rotate_counter_clockwise.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) | ||||
| local TEX_ROTATE_FACE = "nc_extended_rotating_hud_rotate_face.png" | ||||
| local TEX_ROTATE_EDGE = "nc_extended_rotating_hud_rotate_edge.png" | ||||
| 
 | ||||
|     if node and rotate.is_rotatable(node) then | ||||
|         local is_sneaking = player:get_player_control().sneak | ||||
|         local texture = is_sneaking and TEX_ROTATE_COUNTER_CLOCKWISE or TEX_ROTATE_CLOCKWISE | ||||
| local hud = {} | ||||
| 
 | ||||
|         nodecore.hud_set(player, { | ||||
| local function crosshair_hud_element(texture) | ||||
|     return { | ||||
|         label = LABEL_ROTATION_HINT, | ||||
|         hud_elem_type = "image", | ||||
|             text = texture .. "^[opacity:" .. 192, | ||||
|         text = texture, | ||||
|         position  = { x = 0.5, y = 0.5 }, | ||||
|         offset    = { x = 0, y = 0 }, | ||||
|         alignment = { x = 0, y = 0 }, | ||||
|         scale     = { x = 1, y = 1 }, | ||||
|         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 | ||||
|         nodecore.hud_set(player, { | ||||
|             label = LABEL_ROTATION_HINT, | ||||
|             ttl   = 0 | ||||
|         }) | ||||
|         nodecore.hud_set(player, { label = LABEL_ROTATION_HINT, ttl = 0 }) | ||||
|     end | ||||
| end | ||||
| hud.update_player_hud = update_player_hud | ||||
| 
 | ||||
| nodecore.register_playerstep({ | ||||
|     label = "crosshair", | ||||
|     priority = -101, | ||||
|     action = do_player_rotating_checks, | ||||
| }) | ||||
| return hud | ||||
|  | ||||
| @ -1,21 +1,66 @@ | ||||
| local minetest, vector, include | ||||
|     = minetest, vector, include | ||||
| local minetest, vector, nodecore, include | ||||
|     = minetest, vector, nodecore, include | ||||
| 
 | ||||
| local rotate = include("rotate") | ||||
| local hud     = include("hud") | ||||
| local rotate  = include("rotate") | ||||
| local utility = include("utility") | ||||
| 
 | ||||
| minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing) | ||||
|     local def = minetest.registered_nodes[node.name] | ||||
|     if def.paramtype2 ~= "facedir" then return end | ||||
| -- TODO: Add crosshair indicators for rotating around edges. | ||||
| -- TODO: Register only the nodes that should be rotatable. | ||||
| -- 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 (not pointed_thing.above) or (not pointed_thing.under) then return end | ||||
|     local axis = vector.subtract(pointed_thing.above, pointed_thing.under) | ||||
|         if vector.equals(pointed_thing.above, pointed_thing.under) then return end | ||||
|         state.face = 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. | ||||
|     local degrees = -90 | ||||
|     -- If player is sneaking, reverse the rotation. | ||||
|     if minetest.is_player(puncher) and puncher:get_player_control().sneak then degrees = -degrees end | ||||
|     -- Make sure we're still the same node that we raycasted in `playerstep`. | ||||
|     if not vector.equals(pos, state.pos) then return end | ||||
|     if node.name ~= state.node.name or node.param2 ~= state.node.param2 then return 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) | ||||
|  | ||||
| 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 | ||||
|     = minetest | ||||
| local math_abs, math_sign | ||||
|     = math.abs, math.sign | ||||
| local minetest, vector, include | ||||
|     = minetest, vector, include | ||||
| 
 | ||||
| local rotate = include("rotate") | ||||
| 
 | ||||
| local utility = {} | ||||
| 
 | ||||
| local function debug_tell(...) | ||||
|     local text = "" | ||||
|     for _, v in ipairs({...}) do text = text .. tostring(v) end | ||||
|     minetest.chat_send_all(text) | ||||
| -- Default selection boxes for a node that doesn't have one them explicitly. | ||||
| local DEFAULT_SELECTION_BOX = { min = vector.new(-0.5, -0.5, -0.5), | ||||
|                                 max = vector.new( 0.5,  0.5,  0.5) } | ||||
| 
 | ||||
| -- 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 | ||||
| utility.debug_tell = debug_tell | ||||
| utility.find_closest_edge = find_closest_edge | ||||
| 
 | ||||
| return utility | ||||
|  | ||||
					Loading…
					
					
				
		Reference in new issue