OpenMiner/source/client/math/BlockCursorRaycast.cpp
2020-03-17 22:22:22 +01:00

169 lines
6.5 KiB
C++

/*
* =====================================================================================
*
* OpenMiner
*
* Copyright (C) 2018-2020 Unarelith, Quentin Bazin <openminer@unarelith.net>
* Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md)
*
* This file is part of OpenMiner.
*
* OpenMiner is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* OpenMiner is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with OpenMiner; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* =====================================================================================
*/
#include "BlockCursorRaycast.hpp"
#include "BlockGeometry.hpp"
#include "ClientWorld.hpp"
#include "Registry.hpp"
static inline glm::dvec3 intersectAxisPlane(const Axis axis, const double coord,
const glm::dvec3 &position,
const glm::dvec3 &direction)
{
const double t = axis == AXIS_X ? (coord - position.x) / direction.x
: axis == AXIS_Y ? (coord - position.y) / direction.y
: (coord - position.z) / direction.z;
glm::dvec3 result{
axis == AXIS_X ? coord : position.x + t * direction.x,
axis == AXIS_Y ? coord : position.y + t * direction.y,
axis == AXIS_Z ? coord : position.z + t * direction.z
};
return result;
}
static inline void recordHit(const glm::dvec3 &position,
const glm::dvec3 &isect,
const Axis axis, const bool neg,
const s32f nx, const s32f ny, const s32f nz,
s32f &bestX, s32f &bestY, s32f &bestZ,
s8f &bestFace, double &bestDepth,
bool &hit)
{
// Check if we have a record
double depth = glm::distance(position, isect);
if (bestFace == -1 || depth < bestDepth) {
// Yep, register it
bestFace = axis == AXIS_X ? (neg ? 0 : 3)
: axis == AXIS_Y ? (neg ? 1 : 4)
: (neg ? 2 : 5);
bestDepth = depth;
bestX = nx;
bestY = ny;
bestZ = nz;
}
hit = true;
}
void BlockCursorRaycast::rayCastToAxis(const Axis axis, const glm::dvec3 &position,
const glm::dvec3 &lookAt, const double maxReach,
s32f &bestX, s32f &bestY, s32f &bestZ,
s8f &bestFace, double &bestDepth,
const ClientWorld &world)
{
glm::dvec3 isect;
double posCoord, lookAtCoord;
// The line that starts at 'position' and goes in the direction
// of 'lookAt' with length 'maxReach' crosses several nodes in
// the X direction at integer positions. Determine the first and
// last such positions.
switch (axis) {
case AXIS_X:
posCoord = position.x;
lookAtCoord = lookAt.x;
break;
case AXIS_Y:
posCoord = position.y;
lookAtCoord = lookAt.y;
break;
default:
posCoord = position.z;
lookAtCoord = lookAt.z;
break;
}
s32f firstNodeRow = lookAtCoord < 0. ? s32f(floor(posCoord)) : s32f(ceil(posCoord)) - 1;
s32f lastNodeRow = s32f(floor(posCoord + lookAtCoord * maxReach));
int_fast8_t dir = (lookAtCoord > 0.) - (lookAtCoord < 0.);
if (!dir) {
// Can't cross any planes if it doesn't change in this axis
return;
}
for (s32f nodeRow = firstNodeRow + dir;
dir > 0 ? (nodeRow <= lastNodeRow) : (nodeRow >= lastNodeRow); nodeRow += dir)
{
isect = intersectAxisPlane(axis, double(nodeRow + (dir < 0)), position, lookAt);
s32f nx, ny, nz;
nx = axis == AXIS_X ? nodeRow : floor(isect.x);
ny = axis == AXIS_Y ? nodeRow : floor(isect.y);
nz = axis == AXIS_Z ? nodeRow : floor(isect.z);
u32 blockID = world.getBlock(nx, ny, nz);
const Block &block = Registry::getInstance().getBlock(blockID);
u8f orientation = block.isRotatable() ? world.getData(nx, ny, nz) & 0x1F : 0;
const gk::FloatBox &boundingBox = block.boundingBox();
glm::vec3 localCorner1{boundingBox.x, boundingBox.y, boundingBox.z};
glm::vec3 localCorner2{boundingBox.sizeX, boundingBox.sizeY, boundingBox.sizeZ};
localCorner2 += localCorner1;
if (orientation) {
const glm::vec3 half{0.5, 0.5, 0.5};
localCorner1 = BlockGeometry::orientMatrices[orientation] * (localCorner1 - half) + half;
localCorner2 = BlockGeometry::orientMatrices[orientation] * (localCorner2 - half) + half;
if (localCorner2.x < localCorner1.x) std::swap(localCorner1.x, localCorner2.x);
if (localCorner2.y < localCorner1.y) std::swap(localCorner1.y, localCorner2.y);
if (localCorner2.z < localCorner1.z) std::swap(localCorner1.z, localCorner2.z);
}
if (blockID && block.drawType() != BlockDrawType::Liquid) {
// Check bounding box; this should loop over all selection boxes
// when they are implemented
const glm::dvec3 cubePos{double(nx), double(ny), double(nz)};
glm::dvec3 corner1 = glm::dvec3(localCorner1) + cubePos;
glm::dvec3 corner2 = glm::dvec3(localCorner2) + cubePos;
bool hit = false;
// Check if we hit any of the sides of the inner box
isect = intersectAxisPlane(AXIS_X, (lookAt.x < 0. ? corner2.x : corner1.x), position, lookAt);
if (corner1.y <= isect.y && isect.y <= corner2.y
&& corner1.z <= isect.z && isect.z <= corner2.z)
recordHit(position, isect, AXIS_X, lookAt.x < 0., nx, ny, nz, bestX, bestY, bestZ, bestFace, bestDepth, hit);
isect = intersectAxisPlane(AXIS_Y, (lookAt.y < 0. ? corner2.y : corner1.y), position, lookAt);
if (corner1.x <= isect.x && isect.x <= corner2.x
&& corner1.z <= isect.z && isect.z <= corner2.z)
recordHit(position, isect, AXIS_Y, lookAt.y < 0., nx, ny, nz, bestX, bestY, bestZ, bestFace, bestDepth, hit);
isect = intersectAxisPlane(AXIS_Z, (lookAt.z < 0. ? corner2.z : corner1.z), position, lookAt);
if (corner1.x <= isect.x && isect.x <= corner2.x
&& corner1.y <= isect.y && isect.y <= corner2.y)
recordHit(position, isect, AXIS_Z, lookAt.z < 0., nx, ny, nz, bestX, bestY, bestZ, bestFace, bestDepth, hit);
if (hit)
break; // no need to check deeper
}
}
}