summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/content_cao.cpp112
-rw-r--r--src/client/content_cao.h15
-rw-r--r--src/irrlichttypes_extrabloated.h1
-rw-r--r--src/network/networkprotocol.h1
-rw-r--r--src/unittest/test_utilities.cpp114
-rw-r--r--src/util/numeric.cpp35
-rw-r--r--src/util/numeric.h15
7 files changed, 247 insertions, 46 deletions
diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp
index 116a2e53b..6112edaff 100644
--- a/src/client/content_cao.cpp
+++ b/src/client/content_cao.cpp
@@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <IMeshManipulator.h>
#include <IAnimatedMeshSceneNode.h>
#include "content_cao.h"
-#include "util/numeric.h" // For IntervalLimiter
+#include "util/numeric.h" // For IntervalLimiter & setPitchYawRoll
#include "util/serialize.h"
#include "util/basic_macros.h"
#include "client/sound.h"
@@ -365,7 +365,7 @@ void GenericCAO::processInitData(const std::string &data)
return;
}
- // PROTOCOL_VERSION >= 37
+ // PROTOCOL_VERSION >= 37
m_name = deSerializeString(is);
m_is_player = readU8(is);
m_id = readU16(is);
@@ -402,10 +402,9 @@ bool GenericCAO::getSelectionBox(aabb3f *toset) const
v3f GenericCAO::getPosition()
{
- if (getParent() != NULL) {
- scene::ISceneNode *node = getSceneNode();
- if (node)
- return node->getAbsolutePosition();
+ if (getParent() != nullptr) {
+ if (m_matrixnode)
+ return m_matrixnode->getAbsolutePosition();
return m_position;
}
@@ -486,7 +485,7 @@ void GenericCAO::removeFromScene(bool permanent)
LocalPlayer* player = m_env->getLocalPlayer();
if (this == player->parent) {
- player->parent = NULL;
+ player->parent = nullptr;
player->isAttached = false;
}
}
@@ -494,24 +493,30 @@ void GenericCAO::removeFromScene(bool permanent)
if (m_meshnode) {
m_meshnode->remove();
m_meshnode->drop();
- m_meshnode = NULL;
+ m_meshnode = nullptr;
} else if (m_animated_meshnode) {
m_animated_meshnode->remove();
m_animated_meshnode->drop();
- m_animated_meshnode = NULL;
+ m_animated_meshnode = nullptr;
} else if (m_wield_meshnode) {
m_wield_meshnode->remove();
m_wield_meshnode->drop();
- m_wield_meshnode = NULL;
+ m_wield_meshnode = nullptr;
} else if (m_spritenode) {
m_spritenode->remove();
m_spritenode->drop();
- m_spritenode = NULL;
+ m_spritenode = nullptr;
+ }
+
+ if (m_matrixnode) {
+ m_matrixnode->remove();
+ m_matrixnode->drop();
+ m_matrixnode = nullptr;
}
if (m_nametag) {
m_client->getCamera()->removeNametag(m_nametag);
- m_nametag = NULL;
+ m_nametag = nullptr;
}
}
@@ -534,8 +539,11 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
if (m_prop.visual == "sprite") {
infostream<<"GenericCAO::addToScene(): single_sprite"<<std::endl;
+ m_matrixnode = RenderingEngine::get_scene_manager()->
+ addDummyTransformationSceneNode();
+ m_matrixnode->grab();
m_spritenode = RenderingEngine::get_scene_manager()->addBillboardSceneNode(
- NULL, v2f(1, 1), v3f(0,0,0), -1);
+ m_matrixnode, v2f(1, 1), v3f(0,0,0), -1);
m_spritenode->grab();
m_spritenode->setMaterialTexture(0,
tsrc->getTextureForMesh("unknown_node.png"));
@@ -608,17 +616,24 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
mesh->addMeshBuffer(buf);
buf->drop();
}
- m_meshnode = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL);
+ m_matrixnode = RenderingEngine::get_scene_manager()->
+ addDummyTransformationSceneNode();
+ m_matrixnode->grab();
+ m_meshnode = RenderingEngine::get_scene_manager()->
+ addMeshSceneNode(mesh, m_matrixnode);
m_meshnode->grab();
mesh->drop();
// Set it to use the materials of the meshbuffers directly.
// This is needed for changing the texture in the future
m_meshnode->setReadOnlyMaterials(true);
- }
- else if(m_prop.visual == "cube") {
+ } else if (m_prop.visual == "cube") {
infostream<<"GenericCAO::addToScene(): cube"<<std::endl;
scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS));
- m_meshnode = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL);
+ m_matrixnode = RenderingEngine::get_scene_manager()->
+ addDummyTransformationSceneNode(nullptr);
+ m_matrixnode->grab();
+ m_meshnode = RenderingEngine::get_scene_manager()->
+ addMeshSceneNode(mesh, m_matrixnode);
m_meshnode->grab();
mesh->drop();
@@ -630,14 +645,15 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
m_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
m_meshnode->setMaterialType(material_type);
m_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true);
- }
- else if(m_prop.visual == "mesh") {
+ } else if (m_prop.visual == "mesh") {
infostream<<"GenericCAO::addToScene(): mesh"<<std::endl;
scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true);
- if(mesh)
- {
+ if (mesh) {
+ m_matrixnode = RenderingEngine::get_scene_manager()->
+ addDummyTransformationSceneNode(nullptr);
+ m_matrixnode->grab();
m_animated_meshnode = RenderingEngine::get_scene_manager()->
- addAnimatedMeshSceneNode(mesh, NULL);
+ addAnimatedMeshSceneNode(mesh, m_matrixnode);
m_animated_meshnode->grab();
mesh->drop(); // The scene node took hold of it
m_animated_meshnode->animateJoints(); // Needed for some animations
@@ -655,8 +671,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
m_animated_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true);
m_animated_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING,
m_prop.backface_culling);
- }
- else
+ } else
errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
} else if (m_prop.visual == "wielditem" || m_prop.visual == "item") {
ItemStack item;
@@ -674,8 +689,12 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
infostream << "serialized form: " << m_prop.wield_item << std::endl;
item.deSerialize(m_prop.wield_item, m_client->idef());
}
+ m_matrixnode = RenderingEngine::get_scene_manager()->
+ addDummyTransformationSceneNode(nullptr);
+ m_matrixnode->grab();
m_wield_meshnode = new WieldMeshSceneNode(
RenderingEngine::get_scene_manager(), -1);
+ m_wield_meshnode->setParent(m_matrixnode);
m_wield_meshnode->setItem(item, m_client,
(m_prop.visual == "wielditem"));
@@ -763,10 +782,12 @@ void GenericCAO::updateNodePos()
if (node) {
v3s16 camera_offset = m_env->getCameraOffset();
- node->setPosition(pos_translator.val_current - intToFloat(camera_offset, BS));
+ v3f pos = pos_translator.val_current -
+ intToFloat(camera_offset, BS);
+ getPosRotMatrix().setTranslation(pos);
if (node != m_spritenode) { // rotate if not a sprite
v3f rot = m_is_local_player ? -m_rotation : -rot_translator.val_current;
- node->setRotation(rot);
+ setPitchYawRoll(getPosRotMatrix(), rot);
}
}
}
@@ -858,8 +879,10 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
ClientActiveObject *obj = m_env->getActiveObject(*ci);
if (obj) {
scene::ISceneNode *child_node = obj->getSceneNode();
+ // The node's parent is always an IDummyTraformationSceneNode,
+ // so we need to reparent that one instead.
if (child_node)
- child_node->setParent(m_smgr->getRootSceneNode());
+ child_node->getParent()->setParent(m_smgr->getRootSceneNode());
}
++ci;
}
@@ -1266,16 +1289,13 @@ void GenericCAO::updateBonePosition()
void GenericCAO::updateAttachments()
{
-
- if (!getParent()) { // Detach or don't attach
- scene::ISceneNode *node = getSceneNode();
- if (node) {
- v3f old_position = node->getAbsolutePosition();
- v3f old_rotation = node->getRotation();
- node->setParent(m_smgr->getRootSceneNode());
- node->setPosition(old_position);
- node->setRotation(old_rotation);
- node->updateAbsolutePosition();
+ ClientActiveObject *parent = getParent();
+ if (!parent) { // Detach or don't attach
+ if (m_matrixnode) {
+ v3f old_pos = m_matrixnode->getAbsolutePosition();
+ m_matrixnode->setParent(m_smgr->getRootSceneNode());
+ getPosRotMatrix().setTranslation(old_pos);
+ m_matrixnode->updateAbsolutePosition();
}
if (m_is_local_player) {
LocalPlayer *player = m_env->getLocalPlayer();
@@ -1284,20 +1304,20 @@ void GenericCAO::updateAttachments()
}
else // Attach
{
- scene::ISceneNode *my_node = getSceneNode();
-
- scene::ISceneNode *parent_node = getParent()->getSceneNode();
+ scene::ISceneNode *parent_node = parent->getSceneNode();
scene::IAnimatedMeshSceneNode *parent_animated_mesh_node =
- getParent()->getAnimatedMeshSceneNode();
+ parent->getAnimatedMeshSceneNode();
if (parent_animated_mesh_node && !m_attachment_bone.empty()) {
parent_node = parent_animated_mesh_node->getJointNode(m_attachment_bone.c_str());
}
- if (my_node && parent_node) {
- my_node->setParent(parent_node);
- my_node->setPosition(m_attachment_position);
- my_node->setRotation(m_attachment_rotation);
- my_node->updateAbsolutePosition();
+ if (m_matrixnode && parent_node) {
+ m_matrixnode->setParent(parent_node);
+ getPosRotMatrix().setTranslation(m_attachment_position);
+ //setPitchYawRoll(getPosRotMatrix(), m_attachment_rotation);
+ // use Irrlicht eulers instead
+ getPosRotMatrix().setRotationDegrees(m_attachment_rotation);
+ m_matrixnode->updateAbsolutePosition();
}
if (m_is_local_player) {
LocalPlayer *player = m_env->getLocalPlayer();
diff --git a/src/client/content_cao.h b/src/client/content_cao.h
index 98932137e..4627800ee 100644
--- a/src/client/content_cao.h
+++ b/src/client/content_cao.h
@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "object_properties.h"
#include "itemgroup.h"
#include "constants.h"
+#include <cassert>
class Camera;
class Client;
@@ -81,6 +82,7 @@ private:
scene::IAnimatedMeshSceneNode *m_animated_meshnode = nullptr;
WieldMeshSceneNode *m_wield_meshnode = nullptr;
scene::IBillboardSceneNode *m_spritenode = nullptr;
+ scene::IDummyTransformationSceneNode *m_matrixnode = nullptr;
Nametag *m_nametag = nullptr;
v3f m_position = v3f(0.0f, 10.0f * BS, 0);
v3f m_velocity;
@@ -163,6 +165,19 @@ public:
scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode();
+ // m_matrixnode controls the position and rotation of the child node
+ // for all scene nodes, as a workaround for an Irrlicht problem with
+ // rotations. The child node's position can't be used because it's
+ // rotated, and must remain as 0.
+ // Note that m_matrixnode.setPosition() shouldn't be called. Use
+ // m_matrixnode->getRelativeTransformationMatrix().setTranslation()
+ // instead (aka getPosRotMatrix().setTranslation()).
+ inline core::matrix4 &getPosRotMatrix()
+ {
+ assert(m_matrixnode);
+ return m_matrixnode->getRelativeTransformationMatrix();
+ }
+
inline f32 getStepHeight() const
{
return m_prop.stepheight;
diff --git a/src/irrlichttypes_extrabloated.h b/src/irrlichttypes_extrabloated.h
index 83c4ca01b..b03ba7955 100644
--- a/src/irrlichttypes_extrabloated.h
+++ b/src/irrlichttypes_extrabloated.h
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <IImage.h>
#include <IrrlichtDevice.h>
#include <IMeshSceneNode.h>
+#include <IDummyTransformationSceneNode.h>
#include <SMesh.h>
#include <ISceneManager.h>
#include <IMeshBuffer.h>
diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h
index 703e96056..85436068f 100644
--- a/src/network/networkprotocol.h
+++ b/src/network/networkprotocol.h
@@ -193,6 +193,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Add TOCLIENT_NODEMETA_CHANGED
New network float format
ContentFeatures version 13
+ Add full Euler rotations instead of just yaw
*/
#define LATEST_PROTOCOL_VERSION 37
diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp
index 234f622d5..8e8958d18 100644
--- a/src/unittest/test_utilities.cpp
+++ b/src/unittest/test_utilities.cpp
@@ -53,6 +53,7 @@ public:
void testIsPowerOfTwo();
void testMyround();
void testStringJoin();
+ void testEulerConversion();
};
static TestUtilities g_test_instance;
@@ -82,6 +83,7 @@ void TestUtilities::runTests(IGameDef *gamedef)
TEST(testIsPowerOfTwo);
TEST(testMyround);
TEST(testStringJoin);
+ TEST(testEulerConversion);
}
////////////////////////////////////////////////////////////////////////////////
@@ -394,3 +396,115 @@ void TestUtilities::testStringJoin()
UASSERT(str_join(input, " and ") == "one and two and three");
}
+
+static bool within(const f32 value1, const f32 value2, const f32 precision)
+{
+ return std::fabs(value1 - value2) <= precision;
+}
+
+static bool within(const v3f &v1, const v3f &v2, const f32 precision)
+{
+ return within(v1.X, v2.X, precision) && within(v1.Y, v2.Y, precision)
+ && within(v1.Z, v2.Z, precision);
+}
+
+static bool within(const core::matrix4 &m1, const core::matrix4 &m2,
+ const f32 precision)
+{
+ const f32 *M1 = m1.pointer();
+ const f32 *M2 = m2.pointer();
+ for (int i = 0; i < 16; i++)
+ if (! within(M1[i], M2[i], precision))
+ return false;
+ return true;
+}
+
+static bool roundTripsDeg(const v3f &v, const f32 precision)
+{
+ core::matrix4 m;
+ setPitchYawRoll(m, v);
+ return within(v, getPitchYawRoll(m), precision);
+}
+
+void TestUtilities::testEulerConversion()
+{
+ // This test may fail on non-IEEE systems.
+ // Low tolerance is 4 ulp(1.0) for binary floats with 24 bit mantissa.
+ // (ulp = unit in the last place; ulp(1.0) = 2^-23).
+ const f32 tolL = 4.76837158203125e-7f;
+ // High tolerance is 2 ulp(180.0), needed for numbers in degrees.
+ // ulp(180.0) = 2^-16
+ const f32 tolH = 3.0517578125e-5f;
+ v3f v1, v2;
+ core::matrix4 m1, m2;
+ const f32 *M1 = m1.pointer();
+ const f32 *M2 = m2.pointer();
+
+ // Check that the radians version and the degrees version
+ // produce the same results. Check also that the conversion
+ // works both ways for these values.
+ v1 = v3f(M_PI/3.0, M_PI/5.0, M_PI/4.0);
+ v2 = v3f(60.0f, 36.0f, 45.0f);
+ setPitchYawRollRad(m1, v1);
+ setPitchYawRoll(m2, v2);
+ UASSERT(within(m1, m2, tolL));
+ UASSERT(within(getPitchYawRollRad(m1), v1, tolL));
+ UASSERT(within(getPitchYawRoll(m2), v2, tolH));
+
+ // Check the rotation matrix produced.
+ UASSERT(within(M1[0], 0.932004869f, tolL));
+ UASSERT(within(M1[1], 0.353553385f, tolL));
+ UASSERT(within(M1[2], 0.0797927827f, tolL));
+ UASSERT(within(M1[4], -0.21211791f, tolL));
+ UASSERT(within(M1[5], 0.353553355f, tolL));
+ UASSERT(within(M1[6], 0.911046684f, tolL));
+ UASSERT(within(M1[8], 0.293892622f, tolL));
+ UASSERT(within(M1[9], -0.866025448f, tolL));
+ UASSERT(within(M1[10], 0.404508471f, tolL));
+
+ // Check that the matrix is still homogeneous with no translation
+ UASSERT(M1[3] == 0.0f);
+ UASSERT(M1[7] == 0.0f);
+ UASSERT(M1[11] == 0.0f);
+ UASSERT(M1[12] == 0.0f);
+ UASSERT(M1[13] == 0.0f);
+ UASSERT(M1[14] == 0.0f);
+ UASSERT(M1[15] == 1.0f);
+ UASSERT(M2[3] == 0.0f);
+ UASSERT(M2[7] == 0.0f);
+ UASSERT(M2[11] == 0.0f);
+ UASSERT(M2[12] == 0.0f);
+ UASSERT(M2[13] == 0.0f);
+ UASSERT(M2[14] == 0.0f);
+ UASSERT(M2[15] == 1.0f);
+
+ // Compare to Irrlicht's results. To be comparable, the
+ // angles must come in a different order and the matrix
+ // elements to compare are different too.
+ m2.setRotationRadians(v3f(v1.Z, v1.X, v1.Y));
+ UASSERT(within(M1[0], M2[5], tolL));
+ UASSERT(within(M1[1], M2[6], tolL));
+ UASSERT(within(M1[2], M2[4], tolL));
+
+ UASSERT(within(M1[4], M2[9], tolL));
+ UASSERT(within(M1[5], M2[10], tolL));
+ UASSERT(within(M1[6], M2[8], tolL));
+
+ UASSERT(within(M1[8], M2[1], tolL));
+ UASSERT(within(M1[9], M2[2], tolL));
+ UASSERT(within(M1[10], M2[0], tolL));
+
+ // Check that Eulers that produce near gimbal-lock still round-trip
+ UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 0.f), tolH));
+ UASSERT(roundTripsDeg(v3f(89.9999f, 0.f, 19.f), tolH));
+ UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 19.f), tolH));
+
+ // Check that Eulers at an angle > 90 degrees may not round-trip...
+ v1 = v3f(90.00001f, 1.f, 1.f);
+ setPitchYawRoll(m1, v1);
+ v2 = getPitchYawRoll(m1);
+ //UASSERT(within(v1, v2, tolL)); // this is typically false
+ // ... however the rotation matrix is the same for both
+ setPitchYawRoll(m2, v2);
+ UASSERT(within(m1, m2, tolL));
+}
diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp
index a120e3207..bd298e94e 100644
--- a/src/util/numeric.cpp
+++ b/src/util/numeric.cpp
@@ -174,3 +174,38 @@ s16 adjustDist(s16 dist, float zoom_fov)
return std::round(dist * std::cbrt((1.0f - std::cos(threshold_fov)) /
(1.0f - std::cos(zoom_fov / 2.0f))));
}
+
+void setPitchYawRollRad(core::matrix4 &m, const v3f &rot)
+{
+ f64 a1 = rot.Z, a2 = rot.X, a3 = rot.Y;
+ f64 c1 = cos(a1), s1 = sin(a1);
+ f64 c2 = cos(a2), s2 = sin(a2);
+ f64 c3 = cos(a3), s3 = sin(a3);
+ f32 *M = m.pointer();
+
+ M[0] = s1 * s2 * s3 + c1 * c3;
+ M[1] = s1 * c2;
+ M[2] = s1 * s2 * c3 - c1 * s3;
+
+ M[4] = c1 * s2 * s3 - s1 * c3;
+ M[5] = c1 * c2;
+ M[6] = c1 * s2 * c3 + s1 * s3;
+
+ M[8] = c2 * s3;
+ M[9] = -s2;
+ M[10] = c2 * c3;
+}
+
+v3f getPitchYawRollRad(const core::matrix4 &m)
+{
+ const f32 *M = m.pointer();
+
+ f64 a1 = atan2(M[1], M[5]);
+ f64 c2 = sqrt(M[10]*M[10] + M[8]*M[8]);
+ f32 a2 = atan2f(-M[9], c2);
+ f64 c1 = cos(a1);
+ f64 s1 = sin(a1);
+ f32 a3 = atan2f(s1*M[6] - c1*M[2], c1*M[0] - s1*M[4]);
+
+ return v3f(a2, a3, a1);
+}
diff --git a/src/util/numeric.h b/src/util/numeric.h
index bfdff84a0..6f82a18c1 100644
--- a/src/util/numeric.h
+++ b/src/util/numeric.h
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irr_v2d.h"
#include "irr_v3d.h"
#include "irr_aabb3d.h"
+#include <matrix4.h>
#define rangelim(d, min, max) ((d) < (min) ? (min) : ((d) > (max) ? (max) : (d)))
#define myfloor(x) ((x) < 0.0 ? (int)(x) - 1 : (int)(x))
@@ -417,3 +418,17 @@ inline void wrappedApproachShortest(T &current, const T target, const T stepsize
current = target;
}
}
+
+void setPitchYawRollRad(core::matrix4 &m, const v3f &rot);
+
+inline void setPitchYawRoll(core::matrix4 &m, const v3f &rot)
+{
+ setPitchYawRollRad(m, rot * core::DEGTORAD64);
+}
+
+v3f getPitchYawRollRad(const core::matrix4 &m);
+
+inline v3f getPitchYawRoll(const core::matrix4 &m)
+{
+ return getPitchYawRollRad(m) * core::RADTODEG64;
+}