diff --git a/doc/lua_api.md b/doc/lua_api.md index f46c9eb28ae68..eccdc0448c2a5 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8401,6 +8401,22 @@ child will follow movement and rotation of that bone. table {x, y, z} representing the player's instantaneous velocity in nodes/s * `add_player_velocity(vel)`: **DEPRECATED**, use add_velocity(vel) instead. * `get_look_dir()`: get camera direction as a unit vector +* `get_point_dir()`: get pointing direction as a unit vector + * If the player uses a crosshair, this returns the same value as `get_look_dir`. + * If the player uses the touchscreen controls without a crosshair, ... + * ...the look direction and the pointing direction may differ. + * ...the player doesn't have a well-defined pointing direction when not + touching the screen. In this case, `get_point_dir` returns the last + valid pointing direction. +* `get_point_screen_pos()`: get pointer position in screen-space + * Returns a 2D vector in coordinates relative to the screen size: + `{x = 0..1, y = 0..1}` + * If the player uses a crosshair, this always returns `{x = 0.5, y = 0.5}` + * If the player uses the touchscreen controls without a crosshair, ... + * ...other values may be returned. + * ...the player doesn't have a well-defined pointer position when not + touching the screen. In this case, `get_point_screen_pos` returns the + last valid pointer position. * `get_look_vertical()`: pitch in radians * Angle ranges between -pi/2 and pi/2, which are straight up and down respectively. diff --git a/src/client/client.cpp b/src/client/client.cpp index 222c1a2acbe99..dcad3339b61d3 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1013,7 +1013,7 @@ void Client::Send(NetworkPacket* pkt) m_con->Send(PEER_ID_SERVER, scf.channel, pkt, scf.reliable); } -// Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4 bytes +// Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4 + 8 + 4 + 4 bytes void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt, bool camera_inverted) { v3f pf = myplayer->getPosition() * 100; @@ -1027,6 +1027,9 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket * std::ceil(clientMap->getWantedRange() * (1.0f / MAP_BLOCKSIZE))); f32 movement_speed = myplayer->control.movement_speed; f32 movement_dir = myplayer->control.movement_direction; + v2f pointer_pos = myplayer->pointer_pos; + f32 point_pitch = myplayer->point_pitch; + f32 point_yaw = myplayer->point_yaw; v3s32 position(pf.X, pf.Y, pf.Z); v3s32 speed(sf.X, sf.Y, sf.Z); @@ -1043,11 +1046,16 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket * [12+12+4+4+4+1+1] u8 camera_inverted (bool) [12+12+4+4+4+1+1+1] f32 movement_speed [12+12+4+4+4+1+1+1+4] f32 movement_direction + [12+12+4+4+4+1+1+1+4+4] v2f pointer_pos + [12+12+4+4+4+1+1+1+4+4+8] f32 point_pitch + [12+12+4+4+4+1+1+1+4+4+8+4] f32 point_yaw */ *pkt << position << speed << pitch << yaw << keyPressed; *pkt << fov << wanted_range; *pkt << camera_inverted; *pkt << movement_speed << movement_dir; + *pkt << pointer_pos; + *pkt << point_pitch << point_yaw; } void Client::interact(InteractAction action, const PointedThing& pointed) @@ -1394,7 +1402,10 @@ void Client::sendPlayerPos() player->last_camera_inverted == camera_inverted && player->last_wanted_range == wanted_range && player->last_movement_speed == movement_speed && - player->last_movement_dir == movement_dir) + player->last_movement_dir == movement_dir && + player->last_pointer_pos == player->pointer_pos && + player->last_point_pitch == player->point_pitch && + player->last_point_yaw == player->point_yaw) return; player->last_position = player->getPosition(); @@ -1407,8 +1418,11 @@ void Client::sendPlayerPos() player->last_wanted_range = wanted_range; player->last_movement_speed = movement_speed; player->last_movement_dir = movement_dir; + player->last_pointer_pos = player->pointer_pos; + player->last_point_pitch = player->point_pitch; + player->last_point_yaw = player->point_yaw; - NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4); + NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4 + 8 + 4 + 4); writePlayerPos(player, &map, &pkt, camera_inverted); diff --git a/src/client/game.cpp b/src/client/game.cpp index 97b50183ce521..76fdb57cdc7a3 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -558,6 +558,7 @@ class Game { void updateCameraDirection(CameraOrientation *cam, float dtime); void updateCameraOrientation(CameraOrientation *cam, float dtime); + void updatePointDir(const CameraOrientation &cam); void updatePlayerControl(const CameraOrientation &cam); void updatePauseState(); void step(f32 dtime); @@ -996,6 +997,7 @@ void Game::run() cam_view.camera_yaw) * m_cache_cam_smoothing; cam_view.camera_pitch += (cam_view_target.camera_pitch - cam_view.camera_pitch) * m_cache_cam_smoothing; + updatePointDir(cam_view); updatePlayerControl(cam_view); updatePauseState(); @@ -2427,6 +2429,32 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5); } +void Game::updatePointDir(const CameraOrientation &cam) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + if (g_touchcontrols && isTouchCrosshairDisabled()) { + v2s32 pointer_pos = g_touchcontrols->getPointerPos(); + v2u32 screensize = driver->getScreenSize(); + + player->pointer_pos.X = (f32)pointer_pos.X / (f32)screensize.X; + player->pointer_pos.Y = (f32)pointer_pos.Y / (f32)screensize.Y; + + const v3f point_dir = g_touchcontrols->getShootline().getVector().normalize(); + // getHorizontalAngle is Irrlicht's "direction to rotation" function + // Roll (Z) is always 0 + const v3f point_rot = point_dir.getHorizontalAngle(); + + player->point_pitch = point_rot.X; + player->point_yaw = point_rot.Y; + } else { + player->pointer_pos.X = 0.5f; + player->pointer_pos.Y = 0.5f; + player->point_pitch = 0.0f; + player->point_yaw = 0.0f; + } +} + void Game::updatePlayerControl(const CameraOrientation &cam) { diff --git a/src/client/localplayer.h b/src/client/localplayer.h index 93b768ceb0347..a2b80e8eb1891 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -89,6 +89,9 @@ class LocalPlayer : public Player bool last_camera_inverted = false; f32 last_movement_speed = 0.0f; f32 last_movement_dir = 0.0f; + v2f last_pointer_pos = v2f(0.5f, 0.5f); + float last_point_pitch = 0.0f; + float last_point_yaw = 0.0f; float camera_impact = 0.0f; diff --git a/src/gui/touchcontrols.cpp b/src/gui/touchcontrols.cpp index 17352264dcb03..5ac42d4bb0df4 100644 --- a/src/gui/touchcontrols.cpp +++ b/src/gui/touchcontrols.cpp @@ -531,10 +531,11 @@ void TouchControls::translateEvent(const SEvent &event) m_move_has_really_moved = false; m_move_downtime = porting::getTimeMs(); m_move_pos = touch_pos; - // DON'T reset m_tap_state here, otherwise many short taps - // will be ignored if you tap very fast. m_had_move_id = true; m_move_prevent_short_tap = prevent_short_tap; + + // DON'T reset m_tap_state here, otherwise many short taps + // will be ignored if you tap very fast. } } } @@ -662,11 +663,10 @@ void TouchControls::step(float dtime) // Only updating when m_has_move_id means that the shootline will stay at // it's last in-world position when the player doesn't need it. if (!m_draw_crosshair && (m_has_move_id || m_had_move_id)) { - v2s32 pointer_pos = getPointerPos(); m_shootline = m_device ->getSceneManager() ->getSceneCollisionManager() - ->getRayFromScreenCoordinates(pointer_pos); + ->getRayFromScreenCoordinates(m_move_pos); } m_had_move_id = false; } @@ -748,18 +748,13 @@ void TouchControls::show() setVisible(true); } -v2s32 TouchControls::getPointerPos() -{ - if (m_draw_crosshair) - return v2s32(m_screensize.X / 2, m_screensize.Y / 2); - // We can't just use m_pointer_pos[m_move_id] because applyContextControls - // may emit release events after m_pointer_pos[m_move_id] is erased. - return m_move_pos; -} - void TouchControls::emitMouseEvent(EMOUSE_INPUT_EVENT type) { - v2s32 pointer_pos = getPointerPos(); + v2s32 pointer_pos = m_draw_crosshair + ? v2s32(m_screensize.X / 2, m_screensize.Y / 2) + // applyContextControls may emit mouse release events after + // m_pointer_pos[m_move_id] is erased. + : m_move_pos; SEvent event{}; event.EventType = EET_MOUSE_INPUT_EVENT; diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index 701baba427dbe..4cb26e177b831 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -96,6 +96,13 @@ class TouchControls return res; } + /** + * Returns the pointer position in pixels. + * + * May only be used if crosshair is disabled (see setUseCrosshair) + */ + v2s32 getPointerPos() { return m_move_pos; } + /** * Returns a line which describes what the player is pointing at. * The starting point and looking direction are significant, @@ -103,6 +110,8 @@ class TouchControls * the player can reach. * The line starts at the camera and ends on the camera's far plane. * The coordinates do not contain the camera offset. + * + * May only be used if crosshair is disabled (see setUseCrosshair) */ line3d getShootline() { return m_shootline; } @@ -155,7 +164,9 @@ class TouchControls size_t m_move_id; bool m_move_has_really_moved = false; u64 m_move_downtime = 0; - // m_move_pos stays valid even after m_move_id has been released. + // m_move_pos is equivalent to m_pointer_pos[m_move_id] while m_has_move_id + // is true. However, m_move_pos stays valid even after the m_move_id pointer + // has been released and m_pointer_pos[m_move_id] has been erased. v2s32 m_move_pos; // This is needed so that we don't miss if m_has_move_id is true for less // than one client step, i.e. press and release happen in the same step. @@ -217,7 +228,6 @@ class TouchControls // map to store the IDs and positions of currently pressed pointers std::unordered_map m_pointer_pos; - v2s32 getPointerPos(); void emitMouseEvent(EMOUSE_INPUT_EVENT type); TouchInteractionMode m_last_mode = TouchInteractionMode_END; TapState m_tap_state = TapState::None; diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 0d63bd24f4933..1c018a1acb990 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -727,6 +727,9 @@ enum ToServerCommand : u16 [2+12+12+4+4+4+1+1] u8 camera_inverted (bool) [2+12+12+4+4+4+1+1+1] f32 movement_speed [2+12+12+4+4+4+1+1+1+4] f32 movement_direction + [2+12+12+4+4+4+1+1+1+4+4] v2f pointer_pos + [2+12+12+4+4+4+1+1+1+4+4+8] f32 point_pitch + [2+12+12+4+4+4+1+1+1+4+4+8+4] f32 point_yaw */ diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index cf1dcacb170c4..8da06323ed489 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -475,6 +475,16 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, player->control.setMovementFromKeys(); } + if (pkt->getRemainingBytes() >= 16) { + *pkt >> player->pointer_pos; + *pkt >> player->point_pitch >> player->point_yaw; + } else { + player->pointer_pos.X = 0.5f; + player->pointer_pos.Y = 0.5f; + player->point_pitch = 0.0f; + player->point_yaw = 0.0f; + } + v3f position((f32)ps.X / 100.0f, (f32)ps.Y / 100.0f, (f32)ps.Z / 100.0f); v3f speed((f32)ss.X / 100.0f, (f32)ss.Y / 100.0f, (f32)ss.Z / 100.0f); diff --git a/src/player.h b/src/player.h index 25c80039c638f..d876fb9f194b7 100644 --- a/src/player.h +++ b/src/player.h @@ -188,6 +188,18 @@ class Player PlayerControl control; const PlayerControl& getPlayerControl() { return control; } + // Pointer position, as screen coordinates in the range 0..1 + // When using a crosshair, this is always (0.5, 0.5) + v2f pointer_pos = v2f(0.5f, 0.5f); + // Pointing direction, represented as pitch/yaw in degrees + // When using a crosshair, it's unspecified what values these will have. + f32 point_pitch = 0.0f; + f32 point_yaw = 0.0f; + + // Note: If there is no crosshair and the player is not touching the screen, + // pointer position and pointing direction are not well-defined. In this + // case, the last valid values are kept. + PlayerPhysicsOverride physics_override; // Returns non-empty `selected` ItemStack. `hand` is a fallback, if specified diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 2fce2490647d0..e22933e1e5781 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1246,12 +1246,42 @@ int ObjectRef::l_get_look_dir(lua_State *L) if (playersao == nullptr) return 0; - float pitch = playersao->getRadLookPitchDep(); - float yaw = playersao->getRadYawDep(); - v3f v(std::cos(pitch) * std::cos(yaw), std::sin(pitch), std::cos(pitch) * - std::sin(yaw)); + push_v3f(L, playersao->getLookDir()); + return 1; +} + +// get_point_screen_pos(self) +int ObjectRef::l_get_point_screen_pos(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkObject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + push_v2f(L, player->pointer_pos); + return 1; +} + +// get_point_dir(self) +int ObjectRef::l_get_point_dir(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkObject(L, 1); + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + + RemotePlayer *player = playersao->getPlayer(); + + if (player->pointer_pos.X == 0.5f && player->pointer_pos.Y == 0.5f) { + push_v3f(L, playersao->getLookDir()); + return 1; + } - push_v3f(L, v); + const v3f point_rot = v3f(player->point_pitch, player->point_yaw, 0.0f); + const v3f point_dir = point_rot.rotationToDirection(); + push_v3f(L, point_dir); return 1; } @@ -2840,6 +2870,8 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, is_player), luamethod(ObjectRef, get_player_name), luamethod(ObjectRef, get_look_dir), + luamethod(ObjectRef, get_point_screen_pos), + luamethod(ObjectRef, get_point_dir), luamethod(ObjectRef, get_look_pitch), luamethod(ObjectRef, get_look_yaw), luamethod(ObjectRef, get_look_vertical), diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 900ec243dc2b0..1ba7148883b93 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -218,6 +218,12 @@ class ObjectRef : public ModApiBase { // get_look_dir(self) static int l_get_look_dir(lua_State *L); + // get_point_screen_pos(self) + static int l_get_point_screen_pos(lua_State *L); + + // get_point_dir(self) + static int l_get_point_dir(lua_State *L); + // DEPRECATED // get_look_pitch(self) static int l_get_look_pitch(lua_State *L); diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 5d6b891fa95fe..dc45adba8291a 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -409,6 +409,15 @@ void PlayerSAO::setPlayerYaw(const float yaw) UnitSAO::setRotation(rotation); } +v3f PlayerSAO::getLookDir() const +{ + // FIXME: get rid of deprecated method usage + float pitch = getRadLookPitchDep(); + float yaw = getRadYawDep(); + return v3f(std::cos(pitch) * std::cos(yaw), std::sin(pitch), std::cos(pitch) * + std::sin(yaw)); +} + void PlayerSAO::setFov(const float fov) { if (m_player && fov != m_fov) diff --git a/src/server/player_sao.h b/src/server/player_sao.h index 0ce26f7ccafc1..07f9d5ae1345c 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -87,6 +87,7 @@ class PlayerSAO : public UnitSAO f32 getRadLookPitch() const { return m_pitch * core::DEGTORAD; } // Deprecated f32 getRadLookPitchDep() const { return -1.0 * m_pitch * core::DEGTORAD; } + v3f getLookDir() const; void setFov(const float pitch); f32 getFov() const { return m_fov; } void setWantedRange(const s16 range);