Skip to content

Commit

Permalink
Extend bone override capabilities (#12388)
Browse files Browse the repository at this point in the history
  • Loading branch information
appgurueu committed Dec 20, 2023
1 parent 61d0f61 commit 0d61598
Show file tree
Hide file tree
Showing 12 changed files with 374 additions and 79 deletions.
37 changes: 26 additions & 11 deletions doc/lua_api.md
Expand Up @@ -7556,17 +7556,32 @@ child will follow movement and rotation of that bone.
object.
* `set_detach()`: Detaches object. No-op if object was not attached.
* `set_bone_position([bone, position, rotation])`
* `bone`: string. Default is `""`, the root bone
* `position`: `{x=num, y=num, z=num}`, relative, `default {x=0, y=0, z=0}`
* `rotation`: `{x=num, y=num, z=num}`, default `{x=0, y=0, z=0}`
* `get_bone_position(bone)`:
* returns bone parameters previously set by `set_bone_position`
* returns `position, rotation` of the specified bone (as vectors)
* note: position is relative to the object
* `set_properties(object property table)`:
* set a number of object properties in the given table
* only properties listed in the table will be changed
* see the 'Object properties' section for details
* Shorthand for `set_bone_override(bone, {position = position, rotation = rotation:apply(math.rad)})` using absolute values.
* **Note:** Rotation is in degrees, not radians.
* **Deprecated:** Use `set_bone_override` instead.
* `get_bone_position(bone)`: returns the previously set position and rotation of the bone
* Shorthand for `get_bone_override(bone).position.vec, get_bone_override(bone).rotation.vec:apply(math.deg)`.
* **Note:** Returned rotation is in degrees, not radians.
* **Deprecated:** Use `get_bone_override` instead.
* `set_bone_override(bone, override)`
* `bone`: string
* `override`: `{ position = property, rotation = property, scale = property }` or `nil`
* `property`: `{ vec = vector, interpolation = 0, absolute = false}` or `nil`;
* `vec` is in the same coordinate system as the model, and in degrees for rotation
* `property = nil` is equivalent to no override on that property
* `absolute`: If set to `false`, the override will be relative to the animated property:
* Transposition in the case of `position`;
* Composition in the case of `rotation`;
* Multiplication in the case of `scale`
* `interpolation`: Old and new values are interpolated over this timeframe (in seconds)
* `override = nil` (including omission) is shorthand for `override = {}` which clears the override
* **Note:** Unlike `set_bone_position`, the rotation is in radians, not degrees.
* Compatibility note: Clients prior to 5.9.0 only support absolute position and rotation.
All values are treated as absolute and are set immediately (no interpolation).
* `get_bone_override(bone)`: returns `override` in the above format
* **Note:** Unlike `get_bone_position`, the returned rotation is in radians, not degrees.
* `get_bone_overrides()`: returns all bone overrides as table `{[bonename] = override, ...}`
* `set_properties(object property table)`
* `get_properties()`: returns a table of all object properties
* `is_player()`: returns true for players, false otherwise
* `get_nametag_attributes()`
Expand Down
74 changes: 74 additions & 0 deletions src/activeobject.h
Expand Up @@ -21,7 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,

#include "irr_aabb3d.h"
#include "irr_v3d.h"
#include <quaternion.h>
#include <string>
#include <unordered_map>


enum ActiveObjectType {
Expand Down Expand Up @@ -72,6 +74,78 @@ enum ActiveObjectCommand {
AO_CMD_SET_ANIMATION_SPEED
};

struct BoneOverride
{
struct PositionProperty
{
v3f previous;
v3f vector;
bool absolute = false;
f32 interp_timer = 0;
} position;

v3f getPosition(v3f anim_pos) const {
f32 progress = dtime_passed / position.interp_timer;
if (progress > 1.0f || position.interp_timer == 0.0f)
progress = 1.0f;
return position.vector.getInterpolated(position.previous, progress)
+ (position.absolute ? v3f() : anim_pos);
}

struct RotationProperty
{
core::quaternion previous;
core::quaternion next;
bool absolute = false;
f32 interp_timer = 0;
} rotation;

v3f getRotationEulerDeg(v3f anim_rot_euler) const {
core::quaternion rot;

f32 progress = dtime_passed / rotation.interp_timer;
if (progress > 1.0f || rotation.interp_timer == 0.0f)
progress = 1.0f;
rot.slerp(rotation.previous, rotation.next, progress);
if (!rotation.absolute) {
core::quaternion anim_rot(anim_rot_euler * core::DEGTORAD);
rot = rot * anim_rot; // first rotate by anim. bone rot., then rot.
}

v3f rot_euler;
rot.toEuler(rot_euler);
return rot_euler * core::RADTODEG;
}

struct ScaleProperty
{
v3f previous;
v3f vector{1, 1, 1};
bool absolute = false;
f32 interp_timer = 0;
} scale;

v3f getScale(v3f anim_scale) const {
f32 progress = dtime_passed / scale.interp_timer;
if (progress > 1.0f || scale.interp_timer == 0.0f)
progress = 1.0f;
return scale.vector.getInterpolated(scale.previous, progress)
* (scale.absolute ? v3f(1) : anim_scale);
}

f32 dtime_passed = 0;

bool isIdentity() const
{
return !position.absolute && position.vector == v3f()
&& !rotation.absolute && rotation.next == core::quaternion()
&& !scale.absolute && scale.vector == v3f(1);
}
};

typedef std::unordered_map<std::string, BoneOverride> BoneOverrideMap;


/*
Parent class for ServerActiveObject and ClientActiveObject
*/
Expand Down
75 changes: 60 additions & 15 deletions src/client/content_cao.cpp
Expand Up @@ -46,6 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cmath>
#include "client/shader.h"
#include "client/minimap.h"
#include <quaternion.h>

class Settings;
struct ToolCapabilities;
Expand Down Expand Up @@ -828,7 +829,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
updateMarker();
updateNodePos();
updateAnimation();
updateBonePosition();
updateBones(.0f);
updateAttachments();
setNodeLight(m_last_light);
updateMeshCulling();
Expand Down Expand Up @@ -1246,7 +1247,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
updatePositionRecursive(m_matrixnode);
m_animated_meshnode->updateAbsolutePosition();
m_animated_meshnode->animateJoints();
updateBonePosition();
updateBones(dtime);
}
}

