diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 63d090ebe..56f8dbaae 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2557,6 +2557,12 @@ and `minetest.auth_reload` call the authetification handler. * `pos2`: Second position * `stepsize`: smaller gives more accurate results but requires more computing time. Default is `1`. +* `minetest.raycast(pos1, pos2, objects, liquids)`: returns `Raycast` + * Creates a `Raycast` object. + * `pos1`: start of the ray + * `pos2`: end of the ray + * `objects` : if false, only nodes will be returned. Default is `true`. + * `liquids' : if false, liquid nodes won't be returned. Default is `false`. * `minetest.find_path(pos1,pos2,searchdistance,max_jump,max_drop,algorithm)` * returns table containing path * returns a table of 3D points representing a path from `pos1` to `pos2` or `nil` @@ -3755,6 +3761,26 @@ It can be created via `Settings(filename)`. * Writes changes to file. * `to_table()`: returns `{[key1]=value1,...}` +### `Raycast` +A raycast on the map. It works with selection boxes. +Can be used as an iterator in a for loop. + +The map is loaded as the ray advances. If the +map is modified after the `Raycast` is created, +the changes may or may not have an effect on +the object. + +It can be created via `Raycast(pos1, pos2, objects, liquids)` or +`minetest.raycast(pos1, pos2, objects, liquids)` where: + * `pos1`: start of the ray + * `pos2`: end of the ray + * `objects` : if false, only nodes will be returned. Default is true. + * `liquids' : if false, liquid nodes won't be returned. Default is false. + +#### Methods +* `next()`: returns a `pointed_thing` + * Returns the next thing pointed by the ray or nil. + Mapgen objects -------------- A mapgen object is a construct used in map generation. Mapgen objects can be used diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 75a835b89..a7e26af7d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -428,9 +428,9 @@ set(common_SRCS porting.cpp profiler.cpp quicktune.cpp + raycast.cpp reflowscan.cpp remoteplayer.cpp - raycast.cpp rollback.cpp rollback_interface.cpp serialization.cpp diff --git a/src/activeobject.h b/src/activeobject.h index f349ddef3..4796e168c 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -65,7 +65,7 @@ public: { } - u16 getId() + u16 getId() const { return m_id; } @@ -76,7 +76,28 @@ public: } virtual ActiveObjectType getType() const = 0; + + + /*! + * Returns the collision box of the object. + * This box is translated by the object's + * location. + * The box's coordinates are world coordinates. + * @returns true if the object has a collision box. + */ virtual bool getCollisionBox(aabb3f *toset) const = 0; + + + /*! + * Returns the selection box of the object. + * This box is not translated when the + * object moves. + * The box's coordinates are world coordinates. + * @returns true if the object has a selection box. + */ + virtual bool getSelectionBox(aabb3f *toset) const = 0; + + virtual bool collideWithObjects() const = 0; protected: u16 m_id; // 0 is invalid, "no id" diff --git a/src/clientenvironment.cpp b/src/clientenvironment.cpp index 791b61531..9abb6a23d 100644 --- a/src/clientenvironment.cpp +++ b/src/clientenvironment.cpp @@ -604,240 +604,31 @@ ClientEnvEvent ClientEnvironment::getClientEnvEvent() return event; } -ClientActiveObject * ClientEnvironment::getSelectedActiveObject( - const core::line3d &shootline_on_map, v3f *intersection_point, - v3s16 *intersection_normal) +void ClientEnvironment::getSelectedActiveObjects( + const core::line3d &shootline_on_map, + std::vector &objects) { - std::vector objects; + std::vector allObjects; getActiveObjects(shootline_on_map.start, - shootline_on_map.getLength() + 3, objects); + shootline_on_map.getLength() + 10.0f, allObjects); const v3f line_vector = shootline_on_map.getVector(); - // Sort them. - // After this, the closest object is the first in the array. - std::sort(objects.begin(), objects.end()); - - /* Because objects can have different nodebox sizes, - * the object whose center is the nearest isn't necessarily - * the closest one. If an object is found, don't stop - * immediately. */ - - f32 d_min = shootline_on_map.getLength(); - ClientActiveObject *nearest_obj = NULL; - for (u32 i = 0; i < objects.size(); i++) { - ClientActiveObject *obj = objects[i].obj; - - aabb3f *selection_box = obj->getSelectionBox(); - if (selection_box == NULL) + for (u32 i = 0; i < allObjects.size(); i++) { + ClientActiveObject *obj = allObjects[i].obj; + aabb3f selection_box; + if (!obj->getSelectionBox(&selection_box)) continue; - v3f pos = obj->getPosition(); - - aabb3f offsetted_box(selection_box->MinEdge + pos, - selection_box->MaxEdge + pos); - - if (offsetted_box.getCenter().getDistanceFrom( - shootline_on_map.start) > d_min + 9.6f*BS) { - // Probably there is no active object that has bigger nodebox than - // (-5.5,-5.5,-5.5,5.5,5.5,5.5) - // 9.6 > 5.5*sqrt(3) - break; - } + 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, - ¤t_intersection, ¤t_normal)) { - f32 d_current = current_intersection.getDistanceFrom( - shootline_on_map.start); - if (d_current <= d_min) { - d_min = d_current; - nearest_obj = obj; - *intersection_point = current_intersection; - *intersection_normal = current_normal; - } + ¤t_intersection, ¤t_normal)) { + objects.push_back(PointedThing( + (s16) obj->getId(), current_intersection, current_normal, + (current_intersection - shootline_on_map.start).getLengthSQ())); } } - - return nearest_obj; -} - -/* - Check if a node is pointable -*/ -static inline bool isPointableNode(const MapNode &n, - INodeDefManager *ndef, bool liquids_pointable) -{ - const ContentFeatures &features = ndef->get(n); - return features.pointable || - (liquids_pointable && features.isLiquid()); -} - -PointedThing ClientEnvironment::getPointedThing( - core::line3d shootline, - bool liquids_pointable, - bool look_for_object) -{ - PointedThing result; - - INodeDefManager *nodedef = m_map->getNodeDefManager(); - - core::aabbox3d maximal_exceed = nodedef->getSelectionBoxIntUnion(); - // The code needs to search these nodes - core::aabbox3d search_range(-maximal_exceed.MaxEdge, - -maximal_exceed.MinEdge); - // If a node is found, there might be a larger node behind. - // To find it, we have to go further. - s16 maximal_overcheck = - std::max(abs(search_range.MinEdge.X), abs(search_range.MaxEdge.X)) - + std::max(abs(search_range.MinEdge.Y), abs(search_range.MaxEdge.Y)) - + std::max(abs(search_range.MinEdge.Z), abs(search_range.MaxEdge.Z)); - - const v3f original_vector = shootline.getVector(); - const f32 original_length = original_vector.getLength(); - - f32 min_distance = original_length; - - // First try to find an active object - if (look_for_object) { - ClientActiveObject *selected_object = getSelectedActiveObject( - shootline, &result.intersection_point, - &result.intersection_normal); - - if (selected_object != NULL) { - min_distance = - (result.intersection_point - shootline.start).getLength(); - - result.type = POINTEDTHING_OBJECT; - result.object_id = selected_object->getId(); - } - } - - // Reduce shootline - if (original_length > 0) { - shootline.end = shootline.start - + shootline.getVector() / original_length * min_distance; - } - - // Try to find a node that is closer than the selected active - // object (if it exists). - - voxalgo::VoxelLineIterator iterator(shootline.start / BS, - shootline.getVector() / BS); - v3s16 oldnode = iterator.m_current_node_pos; - // Indicates that a node was found. - bool is_node_found = false; - // If a node is found, it is possible that there's a node - // behind it with a large nodebox, so continue the search. - u16 node_foundcounter = 0; - // If a node is found, this is the center of the - // first nodebox the shootline meets. - v3f found_boxcenter(0, 0, 0); - // The untested nodes are in this range. - core::aabbox3d new_nodes; - while (true) { - // Test the nodes around the current node in search_range. - new_nodes = search_range; - new_nodes.MinEdge += iterator.m_current_node_pos; - new_nodes.MaxEdge += iterator.m_current_node_pos; - - // Only check new nodes - v3s16 delta = iterator.m_current_node_pos - oldnode; - if (delta.X > 0) - new_nodes.MinEdge.X = new_nodes.MaxEdge.X; - else if (delta.X < 0) - new_nodes.MaxEdge.X = new_nodes.MinEdge.X; - else if (delta.Y > 0) - new_nodes.MinEdge.Y = new_nodes.MaxEdge.Y; - else if (delta.Y < 0) - new_nodes.MaxEdge.Y = new_nodes.MinEdge.Y; - else if (delta.Z > 0) - new_nodes.MinEdge.Z = new_nodes.MaxEdge.Z; - else if (delta.Z < 0) - new_nodes.MaxEdge.Z = new_nodes.MinEdge.Z; - - // For each untested node - for (s16 x = new_nodes.MinEdge.X; x <= new_nodes.MaxEdge.X; x++) { - for (s16 y = new_nodes.MinEdge.Y; y <= new_nodes.MaxEdge.Y; y++) { - for (s16 z = new_nodes.MinEdge.Z; z <= new_nodes.MaxEdge.Z; z++) { - MapNode n; - v3s16 np(x, y, z); - bool is_valid_position; - - n = m_map->getNodeNoEx(np, &is_valid_position); - if (!(is_valid_position && - isPointableNode(n, nodedef, liquids_pointable))) { - continue; - } - std::vector boxes; - n.getSelectionBoxes(nodedef, &boxes, - n.getNeighbors(np, m_map)); - - v3f npf = intToFloat(np, BS); - for (std::vector::const_iterator i = boxes.begin(); - i != boxes.end(); ++i) { - aabb3f box = *i; - box.MinEdge += npf; - box.MaxEdge += npf; - v3f intersection_point; - v3s16 intersection_normal; - if (!boxLineCollision(box, shootline.start, shootline.getVector(), - &intersection_point, &intersection_normal)) { - continue; - } - f32 distance = (intersection_point - shootline.start).getLength(); - if (distance >= min_distance) { - continue; - } - result.type = POINTEDTHING_NODE; - result.node_undersurface = np; - result.intersection_point = intersection_point; - result.intersection_normal = intersection_normal; - found_boxcenter = box.getCenter(); - min_distance = distance; - is_node_found = true; - } - } - } - } - if (is_node_found) { - node_foundcounter++; - if (node_foundcounter > maximal_overcheck) { - break; - } - } - // Next node - if (iterator.hasNext()) { - oldnode = iterator.m_current_node_pos; - iterator.next(); - } else { - break; - } - } - - if (is_node_found) { - // Set undersurface and abovesurface nodes - f32 d = 0.002 * BS; - v3f fake_intersection = result.intersection_point; - // Move intersection towards its source block. - if (fake_intersection.X < found_boxcenter.X) - fake_intersection.X += d; - else - fake_intersection.X -= d; - - if (fake_intersection.Y < found_boxcenter.Y) - fake_intersection.Y += d; - else - fake_intersection.Y -= d; - - if (fake_intersection.Z < found_boxcenter.Z) - fake_intersection.Z += d; - else - fake_intersection.Z -= d; - - result.node_real_undersurface = floatToInt(fake_intersection, BS); - result.node_abovesurface = result.node_real_undersurface - + result.intersection_normal; - } - return result; } diff --git a/src/clientenvironment.h b/src/clientenvironment.h index 05ec1908f..070ff95fb 100644 --- a/src/clientenvironment.h +++ b/src/clientenvironment.h @@ -30,7 +30,6 @@ class ClientScripting; class ClientActiveObject; class GenericCAO; class LocalPlayer; -struct PointedThing; /* The client-side environment. @@ -125,44 +124,15 @@ public: std::vector &dest); bool hasClientEnvEvents() const { return !m_client_event_queue.empty(); } + // Get event from queue. If queue is empty, it triggers an assertion failure. ClientEnvEvent getClientEnvEvent(); - /*! - * Gets closest object pointed by the shootline. - * Returns NULL if not found. - * - * \param[in] shootline_on_map the shootline for - * the test in world coordinates - * \param[out] intersection_point the first point where - * the shootline meets the object. Valid only if - * not NULL is returned. - * \param[out] intersection_normal the normal vector of - * the intersection, pointing outwards. Zero vector if - * the shootline starts in an active object. - * Valid only if not NULL is returned. - */ - ClientActiveObject * getSelectedActiveObject( + virtual void getSelectedActiveObjects( const core::line3d &shootline_on_map, - v3f *intersection_point, - v3s16 *intersection_normal + std::vector &objects ); - /*! - * Performs a raycast on the world. - * Returns the first thing the shootline meets. - * - * @param[in] shootline the shootline, starting from - * the camera position. This also gives the maximal distance - * of the search. - * @param[in] liquids_pointable if false, liquids are ignored - * @param[in] look_for_object if false, objects are ignored - */ - PointedThing getPointedThing( - core::line3d shootline, - bool liquids_pointable, - bool look_for_object); - u16 attachement_parent_ids[USHRT_MAX + 1]; const std::list &getPlayerNames() { return m_player_names; } diff --git a/src/clientobject.h b/src/clientobject.h index f8075d65a..eeed2516d 100644 --- a/src/clientobject.h +++ b/src/clientobject.h @@ -44,8 +44,8 @@ public: virtual void updateLight(u8 light_at_pos){} virtual void updateLightNoCheck(u8 light_at_pos){} virtual v3s16 getLightPosition(){return v3s16(0,0,0);} - virtual aabb3f *getSelectionBox() { return NULL; } virtual bool getCollisionBox(aabb3f *toset) const { return false; } + virtual bool getSelectionBox(aabb3f *toset) const { return false; } virtual bool collideWithObjects() const { return false; } virtual v3f getPosition(){ return v3f(0,0,0); } virtual float getYaw() const { return 0; } diff --git a/src/content_cao.cpp b/src/content_cao.cpp index aff143bf2..49c2049eb 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -283,8 +283,14 @@ public: void initialize(const std::string &data); - aabb3f *getSelectionBox() - {return &m_selection_box;} + + virtual bool getSelectionBox(aabb3f *toset) const + { + *toset = m_selection_box; + return true; + } + + v3f getPosition() {return m_position;} inline float getYaw() const @@ -605,11 +611,14 @@ GenericCAO::~GenericCAO() removeFromScene(true); } -aabb3f *GenericCAO::getSelectionBox() +bool GenericCAO::getSelectionBox(aabb3f *toset) const { - if(!m_prop.is_visible || !m_is_visible || m_is_local_player || getParent() != NULL) - return NULL; - return &m_selection_box; + if (!m_prop.is_visible || !m_is_visible || m_is_local_player + || getParent() != NULL){ + return false; + } + *toset = m_selection_box; + return true; } v3f GenericCAO::getPosition() @@ -658,7 +667,7 @@ void GenericCAO::setAttachments() updateAttachments(); } -ClientActiveObject* GenericCAO::getParent() +ClientActiveObject* GenericCAO::getParent() const { ClientActiveObject *obj = NULL; diff --git a/src/content_cao.h b/src/content_cao.h index 526f10ea3..d6d5deac8 100644 --- a/src/content_cao.h +++ b/src/content_cao.h @@ -129,13 +129,13 @@ public: void processInitData(const std::string &data); - ClientActiveObject *getParent(); + ClientActiveObject *getParent() const; bool getCollisionBox(aabb3f *toset) const; bool collideWithObjects() const; - aabb3f *getSelectionBox(); + virtual bool getSelectionBox(aabb3f *toset) const; v3f getPosition(); inline float getYaw() const diff --git a/src/content_sao.cpp b/src/content_sao.cpp index c6c00768e..5bbbd154d 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -91,6 +91,9 @@ public: } bool getCollisionBox(aabb3f *toset) const { return false; } + + virtual bool getSelectionBox(aabb3f *toset) const { return false; } + bool collideWithObjects() const { return false; } private: @@ -746,6 +749,18 @@ bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const return false; } +bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const +{ + if (!m_prop.is_visible) { + return false; + } + + toset->MinEdge = m_prop.collisionbox.MinEdge * BS; + toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; + + return true; +} + bool LuaEntitySAO::collideWithObjects() const { return m_prop.collideWithObjects; @@ -1405,3 +1420,14 @@ bool PlayerSAO::getCollisionBox(aabb3f *toset) const toset->MaxEdge += m_base_position; return true; } + +bool PlayerSAO::getSelectionBox(aabb3f *toset) const +{ + if (!m_prop.is_visible) { + return false; + } + + getCollisionBox(toset); + + return true; +} diff --git a/src/content_sao.h b/src/content_sao.h index 0a1ae5ecf..a986acab0 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -127,6 +127,7 @@ public: bool select_horiz_by_yawpitch); std::string getName(); bool getCollisionBox(aabb3f *toset) const; + bool getSelectionBox(aabb3f *toset) const; bool collideWithObjects() const; private: std::string getPropertyPacket(); @@ -357,6 +358,7 @@ public: } bool getCollisionBox(aabb3f *toset) const; + bool getSelectionBox(aabb3f *toset) const; bool collideWithObjects() const { return true; } void finalize(RemotePlayer *player, const std::set &privs); diff --git a/src/environment.cpp b/src/environment.cpp index 904bab6f3..e7ab626b4 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "environment.h" #include "collision.h" +#include "raycast.h" #include "serverobject.h" #include "scripting_server.h" #include "server.h" @@ -83,6 +84,179 @@ float Environment::getTimeOfDayF() return m_time_of_day_f; } +/* + Check if a node is pointable +*/ +inline static bool isPointableNode(const MapNode &n, + INodeDefManager *nodedef , bool liquids_pointable) +{ + const ContentFeatures &features = nodedef->get(n); + return features.pointable || + (liquids_pointable && features.isLiquid()); +} + +void Environment::continueRaycast(RaycastState *state, PointedThing *result) +{ + INodeDefManager *nodedef = getMap().getNodeDefManager(); + if (state->m_initialization_needed) { + // Add objects + if (state->m_objects_pointable) { + std::vector found; + getSelectedActiveObjects(state->m_shootline, found); + for (std::vector::iterator pointed = found.begin(); + pointed != found.end(); ++pointed) { + state->m_found.push(*pointed); + } + } + // Set search range + core::aabbox3d maximal_exceed = nodedef->getSelectionBoxIntUnion(); + state->m_search_range.MinEdge = -maximal_exceed.MaxEdge; + state->m_search_range.MaxEdge = -maximal_exceed.MinEdge; + // Setting is done + state->m_initialization_needed = false; + } + + // The index of the first pointed thing that was not returned + // before. The last index which needs to be tested. + s16 lastIndex = state->m_iterator.m_last_index; + if (!state->m_found.empty()) { + lastIndex = state->m_iterator.getIndex( + floatToInt(state->m_found.top().intersection_point, BS)); + } + + Map &map = getMap(); + // If a node is found, this is the center of the + // first nodebox the shootline meets. + v3f found_boxcenter(0, 0, 0); + // The untested nodes are in this range. + core::aabbox3d new_nodes; + while (state->m_iterator.m_current_index <= lastIndex) { + // Test the nodes around the current node in search_range. + new_nodes = state->m_search_range; + new_nodes.MinEdge += state->m_iterator.m_current_node_pos; + new_nodes.MaxEdge += state->m_iterator.m_current_node_pos; + + // Only check new nodes + v3s16 delta = state->m_iterator.m_current_node_pos + - state->m_previous_node; + if (delta.X > 0) { + new_nodes.MinEdge.X = new_nodes.MaxEdge.X; + } else if (delta.X < 0) { + new_nodes.MaxEdge.X = new_nodes.MinEdge.X; + } else if (delta.Y > 0) { + new_nodes.MinEdge.Y = new_nodes.MaxEdge.Y; + } else if (delta.Y < 0) { + new_nodes.MaxEdge.Y = new_nodes.MinEdge.Y; + } else if (delta.Z > 0) { + new_nodes.MinEdge.Z = new_nodes.MaxEdge.Z; + } else if (delta.Z < 0) { + new_nodes.MaxEdge.Z = new_nodes.MinEdge.Z; + } + + // For each untested node + for (s16 x = new_nodes.MinEdge.X; x <= new_nodes.MaxEdge.X; x++) + for (s16 y = new_nodes.MinEdge.Y; y <= new_nodes.MaxEdge.Y; y++) + for (s16 z = new_nodes.MinEdge.Z; z <= new_nodes.MaxEdge.Z; z++) { + MapNode n; + v3s16 np(x, y, z); + bool is_valid_position; + + n = map.getNodeNoEx(np, &is_valid_position); + if (!(is_valid_position && isPointableNode(n, nodedef, + state->m_liquids_pointable))) { + continue; + } + + PointedThing result; + + std::vector boxes; + n.getSelectionBoxes(nodedef, &boxes, + n.getNeighbors(np, &map)); + + // Is there a collision with a selection box? + bool is_colliding = false; + // Minimal distance of all collisions + float min_distance_sq = 10000000; + + v3f npf = intToFloat(np, BS); + for (std::vector::const_iterator i = boxes.begin(); + i != boxes.end(); ++i) { + // Get current collision box + aabb3f box = *i; + box.MinEdge += npf; + box.MaxEdge += npf; + + v3f intersection_point; + v3s16 intersection_normal; + if (!boxLineCollision(box, state->m_shootline.start, + state->m_shootline.getVector(), &intersection_point, + &intersection_normal)) + continue; + + f32 distanceSq = (intersection_point + - state->m_shootline.start).getLengthSQ(); + // If this is the nearest collision, save it + if (min_distance_sq > distanceSq) { + min_distance_sq = distanceSq; + result.intersection_point = intersection_point; + result.intersection_normal = intersection_normal; + found_boxcenter = box.getCenter(); + is_colliding = true; + } + } + // If there wasn't a collision, stop + if (!is_colliding) { + continue; + } + result.type = POINTEDTHING_NODE; + result.node_undersurface = np; + result.distanceSq = min_distance_sq; + // Set undersurface and abovesurface nodes + f32 d = 0.002 * BS; + v3f fake_intersection = result.intersection_point; + // Move intersection towards its source block. + if (fake_intersection.X < found_boxcenter.X) { + fake_intersection.X += d; + } else { + fake_intersection.X -= d; + } + if (fake_intersection.Y < found_boxcenter.Y) { + fake_intersection.Y += d; + } else { + fake_intersection.Y -= d; + } + if (fake_intersection.Z < found_boxcenter.Z) { + fake_intersection.Z += d; + } else { + fake_intersection.Z -= d; + } + result.node_real_undersurface = floatToInt( + fake_intersection, BS); + result.node_abovesurface = result.node_real_undersurface + + result.intersection_normal; + // Push found PointedThing + state->m_found.push(result); + // If this is nearer than the old nearest object, + // the search can be shorter + s16 newIndex = state->m_iterator.getIndex( + result.node_real_undersurface); + if (newIndex < lastIndex) { + lastIndex = newIndex; + } + } + // Next node + state->m_previous_node = state->m_iterator.m_current_node_pos; + state->m_iterator.next(); + } + // Return empty PointedThing if nothing left on the ray + if (state->m_found.empty()) { + result->type = POINTEDTHING_NOTHING; + } else { + *result = state->m_found.top(); + state->m_found.pop(); + } +} + void Environment::stepTimeOfDay(float dtime) { MutexAutoLock lock(this->m_time_lock); diff --git a/src/environment.h b/src/environment.h index 11c2533ca..b45775f4c 100644 --- a/src/environment.h +++ b/src/environment.h @@ -42,6 +42,8 @@ with this program; if not, write to the Free Software Foundation, Inc., class IGameDef; class Map; +struct PointedThing; +class RaycastState; class Environment { @@ -76,6 +78,26 @@ public: u32 getDayCount(); + /*! + * Gets the objects pointed by the shootline as + * pointed things. + * If this is a client environment, the local player + * won't be returned. + * @param[in] shootline_on_map the shootline for + * the test in world coordinates + * + * @param[out] objects found objects + */ + virtual void getSelectedActiveObjects(const core::line3d &shootline_on_map, + std::vector &objects) = 0; + + /*! + * Returns the next node or object the shootline meets. + * @param state current state of the raycast + * @result output, will contain the next pointed thing + */ + void continueRaycast(RaycastState *state, PointedThing *result); + // counter used internally when triggering ABMs u32 m_added_objects; diff --git a/src/game.cpp b/src/game.cpp index b6304f19e..88ae9c2b7 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -50,6 +50,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "particles.h" #include "profiler.h" #include "quicktune_shortcutter.h" +#include "raycast.h" #include "server.h" #include "settings.h" #include "sky.h" @@ -3667,28 +3668,22 @@ PointedThing Game::updatePointedThing( static thread_local const bool show_entity_selectionbox = g_settings->getBool( "show_entity_selectionbox"); - ClientMap &map = client->getEnv().getClientMap(); - INodeDefManager *nodedef=client->getNodeDefManager(); + ClientEnvironment &env = client->getEnv(); + ClientMap &map = env.getClientMap(); + INodeDefManager *nodedef = map.getNodeDefManager(); runData.selected_object = NULL; - PointedThing result=client->getEnv().getPointedThing( - shootline, liquids_pointable, look_for_object); + RaycastState s(shootline, look_for_object, liquids_pointable); + PointedThing result; + env.continueRaycast(&s, &result); if (result.type == POINTEDTHING_OBJECT) { runData.selected_object = client->getEnv().getActiveObject(result.object_id); - if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox()) { - aabb3f *selection_box = runData.selected_object->getSelectionBox(); - - // Box should exist because object was - // returned in the first place - - assert(selection_box); - + aabb3f selection_box; + if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() && + runData.selected_object->getSelectionBox(&selection_box)) { v3f pos = runData.selected_object->getPosition(); - selectionboxes->push_back(aabb3f( - selection_box->MinEdge, selection_box->MaxEdge)); - selectionboxes->push_back( - aabb3f(selection_box->MinEdge, selection_box->MaxEdge)); + selectionboxes->push_back(aabb3f(selection_box)); hud->setSelectionPos(pos, camera_offset); } } else if (result.type == POINTEDTHING_NODE) { diff --git a/src/raycast.cpp b/src/raycast.cpp index 58102c993..42cc22587 100644 --- a/src/raycast.cpp +++ b/src/raycast.cpp @@ -17,11 +17,47 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "raycast.h" #include "irr_v3d.h" #include "irr_aabb3d.h" +#include "constants.h" + +bool RaycastSort::operator() (const PointedThing &pt1, + const PointedThing &pt2) const +{ + // "nothing" can not be sorted + assert(pt1.type != POINTEDTHING_NOTHING); + assert(pt2.type != POINTEDTHING_NOTHING); + // returns false if pt1 is nearer than pt2 + if (pt1.distanceSq < pt2.distanceSq) { + return false; + } else if (pt1.distanceSq == pt2.distanceSq) { + // Sort them to allow only one order + if (pt1.type == POINTEDTHING_OBJECT) + return (pt2.type == POINTEDTHING_OBJECT + && pt1.object_id < pt2.object_id); + else + return (pt2.type == POINTEDTHING_OBJECT + || pt1.node_undersurface < pt2.node_undersurface); + } + return true; +} + + +RaycastState::RaycastState(const core::line3d &shootline, + bool objects_pointable, bool liquids_pointable) : + m_shootline(shootline), + m_iterator(shootline.start / BS, shootline.getVector() / BS), + m_previous_node(m_iterator.m_current_node_pos), + m_objects_pointable(objects_pointable), + m_liquids_pointable(liquids_pointable) +{ +} + bool boxLineCollision(const aabb3f &box, const v3f &start, - const v3f &dir, v3f *collision_point, v3s16 *collision_normal) { + const v3f &dir, v3f *collision_point, v3s16 *collision_normal) +{ if (box.isPointInside(start)) { *collision_point = start; collision_normal->set(0, 0, 0); diff --git a/src/raycast.h b/src/raycast.h index d7ec8c843..d69d9339b 100644 --- a/src/raycast.h +++ b/src/raycast.h @@ -20,6 +20,49 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef SRC_RAYCAST_H_ #define SRC_RAYCAST_H_ +#include "voxelalgorithms.h" +#include "util/pointedthing.h" + +//! Sorts PointedThings based on their distance. +struct RaycastSort +{ + bool operator() (const PointedThing &pt1, const PointedThing &pt2) const; +}; + +//! Describes the state of a raycast. +class RaycastState +{ +public: + /*! + * Creates a raycast. + * @param objects_pointable if false, only nodes will be found + * @param liquids pointable if false, liquid nodes won't be found + */ + RaycastState(const core::line3d &shootline, bool objects_pointable, + bool liquids_pointable); + + //! Shootline of the raycast. + core::line3d m_shootline; + //! Iterator to store the progress of the raycast. + voxalgo::VoxelLineIterator m_iterator; + //! Previous tested node during the raycast. + v3s16 m_previous_node; + + /*! + * This priority queue stores the found pointed things + * waiting to be returned. + */ + std::priority_queue, RaycastSort> m_found; + + bool m_objects_pointable; + bool m_liquids_pointable; + + //! The code needs to search these nodes around the center node. + core::aabbox3d m_search_range { 0, 0, 0, 0, 0, 0 }; + + //! If true, the Environment will initialize this state. + bool m_initialization_needed = true; +}; /*! * Checks if a line and a box intersects. diff --git a/src/script/cpp_api/s_item.h b/src/script/cpp_api/s_item.h index 7350a71c5..6ceb4b559 100644 --- a/src/script/cpp_api/s_item.h +++ b/src/script/cpp_api/s_item.h @@ -52,6 +52,7 @@ public: protected: friend class LuaItemStack; friend class ModApiItemMod; + friend class LuaRaycast; bool getItemCallback(const char *name, const char *callbackname); void pushPointedThing(const PointedThing& pointed); diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index e7284b035..3a4ba89f3 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -131,6 +131,105 @@ void LuaLBM::trigger(ServerEnvironment *env, v3s16 p, MapNode n) lua_pop(L, 1); // Pop error handler } +int LuaRaycast::l_next(lua_State *L) +{ + MAP_LOCK_REQUIRED; + + ScriptApiItem *script = getScriptApi(L); + GET_ENV_PTR; + + LuaRaycast *o = checkobject(L, 1); + PointedThing pointed; + env->continueRaycast(&o->state, &pointed); + if (pointed.type == POINTEDTHING_NOTHING) + lua_pushnil(L); + else + script->pushPointedThing(pointed); + + return 1; +} + +int LuaRaycast::create_object(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + bool objects = true; + bool liquids = false; + + v3f pos1 = checkFloatPos(L, 1); + v3f pos2 = checkFloatPos(L, 2); + if (lua_isboolean(L, 3)) { + objects = lua_toboolean(L, 3); + } + if (lua_isboolean(L, 4)) { + liquids = lua_toboolean(L, 4); + } + + LuaRaycast *o = new LuaRaycast(core::line3d(pos1, pos2), + objects, liquids); + + *(void **) (lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + return 1; +} + +LuaRaycast *LuaRaycast::checkobject(lua_State *L, int narg) +{ + NO_MAP_LOCK_REQUIRED; + + luaL_checktype(L, narg, LUA_TUSERDATA); + void *ud = luaL_checkudata(L, narg, className); + if (!ud) + luaL_typerror(L, narg, className); + return *(LuaRaycast **) ud; +} + +int LuaRaycast::gc_object(lua_State *L) +{ + LuaRaycast *o = *(LuaRaycast **) (lua_touserdata(L, 1)); + delete o; + return 0; +} + +void LuaRaycast::Register(lua_State *L) +{ + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); + + lua_pushliteral(L, "__call"); + lua_pushcfunction(L, l_next); + lua_settable(L, metatable); + + lua_pop(L, 1); + + luaL_openlib(L, 0, methods, 0); + lua_pop(L, 1); + + lua_register(L, className, create_object); +} + +const char LuaRaycast::className[] = "Raycast"; +const luaL_Reg LuaRaycast::methods[] = +{ + luamethod(LuaRaycast, next), + { 0, 0 } +}; + void LuaEmergeAreaCallback(v3s16 blockpos, EmergeAction action, void *param) { ScriptCallbackState *state = (ScriptCallbackState *)param; @@ -904,6 +1003,11 @@ int ModApiEnvMod::l_fix_light(lua_State *L) return 1; } +int ModApiEnvMod::l_raycast(lua_State *L) +{ + return LuaRaycast::create_object(L); +} + // emerge_area(p1, p2, [callback, context]) // emerge mapblocks in area p1..p2, calls callback with context upon completion int ModApiEnvMod::l_emerge_area(lua_State *L) @@ -1155,6 +1259,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(spawn_tree); API_FCT(find_path); API_FCT(line_of_sight); + API_FCT(raycast); API_FCT(transforming_liquid_add); API_FCT(forceload_block); API_FCT(forceload_free_block); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 7ce19b085..f380d8d6f 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_base.h" #include "serverenvironment.h" +#include "raycast.h" class ModApiEnvMod : public ModApiBase { private: @@ -159,6 +160,9 @@ private: // line_of_sight(pos1, pos2, stepsize) -> true/false static int l_line_of_sight(lua_State *L); + // raycast(pos1, pos2, objects, liquids) -> Raycast + static int l_raycast(lua_State *L); + // find_path(pos1, pos2, searchdistance, // max_jump, max_drop, algorithm) -> table containing path static int l_find_path(lua_State *L); @@ -245,6 +249,47 @@ public: virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n); }; +//! Lua wrapper for RaycastState objects +class LuaRaycast : public ModApiBase +{ +private: + static const char className[]; + static const luaL_Reg methods[]; + //! Inner state + RaycastState state; + + // Exported functions + + // garbage collector + static int gc_object(lua_State *L); + + /*! + * Raycast:next() -> pointed_thing + * Returns the next pointed thing on the ray. + */ + static int l_next(lua_State *L); +public: + //! Constructor with the same arguments as RaycastState. + LuaRaycast( + const core::line3d &shootline, + bool objects_pointable, + bool liquids_pointable) : + state(shootline, objects_pointable, liquids_pointable) + {} + + //! Creates a LuaRaycast and leaves it on top of the stack. + static int create_object(lua_State *L); + + /*! + * Returns the Raycast from the stack or throws an error. + * @param narg location of the RaycastState in the stack + */ + static LuaRaycast *checkobject(lua_State *L, int narg); + + //! Registers Raycast as a Lua userdata type. + static void Register(lua_State *L); +}; + struct ScriptCallbackState { ServerScripting *script; int callback_ref; diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index 51e13f04d..01e8e2fb5 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -92,6 +92,7 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) LuaPerlinNoiseMap::Register(L); LuaPseudoRandom::Register(L); LuaPcgRandom::Register(L); + LuaRaycast::Register(L); LuaSecureRandom::Register(L); LuaVoxelManip::Register(L); NodeMetaRef::Register(L); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index e8bdd2a28..11c71e28e 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -30,7 +30,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "remoteplayer.h" #include "scripting_server.h" #include "server.h" -#include "voxelalgorithms.h" #include "util/serialize.h" #include "util/basic_macros.h" #include "util/pointedthing.h" @@ -1662,6 +1661,38 @@ ActiveObjectMessage ServerEnvironment::getActiveObjectMessage() return message; } +void ServerEnvironment::getSelectedActiveObjects( + const core::line3d &shootline_on_map, + std::vector &objects) +{ + std::vector objectIds; + getObjectsInsideRadius(objectIds, shootline_on_map.start, + shootline_on_map.getLength() + 10.0f); + const v3f line_vector = shootline_on_map.getVector(); + + for (u32 i = 0; i < objectIds.size(); i++) { + ServerActiveObject* obj = getActiveObject(objectIds[i]); + + aabb3f selection_box; + if (!obj->getSelectionBox(&selection_box)) + continue; + + v3f pos = obj->getBasePosition(); + + 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, + ¤t_intersection, ¤t_normal)) { + objects.push_back(PointedThing( + (s16) objectIds[i], current_intersection, current_normal, + (current_intersection - shootline_on_map.start).getLengthSQ())); + } + } +} + /* ************ Private methods ************* */ diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 375b28f8a..48eb5b318 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -284,6 +284,11 @@ public: */ ActiveObjectMessage getActiveObjectMessage(); + virtual void getSelectedActiveObjects( + const core::line3d &shootline_on_map, + std::vector &objects + ); + /* Activate objects and dynamically modify for the dtime determined from timestamp and additional_dtime diff --git a/src/util/pointedthing.cpp b/src/util/pointedthing.cpp index f1f1d3f20..e5c5dcf4c 100644 --- a/src/util/pointedthing.cpp +++ b/src/util/pointedthing.cpp @@ -23,20 +23,47 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "../exceptions.h" #include +PointedThing::PointedThing(const v3s16 &under, const v3s16 &above, + const v3s16 &real_under, const v3f &point, const v3s16 &normal, + f32 distSq): + type(POINTEDTHING_NODE), + node_undersurface(under), + node_abovesurface(above), + node_real_undersurface(real_under), + intersection_point(point), + intersection_normal(normal), + distanceSq(distSq) +{} + +PointedThing::PointedThing(s16 id, const v3f &point, const v3s16 &normal, + f32 distSq) : + type(POINTEDTHING_OBJECT), + object_id(id), + intersection_point(point), + intersection_normal(normal), + distanceSq(distSq) +{} + std::string PointedThing::dump() const { std::ostringstream os(std::ios::binary); - if (type == POINTEDTHING_NOTHING) { - os<<"[nothing]"; - } else if (type == POINTEDTHING_NODE) { + switch (type) { + case POINTEDTHING_NOTHING: + os << "[nothing]"; + break; + case POINTEDTHING_NODE: + { const v3s16 &u = node_undersurface; const v3s16 &a = node_abovesurface; - os<<"[node under="< 0) { m_next_intersection_multi.X = (floorf(m_start_position.X - 0.5) + 1.5 @@ -1440,14 +1442,11 @@ VoxelLineIterator::VoxelLineIterator(const v3f &start_position, const v3f &line_ m_intersection_multi_inc.Z = -1 / m_line_vector.Z; m_step_directions.Z = -1; } - - m_has_next = (m_next_intersection_multi.X <= 1) - || (m_next_intersection_multi.Y <= 1) - || (m_next_intersection_multi.Z <= 1); } void VoxelLineIterator::next() { + m_current_index++; if ((m_next_intersection_multi.X < m_next_intersection_multi.Y) && (m_next_intersection_multi.X < m_next_intersection_multi.Z)) { m_next_intersection_multi.X += m_intersection_multi_inc.X; @@ -1459,10 +1458,13 @@ void VoxelLineIterator::next() m_next_intersection_multi.Z += m_intersection_multi_inc.Z; m_current_node_pos.Z += m_step_directions.Z; } +} - m_has_next = (m_next_intersection_multi.X <= 1) - || (m_next_intersection_multi.Y <= 1) - || (m_next_intersection_multi.Z <= 1); +s16 VoxelLineIterator::getIndex(v3s16 voxel){ + return + abs(voxel.X - m_start_node_pos.X) + + abs(voxel.Y - m_start_node_pos.Y) + + abs(voxel.Z - m_start_node_pos.Z); } } // namespace voxalgo diff --git a/src/voxelalgorithms.h b/src/voxelalgorithms.h index 6e5fd5253..7203585e4 100644 --- a/src/voxelalgorithms.h +++ b/src/voxelalgorithms.h @@ -123,21 +123,25 @@ public: * which multiplying the line's vector gives a vector that ends * on the intersection of two nodes. */ - v3f m_next_intersection_multi = v3f(10000.0f, 10000.0f, 10000.0f); + v3f m_next_intersection_multi { 10000.0f, 10000.0f, 10000.0f }; /*! * Each component stores the smallest positive number, by which * m_next_intersection_multi's components can be increased. */ - v3f m_intersection_multi_inc = v3f(10000.0f, 10000.0f, 10000.0f); + v3f m_intersection_multi_inc { 10000.0f, 10000.0f, 10000.0f }; /*! * Direction of the line. Each component can be -1 or 1 (if a * component of the line's vector is 0, then there will be 1). */ - v3s16 m_step_directions = v3s16(1, 1, 1); + v3s16 m_step_directions { 1, 1, 1 }; //! Position of the current node. v3s16 m_current_node_pos; - //! If true, the next node will intersect the line, too. - bool m_has_next; + //! Index of the current node + s16 m_current_index = 0; + //! Position of the start node. + v3s16 m_start_node_pos; + //! Index of the last node + s16 m_last_index; /*! * Creates a voxel line iterator with the given line. @@ -161,7 +165,18 @@ public: /*! * Returns true if the next voxel intersects the given line. */ - inline bool hasNext() const { return m_has_next; } + inline bool hasNext() const + { + return m_current_index < m_last_index; + } + + /*! + * Returns how many times next() must be called until + * voxel==m_current_node_pos. + * If voxel does not intersect with the line, + * the result is undefined. + */ + s16 getIndex(v3s16 voxel); }; } // namespace voxalgo