]> git.lizzy.rs Git - minetest.git/commitdiff
Allow rotating entity selectionboxes (#12379)
authorLars Müller <34514239+appgurueu@users.noreply.github.com>
Sun, 30 Oct 2022 15:53:14 +0000 (16:53 +0100)
committerGitHub <noreply@github.com>
Sun, 30 Oct 2022 15:53:14 +0000 (16:53 +0100)
18 files changed:
doc/lua_api.txt
games/devtest/mods/testentities/init.lua
games/devtest/mods/testentities/selectionbox.lua [new file with mode: 0644]
src/client/clientenvironment.cpp
src/client/game.cpp
src/client/hud.cpp
src/client/hud.h
src/environment.cpp
src/object_properties.cpp
src/object_properties.h
src/raycast.cpp
src/raycast.h
src/script/common/c_content.cpp
src/server/luaentity_sao.cpp
src/server/unit_sao.h
src/serverenvironment.cpp
src/util/pointedthing.cpp
src/util/pointedthing.h

index fd4e2d3e5af94f27658440a575de36a38d8d435a..9872f09aca56799b38b2d0a9140317009729ca6f 100644 (file)
@@ -1609,6 +1609,8 @@ Exact pointing location (currently only `Raycast` supports these fields):
 * `pointed_thing.intersection_normal`: Unit vector, points outwards of the
   selected selection box. This specifies which face is pointed at.
   Is a null vector `vector.zero()` when the pointer is inside the selection box.
+  For entities with rotated selection boxes, this will be rotated properly
+  by the entity's rotation - it will always be in absolute world space.
 
 
 
@@ -7075,6 +7077,8 @@ child will follow movement and rotation of that bone.
 * `set_rotation(rot)`
     * `rot` is a vector (radians). X is pitch (elevation), Y is yaw (heading)
       and Z is roll (bank).
+    * Does not reset rotation incurred through `automatic_rotate`.
+      Remove & readd your objects to force a certain rotation.
 * `get_rotation()`: returns the rotation, a vector (radians)
 * `set_yaw(yaw)`: sets the yaw in radians (heading).
 * `get_yaw()`: returns number in radians
@@ -7554,6 +7558,22 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or
 * `liquids`: if false, liquid nodes (`liquidtype ~= "none"`) won't be
              returned. Default is false.
 
+### Limitations
+
+Raycasts don't always work properly for attached objects as the server has no knowledge of models & bones.
+
+**Rotated selectionboxes paired with `automatic_rotate` are not reliable** either since the server
+can't reliably know the total rotation of the objects on different clients (which may differ on a per-client basis).
+The server calculates the total rotation incurred through `automatic_rotate` as a "best guess"
+assuming the object was active & rotating on the client all the time since its creation.
+This may be significantly out of sync with what clients see.
+Additionally, network latency and delayed property sending may create a mismatch of client- & server rotations.
+
+In singleplayer mode, raycasts on objects with rotated selectionboxes & automatic rotate will usually only be slightly off;
+toggling automatic rotation may however cause errors to add up.
+
+In multiplayer mode, the error may be arbitrarily large.
+
 ### Methods
 
 * `next()`: returns a `pointed_thing` with exact pointing location
@@ -7670,11 +7690,15 @@ Player properties need to be saved manually.
         collide_with_objects = true,
         -- Collide with other objects if physical = true
 
-        collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
-        selectionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
-        -- Selection box uses collision box dimensions when not set.
-        -- For both boxes: {xmin, ymin, zmin, xmax, ymax, zmax} in nodes from
-        -- object position.
+        collisionbox = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 },  -- default
+        selectionbox = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, rotate = false },
+               -- { xmin, ymin, zmin, xmax, ymax, zmax } in nodes from object position.
+        -- Collision boxes cannot rotate, setting `rotate = true` on it has no effect.
+        -- If not set, the selection box copies the collision box, and will also not rotate.
+        -- If `rotate = false`, the selection box will not rotate with the object itself, remaining fixed to the axes.
+        -- If `rotate = true`, it will match the object's rotation and any attachment rotations.
+        -- Raycasts use the selection box and object's rotation, but do *not* obey attachment rotations.
+        
 
         pointable = true,
         -- Whether the object can be pointed at
index 94d8d47c292ccc8e416946a91f593ad7dbac4848..6e0f8acb8d3127aba9d0554eaba9cd7a0746257b 100644 (file)
@@ -1,2 +1,3 @@
 dofile(minetest.get_modpath("testentities").."/visuals.lua")