Expand Down Expand Up @@ -1527,19 +1528,28 @@ void GenericCAO::updateAnimationSpeed()
m_animated_meshnode->setAnimationSpeed(m_animation_speed);
}

void GenericCAO::updateBonePosition()
void GenericCAO::updateBones(f32 dtime)
{
if (m_bone_position.empty() || !m_animated_meshnode)
if (!m_animated_meshnode)
return;
if (m_bone_override.empty()) {
m_animated_meshnode->setJointMode(scene::EJUOR_NONE);
return;
}

m_animated_meshnode->setJointMode(scene::EJUOR_CONTROL); // To write positions to the mesh on render
for (auto &it : m_bone_position) {
for (auto &it : m_bone_override) {
std::string bone_name = it.first;
scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str());
if (bone) {
bone->setPosition(it.second.X);
bone->setRotation(it.second.Y);
}
if (!bone)
continue;

BoneOverride &props = it.second;
props.dtime_passed += dtime;

bone->setPosition(props.getPosition(bone->getPosition()));
bone->setRotation(props.getRotationEulerDeg(bone->getRotation()));
bone->setScale(props.getScale(bone->getScale()));
}

// search through bones to find mistakenly rotated bones due to bug in Irrlicht
Expand All @@ -1550,7 +1560,7 @@ void GenericCAO::updateBonePosition()

//If bone is manually positioned there is no need to perform the bug check
bool skip = false;
for (auto &it : m_bone_position) {
for (auto &it : m_bone_override) {
if (it.first == bone->getName()) {
skip = true;
break;
Expand Down Expand Up @@ -1852,11 +1862,46 @@ void GenericCAO::processMessage(const std::string &data)
updateAnimationSpeed();
} else if (cmd == AO_CMD_SET_BONE_POSITION) {
std::string bone = deSerializeString16(is);
v3f position = readV3F32(is);
v3f rotation = readV3F32(is);
m_bone_position[bone] = core::vector2d<v3f>(position, rotation);

// updateBonePosition(); now called every step
auto it = m_bone_override.find(bone);
BoneOverride props;
if (it != m_bone_override.end()) {
props = it->second;
// Reset timer
props.dtime_passed = 0;
// Save previous values for interpolation
props.position.previous = props.position.vector;
props.rotation.previous = props.rotation.next;
props.scale.previous = props.scale.vector;
} else {
// Disable interpolation
props.position.interp_timer = 0.0f;
props.rotation.interp_timer = 0.0f;
props.scale.interp_timer = 0.0f;
}
// Read new values
props.position.vector = readV3F32(is);
props.rotation.next = core::quaternion(readV3F32(is) * core::DEGTORAD);
props.scale.vector = readV3F32(is); // reads past end of string on older cmds
if (is.eof()) {
// Backwards compatibility
props.scale.vector = v3f(1, 1, 1); // restore the scale which was not sent
props.position.absolute = true;
props.rotation.absolute = true;
} else {
props.position.interp_timer = readF32(is);
props.rotation.interp_timer = readF32(is);
props.scale.interp_timer = readF32(is);
u8 absoluteFlag = readU8(is);
props.position.absolute = (absoluteFlag & 1) > 0;
props.rotation.absolute = (absoluteFlag & 2) > 0;
props.scale.absolute = (absoluteFlag & 4) > 0;
}
if (props.isIdentity()) {
m_bone_override.erase(bone);
} else {
m_bone_override[bone] = props;
}
// updateBones(); now called every step
} else if (cmd == AO_CMD_ATTACH_TO) {
u16 parent_id = readS16(is);
std::string bone = deSerializeString16(is);
Expand Down
4 changes: 2 additions & 2 deletions src/client/content_cao.h
Expand Up @@ -104,7 +104,7 @@ class GenericCAO : public ClientActiveObject
float m_animation_blend = 0.0f;
bool m_animation_loop = true;
// stores position and rotation for each bone name
std::unordered_map<std::string, core::vector2d<v3f>> m_bone_position;
BoneOverrideMap m_bone_override;

int m_attachment_parent_id = 0;
std::unordered_set<int> m_attachment_child_ids;
Expand Down Expand Up @@ -267,7 +267,7 @@ class GenericCAO : public ClientActiveObject

void updateAnimationSpeed();

void updateBonePosition();
void updateBones(f32 dtime);

void processMessage(const std::string &data) override;

Expand Down
5 changes: 4 additions & 1 deletion src/network/networkprotocol.h
Expand Up @@ -219,9 +219,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
"start_time" added to TOCLIENT_PLAY_SOUND
place_param2 type change u8 -> optional<u8>
[scheduled bump for 5.8.0]
PROTOCOL VERSION 44:
AO_CMD_SET_BONE_POSITION extended
[scheduled bump for 5.9.0]
*/

#define LATEST_PROTOCOL_VERSION 43
#define LATEST_PROTOCOL_VERSION 44
#define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION)

// Server's supported network protocol range
Expand Down

0 comments on commit 0d61598

Please sign in to comment.