Merge branch 'master' into 'next'

Faster raycasting

I have modified the getPointedNode() function. The new time complexity is O(d) instea of O(d^3).
This allows greater building distances to creative players (not implemented, just possibility).
I have split the function to Map::raycast() and createHilightbox(). This makes Map::raycast() reusable in hostile creatures' AIs.
I have also fixed a bug: active objects can no longer be selected if a node blocks the sight (not perfectly accurate, but it works).

See merge request !51
This commit is contained in:
darkrose 2016-05-29 04:00:20 +00:00
commit bb3b914747
3 changed files with 446 additions and 373 deletions

View File

@ -248,74 +248,21 @@ public:
};
/*
Find what the player is pointing at
* Creates a hilightbox for the given node
*/
void getPointedNode(Client *client, v3f player_position,
v3f camera_direction, v3f camera_position,
bool &nodefound, core::line3d<f32> shootline,
v3s16 &nodepos, v3s16 &neighbourpos, v3s16 camera_offset,
core::aabbox3d<f32> &nodehilightbox,
f32 d)
{
f32 mindistance = BS * 1001;
v3s16 pos_i = floatToInt(player_position, BS);
/*infostream<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
<<std::endl;*/
//printf("%f,%f,%f - %f,%f,%f\n",shootline.start.X,shootline.start.Y,shootline.start.Z,shootline.end.X,shootline.end.Y,shootline.end.Z);
s16 a = d;
s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
InventoryItem *wield = (InventoryItem*)client->getLocalPlayer()->getWieldItem();
bool wield_is_hand = (wield == NULL);
bool wield_is_tool = (wield && wield->getContent()&CONTENT_TOOLITEM_MASK);
bool wield_is_craft = (wield && wield->getContent()&CONTENT_CRAFTITEM_MASK);
bool wield_is_material = (!wield_is_hand && !wield_is_tool && !wield_is_craft);
content_t content = CONTENT_IGNORE;
for(s16 y = ystart; y <= yend; y++)
for(s16 z = zstart; z <= zend; z++)
for(s16 x = xstart; x <= xend; x++)
{
//printf("%d,%d,%d\n",x,y,z);
void createHilightbox(Map *map, v3s16 nodepos,
v3s16 camera_offset, core::aabbox3d<f32> &nodehilightbox
){
MapNode n;
try
{
n = client->getNode(v3s16(x,y,z));
if (content_features(n.getContent()).pointable == false) {
if (content_features(n.getContent()).liquid_type != LIQUID_SOURCE)
continue;
if (!wield || content_toolitem_features(wield->getContent()).liquids_pointable == false)
continue;
}else if (content_features(n.getContent()).material_pointable == false && wield_is_material) {
continue;
}
n = map->getNode(nodepos);
}
catch(InvalidPositionException &e)
{
continue;
return;
}
v3s16 np(x,y,z);
v3f npf = intToFloat(np, BS);
f32 d = 0.01;
v3s16 dirs[6] = {
v3s16(0,0,1), // back
v3s16(0,1,0), // top
v3s16(1,0,0), // right
v3s16(0,0,-1), // front
v3s16(0,-1,0), // bottom
v3s16(-1,0,0), // left
};
v3f npf = intToFloat(nodepos, BS);
/*
Meta-objects
@ -325,10 +272,7 @@ void getPointedNode(Client *client, v3f player_position,
v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
dir_f *= BS/2 - BS/6 - BS/20;
v3f cpf = npf + dir_f;
f32 distance = (cpf - camera_position).getLength();
core::aabbox3d<f32> box;
// bottom
if(dir == v3s16(0,-1,0))
{
@ -353,55 +297,26 @@ void getPointedNode(Client *client, v3f player_position,
cpf + v3f(BS/6, BS/3, BS/6)
);
}
if(distance < mindistance)
{
if(box.intersectsWithLine(shootline))
{
nodefound = true;
nodepos = np;
content = n.getContent();
neighbourpos = np;
mindistance = distance;
box.MinEdge -= intToFloat(camera_offset,BS);
box.MaxEdge -= intToFloat(camera_offset,BS);
nodehilightbox = box;
}
}
}else if(n.getContent() == CONTENT_RAIL) {
f32 distance = (npf - camera_position).getLength();
float d = (float)BS/8;
v3f vertices[4] =
{
v3f(BS/2, -BS/2+d, -BS/2),
v3f(-BS/2, -BS/2, BS/2),
};
for(s32 i=0; i<2; i++)
{
vertices[i] += npf;
}
core::aabbox3d<f32> box;
box = core::aabbox3d<f32>(vertices[0]);
box.addInternalPoint(vertices[1]);
if(distance < mindistance)
{
if(box.intersectsWithLine(shootline))
{
nodefound = true;
nodepos = np;
content = n.getContent();
neighbourpos = np;
mindistance = distance;
box.MinEdge -= intToFloat(camera_offset,BS);
box.MaxEdge -= intToFloat(camera_offset,BS);
nodehilightbox = box;
}
}
/*
Roofs and Node boxes
*/
@ -416,16 +331,11 @@ void getPointedNode(Client *client, v3f player_position,
|| content_features(n).draw_type == CDT_SLABLIKE
|| content_features(n).draw_type == CDT_FLAGLIKE
) {
f32 distance = (npf - camera_position).getLength();
if (distance < mindistance) {
aabb3f box;
aabb3f nhbox(0.5*BS,0.5*BS,0.5*BS,-0.5*BS,-0.5*BS,-0.5*BS);
bool hit = false;
std::vector<NodeBox> boxes = content_features(n).getNodeBoxes(n);
for (std::vector<NodeBox>::iterator b = boxes.begin(); b != boxes.end(); b++) {
box = b->m_box;
if (nhbox.MinEdge.X > box.MinEdge.X)
nhbox.MinEdge.X = box.MinEdge.X;
if (nhbox.MinEdge.Y > box.MinEdge.Y)
@ -438,105 +348,18 @@ void getPointedNode(Client *client, v3f player_position,
nhbox.MaxEdge.Y = box.MaxEdge.Y;
if (nhbox.MaxEdge.Z < box.MaxEdge.Z)
nhbox.MaxEdge.Z = box.MaxEdge.Z;
box.MinEdge += npf;
box.MaxEdge += npf;
if (box.intersectsWithLine(shootline)) {
for(u16 i=0; i<6; i++) {
v3f dir_f = v3f(dirs[i].X,
dirs[i].Y, dirs[i].Z);
v3f centerpoint = npf + dir_f * BS/2;
f32 distance =
(centerpoint - camera_position).getLength();
if(distance < mindistance)
{
core::CMatrix4<f32> m;
m.buildRotateFromTo(v3f(0,0,1), dir_f);
// This is the back face
v3f corners[2] = {
v3f(BS/2, BS/2, BS/2),
v3f(-BS/2, -BS/2, BS/2+d)
};
for(u16 j=0; j<2; j++)
{
m.rotateVect(corners[j]);
corners[j] += npf;
}
core::aabbox3d<f32> facebox(corners[0]);
facebox.addInternalPoint(corners[1]);
if(facebox.intersectsWithLine(shootline))
{
nodefound = true;
nodepos = np;
content = n.getContent();
neighbourpos = np + dirs[i];
mindistance = distance;
hit = true;
}
} // if distance < mindistance
} // for dirs
}
}
if (hit) {
nhbox.MinEdge -= 0.002;
nhbox.MaxEdge += 0.002;
v3f nodepos_f = intToFloat(nodepos-camera_offset, BS);
nhbox.MinEdge += nodepos_f;
nhbox.MaxEdge += nodepos_f;
nodehilightbox = nhbox;
}
boxes.clear();
}
/*
Regular blocks
*/
}else{
for(u16 i=0; i<6; i++)
{
v3f dir_f = v3f(dirs[i].X,
dirs[i].Y, dirs[i].Z);
v3f centerpoint = npf + dir_f * BS/2;
f32 distance =
(centerpoint - camera_position).getLength();
//printf("%f %f - %d,%d,%d\n",distance, mindistance,x,y,z);
if(distance < mindistance)
{
core::CMatrix4<f32> m;
m.buildRotateFromTo(v3f(0,0,1), dir_f);
// This is the back face
v3f corners[2] = {
v3f(BS/2, BS/2, BS/2),
v3f(-BS/2, -BS/2, BS/2+d)
};
for(u16 j=0; j<2; j++)
{
m.rotateVect(corners[j]);
corners[j] += npf;
}
core::aabbox3d<f32> facebox(corners[0]);
facebox.addInternalPoint(corners[1]);
if(facebox.intersectsWithLine(shootline))
{
nodefound = true;
nodepos = np;
content = n.getContent();
neighbourpos = np + dirs[i];
mindistance = distance;
//nodehilightbox = facebox;
const float d = 0.502;
core::aabbox3d<f32> nodebox
(-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d);
@ -544,17 +367,7 @@ void getPointedNode(Client *client, v3f player_position,
nodebox.MinEdge += nodepos_f;
nodebox.MaxEdge += nodepos_f;
nodehilightbox = nodebox;
}
} // if distance < mindistance
} // for dirs
} // regular block
} // for coords
if (nodefound) {
client->setPointedNode(nodepos);
client->setPointedContent(content);
}else{
client->setPointedContent(CONTENT_IGNORE);
}
}
/*
@ -1569,10 +1382,44 @@ void the_game(
*/
f32 d = 4; // max. distance
core::line3d<f32> shootline(camera_position, camera_position + camera_direction * BS * (d+1));
core::line3d<f32> shootline(camera_position, camera_position + camera_direction * BS * d);
//which is closer: the active object or the block?
ClientActiveObject *selected_active_object = client.getSelectedActiveObject(d*BS, camera_position, shootline);
f32 active_object_distance=(d+10)*BS;
if(selected_active_object!=NULL)
active_object_distance=(selected_active_object->getPosition()-camera_position).getLength();
//search node
bool nodefound = false;
v3s16 nodepos;
v3s16 neighbourpos;
core::aabbox3d<f32> nodehilightbox;
content_t content=CONTENT_IGNORE;
//inventory test
InventoryItem *wield = (InventoryItem*)client.getLocalPlayer()->getWieldItem();
bool wield_is_hand = (wield == NULL);
bool wield_is_tool = (wield && wield->getContent()&CONTENT_TOOLITEM_MASK);
bool wield_is_craft = (wield && wield->getContent()&CONTENT_CRAFTITEM_MASK);
bool wield_is_material = (!wield_is_hand && !wield_is_tool && !wield_is_craft);
bool ignore_liquids=!wield || content_toolitem_features(wield->getContent()).liquids_pointable == false;
client.getEnv().getMap().raycast(
camera_direction, camera_position, d,
ignore_liquids,wield_is_material,
nodefound, nodepos, neighbourpos);
f32 node_distance=(d+10)*BS;
if(nodefound){
node_distance=(intToFloat(nodepos, BS)-camera_position).getLength();
}
if(active_object_distance<node_distance)
nodefound=false;
else
selected_active_object=NULL;
if(!nodefound){
if (nodepos_old != v3s16(-32768,-32768,-32768)) {
dig_time = 0.0;
nodepos_old = v3s16(-32768,-32768,-32768);
}
has_selected_node = false;
}
if(selected_active_object != NULL) {
has_selected_node = false;
client.setPointedContent(selected_active_object->getContent());
@ -1622,29 +1469,14 @@ void the_game(
infostream<<"Right-clicked object"<<std::endl;
client.clickActiveObject(1, selected_active_object->getId(), g_selected_item);
}
}else{ // selected_object == NULL
/*
Find out which node we are pointing at
*/
bool nodefound = false;
v3s16 nodepos;
v3s16 neighbourpos;
core::aabbox3d<f32> nodehilightbox;
getPointedNode(&client, player_position,
camera_direction, camera_position,
nodefound, shootline,
nodepos, neighbourpos, camera_offset,
nodehilightbox, d);
if (!nodefound) {
if (nodepos_old != v3s16(-32768,-32768,-32768)) {
dig_time = 0.0;
nodepos_old = v3s16(-32768,-32768,-32768);
}
has_selected_node = false;
}else{
}else{ // selected_active_object==NULL
if(nodefound){
content=client.getEnv().getMap().getNode(nodepos).getContent();
createHilightbox(&client.getEnv().getMap(),nodepos,
camera_offset,nodehilightbox);
client.setPointedNode(nodepos);
client.setPointedContent(content);
has_selected_node = true;
if (nodepos != selected_node_pos)
selected_node_crack = 0;
@ -1817,8 +1649,9 @@ void the_game(
}
nodepos_old = nodepos;
}else{
client.setPointedContent(CONTENT_IGNORE);
}
} // selected_object == NULL
}

View File

@ -42,6 +42,7 @@
#include "settings.h"
#include "log.h"
#include "profiler.h"
#include <matrix4.h>
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
@ -1732,6 +1733,227 @@ void Map::nodeMetadataStep(float dtime, core::map<v3s16, MapBlock*> &changed_blo
}
}
void Map::raycast(v3f camera_direction,
v3f camera_position, f32 d,
bool ignore_liquids, bool ignore_not_material_pointable,
bool &nodefound, v3s16 &nodepos, v3s16 &neighbourpos)
{
//initialize iteration
v3s16 cam_i = floatToInt(camera_position, BS);
//coordinates of current node
s16 x = cam_i.X;
s16 y = cam_i.Y;
s16 z = cam_i.Z;
s16 oldx = x;
s16 oldy = y;
s16 oldz = z;
v3f norm_pos = camera_position / BS;//divide by BS so one node is one unit
v3f norm_dir = v3f(camera_direction);
norm_dir.normalize();
norm_dir *= d;
//create shootline
core::line3d<f32> shootline(camera_position,
camera_position + norm_dir * BS);
f32 rx = 2; //ratio by which we multiply norm_dir to hit node (ratio x)
f32 rxs = 1;//incrementing rx by rxs gives the next node in x direction (ratio x step)
s16 xs = 1; //incrementing rx involves incrementing x by xs (x step)
if (norm_dir.X > 0) { //set rx,rxs and xs
rx = (floorf(norm_pos.X - 0.5) + 1.5 - norm_pos.X) / norm_dir.X;
rxs = 1 / norm_dir.X;
} else if (norm_dir.X < 0) {
rx = (floorf(norm_pos.X - 0.5) - norm_pos.X + 0.5) / norm_dir.X;
rxs = -1 / norm_dir.X;
xs = -1;
} //other directions
f32 rz = 2;
f32 rzs = 1;
s16 zs = 1;
if (norm_dir.Z > 0) {
rz = (floorf(norm_pos.Z - 0.5) + 1.5 - norm_pos.Z) / norm_dir.Z;
rzs = 1 / norm_dir.Z;
} else if (norm_dir.Z < 0) {
rz = (floorf(norm_pos.Z - 0.5) - norm_pos.Z + 0.5) / norm_dir.Z;
rzs = -1 / norm_dir.Z;
zs = -1;
}
f32 ry = 2;
f32 rys = 1;
s16 ys = 1;
if (norm_dir.Y > 0) {
ry = (floorf(norm_pos.Y - 0.5) + 1.5 - norm_pos.Y) / norm_dir.Y;
rys = 1 / norm_dir.Y;
} else if (norm_dir.Y < 0) {
ry = (floorf(norm_pos.Y - 0.5) - norm_pos.Y + 0.5) / norm_dir.Y;
rys = -1 / norm_dir.Y;
ys = -1;
}
//now iterate through nodes
do {
bool testable = true;
MapNode n;
try {
n = getNode(v3s16(x, y, z));
if (content_features(n.getContent()).pointable == false) {
if (content_features(n.getContent()).liquid_type
!= LIQUID_SOURCE)
testable = false;
if (ignore_liquids)
testable = false;
} else if (content_features(n.getContent()).material_pointable
== false && ignore_not_material_pointable) {
testable = false;
}
} catch (InvalidPositionException &e) {
testable = false;
}
//if node is valid
if (testable) {
v3s16 np(x, y, z);
v3f npf = intToFloat(np, BS);
f32 d = 0.01;
v3s16 dirs[6] = { v3s16(0, 0, 1), // back
v3s16(0, 1, 0), // top
v3s16(1, 0, 0), // right
v3s16(0, 0, -1), // front
v3s16(0, -1, 0), // bottom
v3s16(-1, 0, 0), // left
};
/*
Meta-objects
*/
if (n.getContent() == CONTENT_TORCH) {
v3s16 dir = unpackDir(n.param2);
v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
dir_f *= BS / 2 - BS / 6 - BS / 20;
v3f cpf = npf + dir_f;
core::aabbox3d<f32> box;
// bottom
if (dir == v3s16(0, -1, 0)) {
box = core::aabbox3d<f32>(npf - v3f(BS / 6, BS / 2, BS / 6),
npf + v3f(BS / 6, -BS / 2 + BS / 3 * 2, BS / 6));
}
// top
else if (dir == v3s16(0, 1, 0)) {
box = core::aabbox3d<f32>(
npf - v3f(BS / 6, -BS / 2 + BS / 3 * 2, BS / 6),
npf + v3f(BS / 6, BS / 2, BS / 6));
}
// side
else {
box = core::aabbox3d<f32>(cpf - v3f(BS / 6, BS / 3, BS / 6),
cpf + v3f(BS / 6, BS / 3, BS / 6));
}
if (box.intersectsWithLine(shootline)) {
nodefound = true;
nodepos = np;
neighbourpos = np;
}
} else if (n.getContent() == CONTENT_RAIL) {
float d = (float) BS / 8;
v3f vertices[4] = { v3f(BS / 2, -BS / 2 + d, -BS / 2), v3f(
-BS / 2, -BS / 2, BS / 2), };
for (s32 i = 0; i < 2; i++) {
vertices[i] += npf;
}
core::aabbox3d<f32> box;
box = core::aabbox3d<f32>(vertices[0]);
box.addInternalPoint(vertices[1]);
if (box.intersectsWithLine(shootline)) {
nodefound = true;
nodepos = np;
neighbourpos = np;
}
/*
Roofs and Node boxes
*/
} else if (content_features(n).draw_type == CDT_NODEBOX
|| content_features(n).draw_type == CDT_NODEBOX_META
|| content_features(n).draw_type == CDT_WIRELIKE
|| content_features(n).draw_type == CDT_3DWIRELIKE
|| content_features(n).draw_type == CDT_FENCELIKE
|| content_features(n).draw_type == CDT_WALLLIKE
|| content_features(n).draw_type == CDT_STAIRLIKE
|| content_features(n).draw_type == CDT_SLABLIKE
|| content_features(n).draw_type == CDT_FLAGLIKE) {
aabb3f box;
std::vector<NodeBox> boxes = content_features(n).getNodeBoxes(
n);
for (std::vector<NodeBox>::iterator b = boxes.begin();
b != boxes.end(); b++) {
box = b->m_box;
box.MinEdge += npf;
box.MaxEdge += npf;
if (box.intersectsWithLine(shootline)) {
for (u16 i = 0; i < 6; i++) {
v3f dir_f = v3f(dirs[i].X, dirs[i].Y, dirs[i].Z);
core::CMatrix4<f32> m;
m.buildRotateFromTo(v3f(0, 0, 1), dir_f);
// This is the back face
v3f corners[2] = { v3f(BS / 2, BS / 2, BS / 2), v3f(
-BS / 2, -BS / 2, BS / 2 + d) };
for (u16 j = 0; j < 2; j++) {
m.rotateVect(corners[j]);
corners[j] += npf;
}
core::aabbox3d<f32> facebox(corners[0]);
facebox.addInternalPoint(corners[1]);
if (facebox.intersectsWithLine(shootline)) {
nodefound = true;
nodepos = np;
neighbourpos = np + dirs[i];
}
} // for dirs
}
}
boxes.clear();
/*
Regular blocks
*/
} else {
nodefound = true;
nodepos = np;
neighbourpos.set(oldx, oldy, oldz);
const float d = 0.502;
core::aabbox3d<f32> nodebox(-BS * d, -BS * d, -BS * d, BS * d,
BS * d, BS * d);
} // regular block
} //testable
//if found node, it is the closest
if (nodefound)
break;
//check if we can step forward
if (rx > 1 && ry > 1 && rz > 1)
break;
//step to next node
oldx = x;
oldy = y;
oldz = z;
if ((rx < ry) && (rx < rz)) {
rx += rxs;
x += xs;
} else if (ry < rz) {
ry += rys;
y += ys;
} else {
rz += rzs;
z += zs;
}
} while (true);
}
/*
ServerMap
*/

View File

@ -293,6 +293,24 @@ public:
*/
core::map<v2s16, MapSector*> *getSectorsPtr(){return &m_sectors;}
//!Finds the nearest node from a position in a given direction.
/**
*\param camera_direction: direction of ray
*\param camera_position: starting point of ray
*\param d: maximal distance of the search
*\param ignore_liquids: if true, liquids are invisible
*\param ignore_not_material_pointable: if true, nodes which are not
* material_pointable will be invisible
*\param nodefound: true if a node was found
*\param nodepos: the integer coordinates of found node
*\param neighbourpos: the node in front of the found node
*/
void raycast(v3f camera_direction,
v3f camera_position, f32 d,
bool ignore_liquids, bool ignore_not_material_pointable,
bool &nodefound, v3s16 &nodepos, v3s16 &neighbourpos);
/*
Variables
*/