+dofile(minetest.get_modpath("testentities").."/selectionbox.lua")
 dofile(minetest.get_modpath("testentities").."/armor.lua")
diff --git a/games/devtest/mods/testentities/selectionbox.lua b/games/devtest/mods/testentities/selectionbox.lua
new file mode 100644 (file)
index 0000000..36a8daa
--- /dev/null
@@ -0,0 +1,96 @@
+local function color(hex)
+       return ("blank.png^[noalpha^[colorize:#%06X:255"):format(hex)
+end
+
+local function random_color()
+       return color(math.random(0, 0xFFFFFF))
+end
+
+local function random_rotation()
+       return 2 * math.pi * vector.new(math.random(), math.random(), math.random())
+end
+
+local active_selectionbox_entities = 0 -- count active entities
+
+minetest.register_entity("testentities:selectionbox", {
+       initial_properties = {
+               visual = "cube",
+               infotext = "Punch to randomize rotation, rightclick to toggle rotation"
+       },
+       on_activate = function(self)
+               active_selectionbox_entities = active_selectionbox_entities + 1
+
+               local w, h, l = math.random(), math.random(), math.random()
+               self.object:set_properties({
+                       textures = {random_color(), random_color(), random_color(), random_color(), random_color(), random_color()},
+                       selectionbox = {rotate = true, -w/2, -h/2, -l/2, w/2, h/2, l/2},
+                       visual_size = vector.new(w, h, l),
+                       automatic_rotate = 2 * math.pi * (math.random() - 0.5)
+               })
+               assert(self.object:get_properties().selectionbox.rotate)
+               self.object:set_armor_groups({punch_operable = 1})
+               self.object:set_rotation(random_rotation())
+       end,
+       on_deactivate = function()
+               active_selectionbox_entities = active_selectionbox_entities - 1
+       end,
+       on_punch = function(self)
+               self.object:set_rotation(random_rotation())
+       end,
+       on_rightclick = function(self)
+               self.object:set_properties({
+                       automatic_rotate = self.object:get_properties().automatic_rotate == 0 and 2 * math.pi * (math.random() - 0.5) or 0
+               })
+       end
+})
+
+local hud_ids = {}
+minetest.register_globalstep(function()
+       if active_selectionbox_entities == 0 then
+               return
+       end
+
+       for _, player in pairs(minetest.get_connected_players()) do
+               local offset = player:get_eye_offset()
+               offset.y = offset.y + player:get_properties().eye_height
+               local pos1 = vector.add(player:get_pos(), offset)
+               local raycast = minetest.raycast(pos1, vector.add(pos1, vector.multiply(player:get_look_dir(), 10)), true, false)
+               local pointed_thing = raycast()
+               if pointed_thing.ref == player then
+                       pointed_thing = raycast()
+               end
+               local remove_hud_element = true
+               local pname = player:get_player_name()
+               local hud_id = hud_ids[pname]
+               if pointed_thing and pointed_thing.type == "object" then
+                       local ent = pointed_thing.ref:get_luaentity()
+                       if ent and ent.name == "testentities:selectionbox" then
+                               hud_ids[pname] = hud_id or player:hud_add({
+                                       hud_elem_type = "text",  -- See HUD element types
+                                       position = {x=0.5, y=0.5},
+                               text = "X",
+                                       number = 0xFF0000,
+                                       alignment = {x=0, y=0},
+                               })
+                               local shade = math.random(0, 0xFF)
+                               minetest.add_particle({
+                                       -- Random shade of red for the intersection point
+                                       texture = color(0x10000 * shade),
+                                       pos = pointed_thing.intersection_point,
+                                       size = 0.1
+                               })
+                               minetest.add_particle({
+                                       -- Same shade of green for the corresponding intersection normal
+                                       texture = color(0x100 * shade),
+                                       pos = vector.add(pointed_thing.intersection_point, pointed_thing.intersection_normal * 0.1),
+                                       size = 0.1
+                               })
+                               remove_hud_element = false
+                       end
+               end
+               if remove_hud_element and hud_id then
+                       player:hud_remove(hud_id)
+                       hud_ids[pname] = nil
+               end
+       end
+end)
index 0b6f1a32593bd5dc9a2e8892cdb0639e15470fd3..1ce443bccc6f509e064d0bd087fb0ca68310a8c0 100644 (file)
@@ -505,15 +505,24 @@ void ClientEnvironment::getSelectedActiveObjects(
                if (!obj->getSelectionBox(&selection_box))
                        continue;
 
-               const v3f &pos = obj->getPosition();
-               aabb3f offsetted_box(selection_box.MinEdge + pos,
-                       selection_box.MaxEdge + pos);
-
                v3f current_intersection;
-               v3s16 current_normal;
-               if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
-                               &current_intersection, &current_normal)) {
-                       objects.emplace_back((s16) obj->getId(), current_intersection, current_normal,
+               v3f current_normal, current_raw_normal;
+               const v3f rel_pos = shootline_on_map.start - obj->getPosition();
+               bool collision;
+               GenericCAO* gcao = dynamic_cast<GenericCAO*>(obj);
+               if (gcao != nullptr && gcao->getProperties().rotate_selectionbox) {
+                       gcao->getSceneNode()->updateAbsolutePosition();
+                       const v3f deg = obj->getSceneNode()->getAbsoluteTransformation().getRotationDegrees();
+                       collision = boxLineCollision(selection_box, deg,
+                               rel_pos, line_vector, &current_intersection, &current_normal, &current_raw_normal);
+               } else {
+                       collision = boxLineCollision(selection_box, rel_pos, line_vector,
+                               &current_intersection, &current_normal);
+                       current_raw_normal = current_normal;
+               }
+               if (collision) {
+                       current_intersection += obj->getPosition();
+                       objects.emplace_back(obj->getId(), current_intersection, current_normal, current_raw_normal,
                                (current_intersection - shootline_on_map.start).getLengthSQ());
                }
        }
index 91c93ef7f874ebad86bde99c00f8d0219919cf3e..cdcfde7591a2132184ddb8e01b4bc9c4ad5ecd18 100644 (file)
@@ -3297,7 +3297,7 @@ PointedThing Game::updatePointedThing(
 {
        std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
        selectionboxes->clear();
-       hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
+       hud->setSelectedFaceNormal(v3f());
        static thread_local const bool show_entity_selectionbox = g_settings->getBool(
                "show_entity_selectionbox");
 
@@ -3321,7 +3321,13 @@ PointedThing Game::updatePointedThing(
                        v3f pos = runData.selected_object->getPosition();
                        selectionboxes->push_back(aabb3f(selection_box));
                        hud->setSelectionPos(pos, camera_offset);
+                       GenericCAO* gcao = dynamic_cast<GenericCAO*>(runData.selected_object);
+                       if (gcao != nullptr && gcao->getProperties().rotate_selectionbox)
+                               hud->setSelectionRotation(gcao->getSceneNode()->getAbsoluteTransformation().getRotationDegrees());
+                       else
+                               hud->setSelectionRotation(v3f());
                }
+               hud->setSelectedFaceNormal(result.raw_intersection_normal);
        } else if (result.type == POINTEDTHING_NODE) {
                // Update selection boxes
                MapNode n = map.getNode(result.node_undersurface);
@@ -3339,10 +3345,8 @@ PointedThing Game::updatePointedThing(
                }
                hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
                        camera_offset);
-               hud->setSelectedFaceNormal(v3f(
-                       result.intersection_normal.X,
-                       result.intersection_normal.Y,
-                       result.intersection_normal.Z));
+               hud->setSelectionRotation(v3f());
+               hud->setSelectedFaceNormal(result.intersection_normal);
        }
 
        // Update selection mesh light level and vertex colors
index e8ccdbb2b7a18b98581411ce3473694d71ad19ee..58f5ec5f137607ae7c5a4d718918e020843b67b4 100644 (file)
@@ -826,28 +826,31 @@ void Hud::setSelectionPos(const v3f &pos, const v3s16 &camera_offset)
 
 void Hud::drawSelectionMesh()
 {
+       if (m_mode == HIGHLIGHT_NONE || (m_mode == HIGHLIGHT_HALO && !m_selection_mesh))
+               return;
+       const video::SMaterial oldmaterial = driver->getMaterial2D();
+       driver->setMaterial(m_selection_material);
+       const core::matrix4 oldtransform = driver->getTransform(video::ETS_WORLD);
+
+       core::matrix4 translate;
+       translate.setTranslation(m_selection_pos_with_offset);
+       core::matrix4 rotation;
+       rotation.setRotationDegrees(m_selection_rotation);
+       driver->setTransform(video::ETS_WORLD, translate * rotation);
+
        if (m_mode == HIGHLIGHT_BOX) {
                // Draw 3D selection boxes
-               video::SMaterial oldmaterial = driver->getMaterial2D();
-               driver->setMaterial(m_selection_material);
                for (auto & selection_box : m_selection_boxes) {
-                       aabb3f box = aabb3f(
-                               selection_box.MinEdge + m_selection_pos_with_offset,
-                               selection_box.MaxEdge + m_selection_pos_with_offset);
-
                        u32 r = (selectionbox_argb.getRed() *
                                        m_selection_mesh_color.getRed() / 255);
                        u32 g = (selectionbox_argb.getGreen() *
                                        m_selection_mesh_color.getGreen() / 255);
                        u32 b = (selectionbox_argb.getBlue() *
                                        m_selection_mesh_color.getBlue() / 255);
-                       driver->draw3DBox(box, video::SColor(255, r, g, b));
+                       driver->draw3DBox(selection_box, video::SColor(255, r, g, b));
                }
-               driver->setMaterial(oldmaterial);
        } else if (m_mode == HIGHLIGHT_HALO && m_selection_mesh) {
                // Draw selection mesh
-               video::SMaterial oldmaterial = driver->getMaterial2D();
-               driver->setMaterial(m_selection_material);
                setMeshColor(m_selection_mesh, m_selection_mesh_color);
                video::SColor face_color(0,
                        MYMIN(255, m_selection_mesh_color.getRed() * 1.5),
@@ -855,16 +858,14 @@ void Hud::drawSelectionMesh()
                        MYMIN(255, m_selection_mesh_color.getBlue() * 1.5));
                setMeshColorByNormal(m_selection_mesh, m_selected_face_normal,
                        face_color);
-               scene::IMesh* mesh = cloneMesh(m_selection_mesh);
-               translateMesh(mesh, m_selection_pos_with_offset);
                u32 mc = m_selection_mesh->getMeshBufferCount();
                for (u32 i = 0; i < mc; i++) {
-                       scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
+                       scene::IMeshBuffer *buf = m_selection_mesh->getMeshBuffer(i);
                        driver->drawMeshBuffer(buf);
                }
-               mesh->drop();
-               driver->setMaterial(oldmaterial);
        }
+       driver->setMaterial(oldmaterial);
+       driver->setTransform(video::ETS_WORLD, oldtransform);
 }
 
 enum Hud::BlockBoundsMode Hud::toggleBlockBounds()
index fd79183a00dbb99b357bddd8984a5efda623a7fb..b6ff84243c3c9137537678b32157f758c32df30e 100644 (file)
@@ -75,6 +75,10 @@ class Hud
 
        v3f getSelectionPos() const { return m_selection_pos; }
 
+       void setSelectionRotation(v3f rotation) { m_selection_rotation = rotation; }
+
+       v3f getSelectionRotation() const { return m_selection_rotation; }
+
        void setSelectionMeshColor(const video::SColor &color)
        {
                m_selection_mesh_color = color;
@@ -126,6 +130,7 @@ class Hud
        std::vector<aabb3f> m_halo_boxes;
        v3f m_selection_pos;
        v3f m_selection_pos_with_offset;
+       v3f m_selection_rotation;
 
        scene::IMesh *m_selection_mesh = nullptr;
        video::SColor m_selection_mesh_color;
index b04f77557190d03bfae6830fd2a6d7883dfb2a9f..514b15d01c98a82bf33a41ed7b38014df9b1bfc2 100644 (file)
@@ -202,21 +202,21 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result)
                        // ID of the current box (loop counter)
                        u16 id = 0;
 
+                       // Do calculations relative to the node center
+                       // to translate the ray rather than the boxes
                        v3f npf = intToFloat(np, BS);
-                       // This loop translates the boxes to their in-world place.
+                       v3f rel_start = state->m_shootline.start - npf;
                        for (aabb3f &box : boxes) {
-                               box.MinEdge += npf;
-                               box.MaxEdge += npf;
-
                                v3f intersection_point;
-                               v3s16 intersection_normal;
-                               if (!boxLineCollision(box, state->m_shootline.start,
+                               v3f intersection_normal;
+                               if (!boxLineCollision(box, rel_start,
                                                state->m_shootline.getVector(), &intersection_point,
                                                &intersection_normal)) {
                                        ++id;
                                        continue;
                                }
 
+                               intersection_point += npf; // translate back to world coords
                                f32 distanceSq = (intersection_point
                                        - state->m_shootline.start).getLengthSQ();
                                // If this is the nearest collision, save it
@@ -259,7 +259,7 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result)
                        result.node_real_undersurface = floatToInt(
                                fake_intersection, BS);
                        result.node_abovesurface = result.node_real_undersurface
-                               + result.intersection_normal;
+                               + floatToInt(result.intersection_normal, 1.0f);
                        // Push found PointedThing
                        state->m_found.push(result);
                        // If this is nearer than the old nearest object,
index c7f6becf0a887b5029929fcb3bed9c3357a6a2a0..e4c6569465a1bc0c0e2a180b642e85c01c87c077 100644 (file)
@@ -72,6 +72,7 @@ std::string ObjectProperties::dump()
                os << ", nametag_bgcolor=null ";
 
        os << ", selectionbox=" << PP(selectionbox.MinEdge) << "," << PP(selectionbox.MaxEdge);
+       os << ", rotate_selectionbox=" << rotate_selectionbox;
        os << ", pointable=" << pointable;
        os << ", static_save=" << static_save;
        os << ", eye_height=" << eye_height;
@@ -169,6 +170,7 @@ void ObjectProperties::serialize(std::ostream &os) const
        else
                writeARGB8(os, nametag_bgcolor.value());
 
+       writeU8(os, rotate_selectionbox);
        // Add stuff only at the bottom.
        // Never remove anything, because we don't want new versions of this
 }
@@ -236,5 +238,10 @@ void ObjectProperties::deSerialize(std::istream &is)
                        nametag_bgcolor = bgcolor;
                else
                        nametag_bgcolor = nullopt;
+
+               tmp = readU8(is);
+               if (is.eof())
+                       return;
+               rotate_selectionbox = tmp;
        } catch (SerializationError &e) {}
 }
