Expose getPointedThing to Lua

This commit introduces Raycast, a Lua user object, which can be
used to perform a raycast on the map. The ray is continuable, so one can
also get hidden nodes (for example to see trough glass).
This commit is contained in:
Dániel Juhász 2016-07-23 21:11:20 +02:00 committed by paramat
parent a80ecbee1e
commit 3caad3f3c9
25 changed files with 671 additions and 313 deletions

@ -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

@ -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

@ -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"

@ -604,240 +604,31 @@ ClientEnvEvent ClientEnvironment::getClientEnvEvent()
return event;
}
ClientActiveObject * ClientEnvironment::getSelectedActiveObject(
const core::line3d<f32> &shootline_on_map, v3f *intersection_point,
v3s16 *intersection_normal)
void ClientEnvironment::getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects)
{
std::vector<DistanceSortedActiveObject> objects;
std::vector<DistanceSortedActiveObject> 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,
&current_intersection, &current_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;
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<f32> shootline,
bool liquids_pointable,
bool look_for_object)
{
PointedThing result;
INodeDefManager *nodedef = m_map->getNodeDefManager();
core::aabbox3d<s16> maximal_exceed = nodedef->getSelectionBoxIntUnion();
// The code needs to search these nodes
core::aabbox3d<s16> 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<s16> 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<aabb3f> boxes;
n.getSelectionBoxes(nodedef, &boxes,
n.getNeighbors(np, m_map));
v3f npf = intToFloat(np, BS);
for (std::vector<aabb3f>::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;
}

@ -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<DistanceSortedActiveObject> &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<f32> &shootline_on_map,
v3f *intersection_point,
v3s16 *intersection_normal
std::vector<PointedThing> &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<f32> shootline,
bool liquids_pointable,
bool look_for_object);
u16 attachement_parent_ids[USHRT_MAX + 1];
const std::list<std::string> &getPlayerNames() { return m_player_names; }

@ -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; }

@ -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;

@ -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

@ -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;
}

@ -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<std::string> &privs);

@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <fstream>
#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<PointedThing> found;
getSelectedActiveObjects(state->m_shootline, found);
for (std::vector<PointedThing>::iterator pointed = found.begin();
pointed != found.end(); ++pointed) {
state->m_found.push(*pointed);
}
}
// Set search range
core::aabbox3d<s16> 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<s16> 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<aabb3f> 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<aabb3f>::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);

@ -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<f32> &shootline_on_map,
std::vector<PointedThing> &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;

@ -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) {

@ -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<f32> &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);

@ -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<f32> &shootline, bool objects_pointable,
bool liquids_pointable);
//! Shootline of the raycast.
core::line3d<f32> 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<PointedThing, std::vector<PointedThing>, RaycastSort> m_found;
bool m_objects_pointable;
bool m_liquids_pointable;
//! The code needs to search these nodes around the center node.
core::aabbox3d<s16> 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.

@ -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);

@ -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<ScriptApiItem>(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<f32>(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);

@ -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<f32> &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;

@ -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);

@ -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<f32> &shootline_on_map,
std::vector<PointedThing> &objects)
{
std::vector<u16> 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,
&current_intersection, &current_normal)) {
objects.push_back(PointedThing(
(s16) objectIds[i], current_intersection, current_normal,
(current_intersection - shootline_on_map.start).getLengthSQ()));
}
}
}
/*
************ Private methods *************
*/

@ -284,6 +284,11 @@ public:
*/
ActiveObjectMessage getActiveObjectMessage();
virtual void getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects
);
/*
Activate objects and dynamically modify for the dtime determined
from timestamp and additional_dtime

@ -23,19 +23,46 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "../exceptions.h"
#include <sstream>
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) {
switch (type) {
case POINTEDTHING_NOTHING:
os << "[nothing]";
} else if (type == POINTEDTHING_NODE) {
break;
case POINTEDTHING_NODE:
{
const v3s16 &u = node_undersurface;
const v3s16 &a = node_abovesurface;
os<<"[node under="<<u.X<<","<<u.Y<<","<<u.Z
<< " above="<<a.X<<","<<a.Y<<","<<a.Z<<"]";
} else if (type == POINTEDTHING_OBJECT) {
os << "[node under=" << u.X << "," << u.Y << "," << u.Z << " above="
<< a.X << "," << a.Y << "," << a.Z << "]";
}
break;
case POINTEDTHING_OBJECT:
os << "[object " << object_id << "]";
} else {
break;
default:
os << "[unknown PointedThing]";
}
return os.str();
@ -104,4 +131,3 @@ bool PointedThing::operator!=(const PointedThing &pt2) const
{
return !(*this == pt2);
}

@ -58,6 +58,11 @@ struct PointedThing
* point of the collision and the nodebox of the node.
*/
v3s16 node_real_undersurface;
/*!
* Only valid if type is POINTEDTHING_OBJECT.
* The ID of the object the ray hit.
*/
s16 object_id = -1;
/*!
* Only valid if type isn't POINTEDTHING_NONE.
* First intersection point of the ray and the nodebox.
@ -71,12 +76,19 @@ struct PointedThing
*/
v3s16 intersection_normal;
/*!
* Only valid if type is POINTEDTHING_OBJECT.
* The ID of the object the ray hit.
* Square of the distance between the pointing
* ray's start point and the intersection point.
*/
s16 object_id = -1;
f32 distanceSq = 0;
//! Constructor for POINTEDTHING_NOTHING
PointedThing() {};
//! Constructor for POINTEDTHING_NODE
PointedThing(const v3s16 &under, const v3s16 &above,
const v3s16 &real_under, const v3f &point, const v3s16 &normal,
f32 distSq);
//! Constructor for POINTEDTHING_OBJECT
PointedThing(s16 id, const v3f &point, const v3s16 &normal, f32 distSq);
std::string dump() const;
void serialize(std::ostream &os) const;
void deSerialize(std::istream &is);

@ -1407,6 +1407,8 @@ VoxelLineIterator::VoxelLineIterator(const v3f &start_position, const v3f &line_
m_line_vector(line_vector)
{
m_current_node_pos = floatToInt(m_start_position, 1);
m_start_node_pos = m_current_node_pos;
m_last_index = getIndex(floatToInt(start_position + line_vector, 1));
if (m_line_vector.X > 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

@ -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