Skip to content

Commit

Permalink
Character: added bone blend masks.
Browse files Browse the repository at this point in the history
Example for the stock character.mesh (disables all upper body animation when running):
```
begin_bone_blend_mask
    anim_name "Run"
    bone_weight "hand.R" 0
    bone_weight "hand.L" 0
    bone_weight "forearm.R" 0
    bone_weight "forearm.L" 0
    bone_weight "upper_arm.R" 0
    bone_weight "upper_arm.L" 0
    bone_weight "shoulder.R" 0
    bone_weight "shoulder.L" 0
    bone_weight "head" 0
    bone_weight "neck" 0
    bone_weight "chest" 0
    bone_weight "spine" 0
end_bone_blend_mask
```
  • Loading branch information
ohlidalp committed Oct 12, 2022
1 parent 9c57646 commit 89fe15e
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 8 deletions.
25 changes: 24 additions & 1 deletion resources/skeleton/config/classic.character
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
; The classic character (aka RORbot)
; NOTE each anim is evaluated separately, there is no either-or relation,

; This file format lets you compose game animations from one or more
; skeletal animations exported from a 3D modelling tool.
; NOTE each game-anim is evaluated separately, there is no either-or relation,
; so you must set each anim's conditions to avoid conflicts.

; For explanation of individual parameters,
Expand All @@ -14,6 +17,24 @@
mesh_name "character.mesh"
character_name "Classic RORBot"

begin_bone_blend_mask
; This is a dummy example of how bone blend masks can be done.
; bone_weight <bone name> <weight: 0.0 - 1.0>
anim_name "Run"
bone_weight "hand.R" 1
bone_weight "hand.L" 1
bone_weight "forearm.R" 1
bone_weight "forearm.L" 1
bone_weight "upper_arm.R" 1
bone_weight "upper_arm.L" 1
bone_weight "shoulder.R" 1
bone_weight "shoulder.L" 1
bone_weight "head" 1
bone_weight "neck" 1
bone_weight "chest" 1
bone_weight "spine" 1
end_bone_blend_mask

begin_animation
game_description "driving"
for_situation SITUATION_DRIVING
Expand Down Expand Up @@ -160,6 +181,7 @@ begin_animation
except_situation SITUATION_CUSTOM_MODE_01
anim_name "Spot_swim"
playback_time_ratio 10.0
anim_continuous false
end_animation

begin_animation
Expand All @@ -169,4 +191,5 @@ begin_animation
except_situation SITUATION_DRIVING
anim_name "Driving"
playback_time_ratio 2.0

end_animation
42 changes: 39 additions & 3 deletions source/main/gfx/GfxCharacter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,18 @@ GfxCharacter::GfxCharacter(Character* character)
MaterialPtr mat2 = mat1->clone("tracks/" + xc_instance_name);
entity->setMaterialName("tracks/" + xc_instance_name);

// setup animations
if (character->getCharacterDocument()->animblend_cumulative)
// setup animation blend
switch (character->getCharacterDocument()->force_animblend)
{
entity->getSkeleton()->setBlendMode(ANIMBLEND_CUMULATIVE);
case ForceAnimBlend::CUMULATIVE: entity->getSkeleton()->setBlendMode(ANIMBLEND_CUMULATIVE); break;
case ForceAnimBlend::AVERAGE: entity->getSkeleton()->setBlendMode(ANIMBLEND_AVERAGE); break;
default:; // Keep the preset defined in .skeleton file
}

// setup bone blend masks
for (BoneBlendMaskDef& mask_def : character->getCharacterDocument()->bone_blend_masks)
{
this->SetupBoneBlendMask(mask_def);
}

// setup diagnostic UI
Expand All @@ -76,6 +84,34 @@ GfxCharacter::GfxCharacter(Character* character)
}
}

void GfxCharacter::SetupBoneBlendMask(BoneBlendMaskDef const& mask_def)
{
Entity* entity = static_cast<Entity*>(xc_scenenode->getAttachedObject(0));

AnimationState* mask_created = nullptr;
try
{
AnimationState* anim = entity->getAnimationState(mask_def.anim_name);
if (mask_def.bone_weights.size() > 0)
{
anim->createBlendMask(entity->getSkeleton()->getNumBones());
mask_created = anim;
for (BoneBlendMaskWeightDef const& def : mask_def.bone_weights)
{
Bone* bone = entity->getSkeleton()->getBone(def.bone_name);
anim->setBlendMaskEntry(bone->getHandle(), def.bone_weight);
}
}
}
catch (Exception& eeh)
{
App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_ERROR,
fmt::format("error setting up bone blend mask for animation '{}', message:{}", mask_def.anim_name, eeh.getFullDescription()));
if (mask_created)
mask_created->destroyBlendMask();
}
}