index 79866a22c4ba8949f639d7c34e80f3cded067b22..f4c425d7bff61b3f2ffa43d032aa66f065d6541f 100644 (file)
@@ -35,6 +35,7 @@ struct ObjectProperties
        // Values are BS=1
        aabb3f collisionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f);
        aabb3f selectionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f);
+       bool rotate_selectionbox = false;
        bool pointable = true;
        std::string visual = "sprite";
        std::string mesh = "";
index ebc40235db815042b8d785732af2a9720a5230c1..ead024dd1bb4cc1b63bebe84692093915cef6a93 100644 (file)
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "raycast.h"
 #include "irr_v3d.h"
 #include "irr_aabb3d.h"
+#include <quaternion.h>
 #include "constants.h"
 
 bool RaycastSort::operator() (const PointedThing &pt1,
@@ -68,7 +69,7 @@ RaycastState::RaycastState(const core::line3d<f32> &shootline,
 
 
 bool boxLineCollision(const aabb3f &box, const v3f &start,
-       const v3f &dir, v3f *collision_point, v3s16 *collision_normal)
+       const v3f &dir, v3f *collision_point, v3f *collision_normal)
 {
        if (box.isPointInside(start)) {
                *collision_point = start;
@@ -135,3 +136,23 @@ bool boxLineCollision(const aabb3f &box, const v3f &start,
        }
        return false;
 }
+
+bool boxLineCollision(const aabb3f &box, const v3f &rotation,
+       const v3f &start, const v3f &dir,
+       v3f *collision_point, v3f *collision_normal, v3f *raw_collision_normal)
+{
+       // Inversely transform the ray rather than rotating the box faces;
+       // this allows us to continue using a simple ray - AABB intersection
+       core::quaternion rot(rotation * core::DEGTORAD);
+       rot.makeInverse();
+
+       bool collision = boxLineCollision(box, rot * start, rot * dir, collision_point, collision_normal);
+       if (!collision) return collision;
+
+       // Transform the results back
+       rot.makeInverse();
+       *collision_point = rot * *collision_point;
+       *raw_collision_normal = *collision_normal;
+       *collision_normal = rot * *collision_normal;
+       return collision;
+}
index 734efd6ad4ae2cc76f7b1ca9d92b8566bb9d3711..8da075738ccf3a89c48546f99b8e3f851156e72c 100644 (file)
@@ -74,4 +74,8 @@ class RaycastState
  * @returns true if a collision point was found
  */
 bool boxLineCollision(const aabb3f &box, const v3f &start, const v3f &dir,
-       v3f *collision_point, v3s16 *collision_normal);
+       v3f *collision_point, v3f *collision_normal);
+
+bool boxLineCollision(const aabb3f &box, const v3f &box_rotation,
+       const v3f &start, const v3f &dir,
+       v3f *collision_point, v3f *collision_normal, v3f *raw_collision_normal);
index cbe2d328cf689625b0b5cf3aa331260a6311b77e..6203ea78c04005509431f780d0631eda05b89f52 100644 (file)
@@ -236,10 +236,12 @@ void read_object_properties(lua_State *L, int index,
        lua_pop(L, 1);
 
        lua_getfield(L, -1, "selectionbox");
-       if (lua_istable(L, -1))
+       if (lua_istable(L, -1)) {
+               getboolfield(L, -1, "rotate", prop->rotate_selectionbox);
                prop->selectionbox = read_aabb3f(L, -1, 1.0);
-       else if (collisionbox_defined)
+       } else if (collisionbox_defined) {
                prop->selectionbox = prop->collisionbox;
+       }
        lua_pop(L, 1);
 
        getboolfield(L, -1, "pointable", prop->pointable);
@@ -377,6 +379,8 @@ void push_object_properties(lua_State *L, ObjectProperties *prop)
        push_aabb3f(L, prop->collisionbox);
        lua_setfield(L, -2, "collisionbox");
        push_aabb3f(L, prop->selectionbox);
+       lua_pushboolean(L, prop->rotate_selectionbox);
+       lua_setfield(L, -2, "rotate");
        lua_setfield(L, -2, "selectionbox");
        lua_pushboolean(L, prop->pointable);
        lua_setfield(L, -2, "pointable");
@@ -1880,7 +1884,7 @@ void push_pointed_thing(lua_State *L, const PointedThing &pointed, bool csm,
        if (hitpoint && (pointed.type != POINTEDTHING_NOTHING)) {
                push_v3f(L, pointed.intersection_point / BS); // convert to node coords
                lua_setfield(L, -2, "intersection_point");
-               push_v3s16(L, pointed.intersection_normal);
+               push_v3f(L, pointed.intersection_normal);
                lua_setfield(L, -2, "intersection_normal");
                lua_pushinteger(L, pointed.box_id + 1); // change to Lua array index
                lua_setfield(L, -2, "box_id");
index b25f19b85220c519511a8be27d920c4e0cc37778..5624c0f5512608ee73b800aa3d55381a709b4af5 100644 (file)
@@ -197,6 +197,11 @@ void LuaEntitySAO::step(float dtime, bool send_recommended)
                }
        }
 
+       if (fabs(m_prop.automatic_rotate) > 0.001f) {
+               m_rotation_add_yaw = modulo360f(m_rotation_add_yaw + dtime * core::RADTODEG *
+                               m_prop.automatic_rotate);
+       }
+
        if(m_registered) {
                m_env->getScriptIface()->luaentity_Step(m_id, dtime, moveresult_p);
        }
index a21e055c567607bd13124b57a1e58666a857f050..dedb1874e9a09eea5fe0a3faae37b7a7d1294c55 100644 (file)
@@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "object_properties.h"
 #include "serveractiveobject.h"
+#include <quaternion.h>
+#include "util/numeric.h"
 
 class UnitSAO : public ServerActiveObject
 {
@@ -36,6 +38,17 @@ class UnitSAO : public ServerActiveObject
        // Rotation
        void setRotation(v3f rotation) { m_rotation = rotation; }
        const v3f &getRotation() const { return m_rotation; }
+       const v3f getTotalRotation() const {
+               // This replicates what happens clientside serverside
+               core::matrix4 rot;
+               setPitchYawRoll(rot, -m_rotation);
+               v3f res;
+               // First rotate by m_rotation, then rotate by the automatic rotate yaw
+               (core::quaternion(v3f(0, -m_rotation_add_yaw * core::DEGTORAD, 0))
+                               * core::quaternion(rot.getRotationDegrees() * core::DEGTORAD))
+                               .toEuler(res);
+               return res * core::RADTODEG;
+       }
        v3f getRadRotation() { return m_rotation * core::DEGTORAD; }
 
        // Deprecated
@@ -95,6 +108,7 @@ class UnitSAO : public ServerActiveObject
        u16 m_hp = 1;
 
        v3f m_rotation;
+       f32 m_rotation_add_yaw = 0;
 
        ItemGroupList m_armor_groups;
 
index 4cdb21a4b1676dc738aa81707f7328e4774d1b6e..f893064de9429264c2b65972e88d7570ca2907f3 100644 (file)
@@ -1771,16 +1771,27 @@ void ServerEnvironment::getSelectedActiveObjects(
                        continue;
 
                v3f pos = obj->getBasePosition();
-
-               aabb3f offsetted_box(selection_box.MinEdge + pos,
-                       selection_box.MaxEdge + pos);
+               v3f rel_pos = shootline_on_map.start - pos;
 
                v3f current_intersection;
-               v3s16 current_normal;
-               if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
-                               &current_intersection, &current_normal)) {
+               v3f current_normal;
+               v3f current_raw_normal;
+
+               ObjectProperties *props = obj->accessObjectProperties();
+               bool collision;
+               UnitSAO* usao = dynamic_cast<UnitSAO*>(obj);
+               if (props->rotate_selectionbox && usao != nullptr) {
+                       collision = boxLineCollision(selection_box, usao->getTotalRotation(),
+                               rel_pos, line_vector, &current_intersection, &current_normal, &current_raw_normal);
+               } else {
+                       collision = boxLineCollision(selection_box, rel_pos, line_vector,
+                               &current_intersection, &current_normal);
+                       current_raw_normal = current_normal;
+               }
+               if (collision) {
+                       current_intersection += pos;
                        objects.emplace_back(
-                               (s16) obj->getId(), current_intersection, current_normal,
+                               (s16) obj->getId(), current_intersection, current_normal, current_raw_normal,
                                (current_intersection - shootline_on_map.start).getLengthSQ());
                }
        }
index 6aa37dfe862b05d95cb86245ad83b5f78af3aacb..f6b4b77b6c0fe3b12080728a39360e244e567784 100644 (file)
@@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <sstream>
 
 PointedThing::PointedThing(const v3s16 &under, const v3s16 &above,
-       const v3s16 &real_under, const v3f &point, const v3s16 &normal,
+       const v3s16 &real_under, const v3f &point, const v3f &normal,
        u16 box_id, f32 distSq):
        type(POINTEDTHING_NODE),
        node_undersurface(under),
@@ -36,12 +36,13 @@ PointedThing::PointedThing(const v3s16 &under, const v3s16 &above,
        distanceSq(distSq)
 {}
 
-PointedThing::PointedThing(u16 id, const v3f &point, const v3s16 &normal,
-       f32 distSq) :
+PointedThing::PointedThing(u16 id, const v3f &point,
+  const v3f &normal, const v3f &raw_normal, f32 distSq) :
        type(POINTEDTHING_OBJECT),
        object_id(id),
        intersection_point(point),
        intersection_normal(normal),
+       raw_intersection_normal(raw_normal),
        distanceSq(distSq)
 {}
 
index 68b18319565286177e371b4b96476f8a8995b865..e4f2eb20cc000329f5a1d8df2d39c06ecea15f1b 100644 (file)
@@ -74,7 +74,12 @@ struct PointedThing
         * This is perpendicular to the face the ray hits,
         * points outside of the box and it's length is 1.
         */
-       v3s16 intersection_normal;
+       v3f intersection_normal;
+       /*!
+        * Only valid if type is POINTEDTHING_OBJECT.
+        * Raw normal vector of the intersection before applying rotation.
+        */
+       v3f raw_intersection_normal;
        /*!
         * Only valid if type isn't POINTEDTHING_NONE.
         * Indicates which selection box is selected, if there are more of them.
@@ -90,10 +95,10 @@ struct PointedThing
        PointedThing() = default;
        //! Constructor for POINTEDTHING_NODE
        PointedThing(const v3s16 &under, const v3s16 &above,
-               const v3s16 &real_under, const v3f &point, const v3s16 &normal,
+               const v3s16 &real_under, const v3f &point, const v3f &normal,
                u16 box_id, f32 distSq);
        //! Constructor for POINTEDTHING_OBJECT
-       PointedThing(u16 id, const v3f &point, const v3s16 &normal, f32 distSq);
+       PointedThing(u16 id, const v3f &point, const v3f &normal, const v3f &raw_normal, f32 distSq);
        std::string dump() const;
        void serialize(std::ostream &os) const;
        void deSerialize(std::istream &is);