RoR::GfxCharacter::~GfxCharacter()
{
Entity* ent = static_cast<Ogre::Entity*>(xc_scenenode->getAttachedObject(0));
Expand Down
1 change: 1 addition & 0 deletions source/main/gfx/GfxCharacter.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ struct GfxCharacter
void EnableAnim(Ogre::AnimationState* anim_state, float time);
void UpdateAnimations(float dt);
void EvaluateAnimDef(CharacterAnimDef const& def, float dt);
void SetupBoneBlendMask(BoneBlendMaskDef const& mask_def);

Ogre::SceneNode* xc_scenenode;
CharacterSB xc_simbuf;
Expand Down
3 changes: 2 additions & 1 deletion source/main/gui/panels/GUI_CharacterPoseUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ void CharacterPoseUtil::DrawAnimControls(Ogre::AnimationState* anim_state)

// anim name line
ImVec4 color = (anim_state->getEnabled()) ? ImGui::GetStyle().Colors[ImGuiCol_Text] : ImGui::GetStyle().Colors[ImGuiCol_TextDisabled];
ImGui::TextColored(color, "'%s' (%.2f sec)", anim_state->getAnimationName().c_str(), anim_state->getLength());
const char* uses_boneblendmask_text = anim_state->getBlendMask() ? ", uses bone blend mask!" : "";
ImGui::TextColored(color, "'%s' (%.2f sec%s)", anim_state->getAnimationName().c_str(), anim_state->getLength(), uses_boneblendmask_text);
if (m_manual_pose_active)
{
ImGui::SameLine();
Expand Down
34 changes: 32 additions & 2 deletions source/main/resources/character_fileformat/CharacterFileFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ void CharacterParser::TokenizeCurrentLine()
// retval true = continue processing (false = stop)
void CharacterParser::ProcessCurrentLine()
{
if (!m_ctx.in_anim)
if (!m_ctx.in_anim && !m_ctx.in_bone_blend_mask)
{
// Root level

if (StartsWith(m_cur_line, "character_name"))
{
m_def->character_name = GetParam(1);
Expand All @@ -138,9 +140,15 @@ void CharacterParser::ProcessCurrentLine()
{
m_ctx.in_anim = true;
}
else if (StartsWith(m_cur_line, "begin_bone_blend_mask"))
{
m_ctx.in_bone_blend_mask = true;
}
}
else
else if (m_ctx.in_anim)
{
// In '[begin/end]_animation' block.

if (StartsWith(m_cur_line, "end_animation"))
{
m_ctx.anim.game_id = (int)m_def->anims.size();
Expand Down Expand Up @@ -209,6 +217,28 @@ void CharacterParser::ProcessCurrentLine()
m_ctx.anim.source_percentual = Ogre::StringConverter::parseBool(GetParam(1));
}
}
else if (m_ctx.in_bone_blend_mask)
{
// In '[begin/end]_bone_blend_mask' block.

if (StartsWith(m_cur_line, "end_bone_blend_mask"))
{
m_def->bone_blend_masks.push_back(m_ctx.bone_blend_mask);
m_ctx.bone_blend_mask = BoneBlendMaskDef();
m_ctx.in_bone_blend_mask = false;
}
else if (StartsWith(m_cur_line, "anim_name"))
{
m_ctx.bone_blend_mask.anim_name = GetParam(1);
}
else if (StartsWith(m_cur_line, "bone_weight"))
{
BoneBlendMaskWeightDef def;
def.bone_name = GetParam(1);
def.bone_weight = Ogre::StringConverter::parseReal(GetParam(2));
m_ctx.bone_blend_mask.bone_weights.push_back(def);
}
}
}


16 changes: 15 additions & 1 deletion source/main/resources/character_fileformat/CharacterFileFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ namespace RoR {
/// @addtogroup Character
/// @{

struct BoneBlendMaskWeightDef //!< See `Ogre::AnimationState::setBlendMaskEntry()`
{
std::string bone_name;
float bone_weight = 0.f;
};

struct BoneBlendMaskDef //!< Additional settings for a skeletal animation track exported from 3D modelling tool.
{
std::string anim_name; //!< Name of the skeletal animation from OGRE's *.skeleton file.
std::vector<BoneBlendMaskWeightDef> bone_weights;
};

struct CharacterAnimDef
{
std::string anim_name; //!< Name of the skeletal animation from OGRE's *.skeleton file.
Expand Down Expand Up @@ -71,7 +83,7 @@ struct CharacterDocument
std::string character_name;
std::string mesh_name;
std::vector<CharacterAnimDef> anims;
std::vector<SkeletalAnimOptions> skeletal_anim_opts;
std::vector<BoneBlendMaskDef> bone_blend_masks;
ForceAnimBlend force_animblend = ForceAnimBlend::NONE; //!< Should a specific `Ogre::SkeletonAnimationBlendMode` be forced, or should we keep what the .skeleton file defines?

CharacterAnimDef* getAnimById(int id)
Expand Down Expand Up @@ -104,7 +116,9 @@ class CharacterParser
struct CharacterParserContext
{
CharacterAnimDef anim;
BoneBlendMaskDef bone_blend_mask;
bool in_anim = false;
bool in_bone_blend_mask = false;
} m_ctx; //!< Parser context

CharacterDocumentPtr m_def;
Expand Down

0 comments on commit 89fe15e

Please sign in to comment.