poses 2
This commit is contained in:
@@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
#include <osg/ref_ptr>
|
#include <osg/ref_ptr>
|
||||||
#include <osg/Vec3>
|
#include <osg/Vec3>
|
||||||
|
#include <osg/Matrix>
|
||||||
#include <osg/Geode>
|
#include <osg/Geode>
|
||||||
|
#include <osg/PrimitiveSet>
|
||||||
#include <osg/Group>
|
#include <osg/Group>
|
||||||
#include <osg/Geometry>
|
#include <osg/Geometry>
|
||||||
#include <osgViewer/Viewer>
|
#include <osgViewer/Viewer>
|
||||||
@@ -31,6 +33,9 @@ public:
|
|||||||
void setVisible(bool v);
|
void setVisible(bool v);
|
||||||
bool isVisible() const { return m_visible; }
|
bool isVisible() const { return m_visible; }
|
||||||
|
|
||||||
|
/// Set the model world matrix so bone positions are transformed correctly.
|
||||||
|
void setModelMatrix(const osg::Matrix& m) { m_modelMatrix = m; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void buildInitialGeometry(); // called once on init
|
void buildInitialGeometry(); // called once on init
|
||||||
void updatePositions(); // cheap per-frame position update
|
void updatePositions(); // cheap per-frame position update
|
||||||
@@ -43,6 +48,7 @@ private:
|
|||||||
// Line geometry arrays — updated in place each frame
|
// Line geometry arrays — updated in place each frame
|
||||||
osg::ref_ptr<osg::Vec3Array> m_lineVerts;
|
osg::ref_ptr<osg::Vec3Array> m_lineVerts;
|
||||||
osg::ref_ptr<osg::Vec4Array> m_lineColors;
|
osg::ref_ptr<osg::Vec4Array> m_lineColors;
|
||||||
|
osg::ref_ptr<osg::DrawArrays> m_lineDrawArrays;
|
||||||
|
|
||||||
// Sphere positions — one MatrixTransform per bone
|
// Sphere positions — one MatrixTransform per bone
|
||||||
std::vector<osg::ref_ptr<osg::MatrixTransform>> m_sphereXforms;
|
std::vector<osg::ref_ptr<osg::MatrixTransform>> m_sphereXforms;
|
||||||
@@ -52,4 +58,5 @@ private:
|
|||||||
std::string m_prevSelected; // detect selection changes
|
std::string m_prevSelected; // detect selection changes
|
||||||
bool m_visible = true;
|
bool m_visible = true;
|
||||||
bool m_geometryBuilt = false;
|
bool m_geometryBuilt = false;
|
||||||
|
osg::Matrix m_modelMatrix; // model world transform for coordinate conversion
|
||||||
};
|
};
|
||||||
@@ -46,7 +46,8 @@ public:
|
|||||||
/// Called once after skeleton is loaded.
|
/// Called once after skeleton is loaded.
|
||||||
void init(osgAnimation::Skeleton* skeleton,
|
void init(osgAnimation::Skeleton* skeleton,
|
||||||
const std::unordered_map<std::string,
|
const std::unordered_map<std::string,
|
||||||
osg::ref_ptr<osgAnimation::Bone>>& boneMap);
|
osg::ref_ptr<osgAnimation::Bone>>& boneMap,
|
||||||
|
const std::unordered_map<std::string, osg::Vec3>& bindPositions = {});
|
||||||
|
|
||||||
/// Sorted list of all bone names (for UI tree).
|
/// Sorted list of all bone names (for UI tree).
|
||||||
const std::vector<std::string>& boneNames() const { return m_boneNames; }
|
const std::vector<std::string>& boneNames() const { return m_boneNames; }
|
||||||
@@ -84,7 +85,8 @@ private:
|
|||||||
osg::ref_ptr<osgAnimation::Skeleton> m_skeleton;
|
osg::ref_ptr<osgAnimation::Skeleton> m_skeleton;
|
||||||
std::unordered_map<std::string, osg::ref_ptr<osgAnimation::Bone>> m_boneMap;
|
std::unordered_map<std::string, osg::ref_ptr<osgAnimation::Bone>> m_boneMap;
|
||||||
std::unordered_map<std::string, BonePose> m_poses;
|
std::unordered_map<std::string, BonePose> m_poses;
|
||||||
std::unordered_map<std::string, osg::Matrix> m_bindMatrices; // local bind pose
|
std::unordered_map<std::string, osg::Matrix> m_bindMatrices; // local bind pose
|
||||||
|
std::unordered_map<std::string, osg::Vec3> m_bindWorldPositions; // world pos from offset matrix
|
||||||
std::unordered_map<std::string, std::string> m_parents; // child → parent
|
std::unordered_map<std::string, std::string> m_parents; // child → parent
|
||||||
std::unordered_map<std::string, std::vector<std::string>> m_children;
|
std::unordered_map<std::string, std::vector<std::string>> m_children;
|
||||||
std::vector<std::string> m_boneNames; // sorted
|
std::vector<std::string> m_boneNames; // sorted
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ public:
|
|||||||
osg::ref_ptr<osgAnimation::Skeleton> skeleton;
|
osg::ref_ptr<osgAnimation::Skeleton> skeleton;
|
||||||
std::unordered_map<std::string,
|
std::unordered_map<std::string,
|
||||||
osg::ref_ptr<osgAnimation::Bone>> boneMap;
|
osg::ref_ptr<osgAnimation::Bone>> boneMap;
|
||||||
|
std::unordered_map<std::string, osg::Vec3> bindPositions; // world pos from inverted offset
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ bool Application::loadModel(const std::string& filepath) {
|
|||||||
auto skelResult = skelLoader.load(scene, baseDir, m_morphMgr.get());
|
auto skelResult = skelLoader.load(scene, baseDir, m_morphMgr.get());
|
||||||
if (skelResult.valid) {
|
if (skelResult.valid) {
|
||||||
m_modelNode = skelResult.root;
|
m_modelNode = skelResult.root;
|
||||||
m_poseMgr->init(skelResult.skeleton.get(), skelResult.boneMap);
|
m_poseMgr->init(skelResult.skeleton.get(), skelResult.boneMap,
|
||||||
|
skelResult.bindPositions);
|
||||||
m_boneSel = std::make_unique<BoneSelector>(m_poseMgr.get());
|
m_boneSel = std::make_unique<BoneSelector>(m_poseMgr.get());
|
||||||
m_boneSel->init(m_viewer.get(), m_sceneRoot.get());
|
m_boneSel->init(m_viewer.get(), m_sceneRoot.get());
|
||||||
m_imguiLayer->setPoseManager(m_poseMgr.get());
|
m_imguiLayer->setPoseManager(m_poseMgr.get());
|
||||||
@@ -260,9 +261,18 @@ void Application::setPoseMode(bool enabled) {
|
|||||||
|
|
||||||
void Application::applyPoseUpdate() {
|
void Application::applyPoseUpdate() {
|
||||||
if (!m_poseMgr || !m_poseMgr->isInitialized()) return;
|
if (!m_poseMgr || !m_poseMgr->isInitialized()) return;
|
||||||
// applyPose sets local matrices, then updateSkeletonMatrices propagates FK
|
|
||||||
if (m_poseMode) m_poseMgr->applyPose();
|
if (m_poseMode) m_poseMgr->applyPose();
|
||||||
m_poseMgr->updateSkeletonMatrices(); // always keep world positions fresh
|
m_poseMgr->updateSkeletonMatrices();
|
||||||
|
|
||||||
|
// Keep BoneSelector in sync with the model's current world transform
|
||||||
|
// so overlay positions match the rendered mesh position/scale
|
||||||
|
if (m_boneSel && m_modelXform) {
|
||||||
|
// Compute full world matrix by walking up parent transforms
|
||||||
|
osg::Matrix worldMat = m_modelXform->getMatrix();
|
||||||
|
// m_modelXform -> m_shaderGroup -> m_sceneRoot (no further transforms)
|
||||||
|
m_boneSel->setModelMatrix(worldMat);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_poseMode && m_boneSel) m_boneSel->updateVisualization();
|
if (m_poseMode && m_boneSel) m_boneSel->updateVisualization();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,17 +44,24 @@ void BoneSelector::buildInitialGeometry() {
|
|||||||
const auto& names = m_poseMgr->boneNames();
|
const auto& names = m_poseMgr->boneNames();
|
||||||
|
|
||||||
// ── Lines ─────────────────────────────────────────────────────────────────
|
// ── Lines ─────────────────────────────────────────────────────────────────
|
||||||
|
// Count bones that have parents (those get a line)
|
||||||
|
int lineCount = 0;
|
||||||
|
for (const auto& name : names)
|
||||||
|
if (!m_poseMgr->boneParent(name).empty()) ++lineCount;
|
||||||
|
|
||||||
m_lineVerts = new osg::Vec3Array;
|
m_lineVerts = new osg::Vec3Array;
|
||||||
m_lineColors = new osg::Vec4Array;
|
m_lineColors = new osg::Vec4Array;
|
||||||
m_lineVerts->resize(names.size() * 2, osg::Vec3());
|
m_lineVerts->resize(lineCount * 2, osg::Vec3());
|
||||||
m_lineColors->resize(names.size() * 2, osg::Vec4(0.4f,0.8f,1.f,0.7f));
|
m_lineColors->resize(lineCount * 2, osg::Vec4(0.4f,0.8f,1.f,0.7f));
|
||||||
|
|
||||||
auto lineGeom = new osg::Geometry;
|
auto lineGeom = new osg::Geometry;
|
||||||
lineGeom->setDataVariance(osg::Object::DYNAMIC);
|
lineGeom->setDataVariance(osg::Object::DYNAMIC);
|
||||||
lineGeom->setVertexArray(m_lineVerts);
|
lineGeom->setVertexArray(m_lineVerts);
|
||||||
lineGeom->setColorArray(m_lineColors, osg::Array::BIND_PER_VERTEX);
|
lineGeom->setColorArray(m_lineColors, osg::Array::BIND_PER_VERTEX);
|
||||||
lineGeom->addPrimitiveSet(
|
// Store DrawArrays so we can update its count
|
||||||
new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, m_lineVerts->size()));
|
m_lineDrawArrays = new osg::DrawArrays(osg::PrimitiveSet::LINES,
|
||||||
|
0, lineCount * 2);
|
||||||
|
lineGeom->addPrimitiveSet(m_lineDrawArrays);
|
||||||
lineGeom->getOrCreateStateSet()->setAttribute(new osg::LineWidth(1.5f));
|
lineGeom->getOrCreateStateSet()->setAttribute(new osg::LineWidth(1.5f));
|
||||||
|
|
||||||
m_lines = new osg::Geode;
|
m_lines = new osg::Geode;
|
||||||
@@ -104,9 +111,10 @@ void BoneSelector::updatePositions() {
|
|||||||
int lineIdx = 0;
|
int lineIdx = 0;
|
||||||
for (unsigned i = 0; i < m_sphereBoneNames.size() && i < m_sphereXforms.size(); ++i) {
|
for (unsigned i = 0; i < m_sphereBoneNames.size() && i < m_sphereXforms.size(); ++i) {
|
||||||
const std::string& name = m_sphereBoneNames[i];
|
const std::string& name = m_sphereBoneNames[i];
|
||||||
osg::Vec3 pos = m_poseMgr->getBoneWorldPos(name);
|
// Transform from skeleton space to world space via model matrix
|
||||||
|
osg::Vec3 skelPos = m_poseMgr->getBoneWorldPos(name);
|
||||||
|
osg::Vec3 pos = skelPos * m_modelMatrix;
|
||||||
|
|
||||||
// Move sphere via transform
|
|
||||||
m_sphereXforms[i]->setMatrix(osg::Matrix::translate(pos));
|
m_sphereXforms[i]->setMatrix(osg::Matrix::translate(pos));
|
||||||
|
|
||||||
// Update selection colour if changed
|
// Update selection colour if changed
|
||||||
@@ -126,7 +134,8 @@ void BoneSelector::updatePositions() {
|
|||||||
// Line: parent → this bone
|
// Line: parent → this bone
|
||||||
std::string parent = m_poseMgr->boneParent(name);
|
std::string parent = m_poseMgr->boneParent(name);
|
||||||
if (!parent.empty() && lineIdx + 1 < (int)m_lineVerts->size()) {
|
if (!parent.empty() && lineIdx + 1 < (int)m_lineVerts->size()) {
|
||||||
osg::Vec3 parentPos = m_poseMgr->getBoneWorldPos(parent);
|
osg::Vec3 parentSkelPos = m_poseMgr->getBoneWorldPos(parent);
|
||||||
|
osg::Vec3 parentPos = parentSkelPos * m_modelMatrix;
|
||||||
bool isSel = (name == m_selected || parent == m_selected);
|
bool isSel = (name == m_selected || parent == m_selected);
|
||||||
(*m_lineVerts)[lineIdx] = parentPos;
|
(*m_lineVerts)[lineIdx] = parentPos;
|
||||||
(*m_lineVerts)[lineIdx+1] = pos;
|
(*m_lineVerts)[lineIdx+1] = pos;
|
||||||
@@ -139,11 +148,13 @@ void BoneSelector::updatePositions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dirty line arrays
|
// Dirty line arrays so OSG re-uploads them
|
||||||
if (m_lineVerts) m_lineVerts->dirty();
|
if (m_lineVerts) m_lineVerts->dirty();
|
||||||
if (m_lineColors) m_lineColors->dirty();
|
if (m_lineColors) m_lineColors->dirty();
|
||||||
if (m_lines && m_lines->getNumDrawables() > 0)
|
if (m_lines && m_lines->getNumDrawables() > 0) {
|
||||||
m_lines->getDrawable(0)->dirtyBound();
|
m_lines->getDrawable(0)->dirtyBound();
|
||||||
|
m_lines->getDrawable(0)->dirtyDisplayList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ void ImGuiLayer::renderPoseTab() {
|
|||||||
// (changed flag removed — pose applied immediately via callback)
|
// (changed flag removed — pose applied immediately via callback)
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::TextUnformatted("Rotation (degrees):");
|
ImGui::TextUnformatted("Rotation (bone-local degrees):");
|
||||||
ImGui::SetNextItemWidth(-1.f);
|
ImGui::SetNextItemWidth(-1.f);
|
||||||
if (ImGui::SliderFloat3("##rot", m_boneEuler, -180.f, 180.f)) {
|
if (ImGui::SliderFloat3("##rot", m_boneEuler, -180.f, 180.f)) {
|
||||||
// Convert euler degrees to quaternion
|
// Convert euler degrees to quaternion
|
||||||
@@ -550,14 +550,15 @@ void ImGuiLayer::renderPoseTab() {
|
|||||||
osg::Quat qx(rx, osg::Vec3(1,0,0));
|
osg::Quat qx(rx, osg::Vec3(1,0,0));
|
||||||
osg::Quat qy(ry, osg::Vec3(0,1,0));
|
osg::Quat qy(ry, osg::Vec3(0,1,0));
|
||||||
osg::Quat qz(rz, osg::Vec3(0,0,1));
|
osg::Quat qz(rz, osg::Vec3(0,0,1));
|
||||||
m_poseMgr->setBoneRotation(m_selectedBone, qz * qy * qx);
|
// Local-space: Rx * Ry * Rz — rotates around bone's own axes
|
||||||
|
m_poseMgr->setBoneRotation(m_selectedBone, qx * qy * qz);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::TextUnformatted("Translation:");
|
ImGui::TextUnformatted("Translation:");
|
||||||
ImGui::SetNextItemWidth(-1.f);
|
ImGui::SetNextItemWidth(-1.f);
|
||||||
if (ImGui::SliderFloat3("##trans", m_boneTrans, -0.5f, 0.5f)) {
|
if (ImGui::SliderFloat3("##trans", m_boneTrans, -20.f, 20.f)) {
|
||||||
m_poseMgr->setBoneTranslation(m_selectedBone,
|
m_poseMgr->setBoneTranslation(m_selectedBone,
|
||||||
osg::Vec3(m_boneTrans[0], m_boneTrans[1], m_boneTrans[2]));
|
osg::Vec3(m_boneTrans[0], m_boneTrans[1], m_boneTrans[2]));
|
||||||
|
|
||||||
@@ -606,9 +607,29 @@ void ImGuiLayer::renderPoseTab() {
|
|||||||
if (ImGui::Selectable(name.c_str(), sel)) {
|
if (ImGui::Selectable(name.c_str(), sel)) {
|
||||||
m_selectedBone = name;
|
m_selectedBone = name;
|
||||||
if (m_boneSel) m_boneSel->setSelected(name);
|
if (m_boneSel) m_boneSel->setSelected(name);
|
||||||
|
// Sync sliders to current pose
|
||||||
|
if (m_poseMgr) {
|
||||||
|
const auto& p = m_poseMgr->getBonePose(m_selectedBone);
|
||||||
|
if (p.modified) {
|
||||||
|
// Extract euler angles from quaternion via rotation matrix
|
||||||
|
osg::Matrix rotMat; rotMat.makeRotate(p.rotation);
|
||||||
|
// Extract XYZ euler: atan2 from rotation matrix elements
|
||||||
|
float ex = std::atan2( rotMat(2,1), rotMat(2,2));
|
||||||
|
float ey = std::atan2(-rotMat(2,0),
|
||||||
|
std::sqrt(rotMat(2,1)*rotMat(2,1)+rotMat(2,2)*rotMat(2,2)));
|
||||||
|
float ez = std::atan2( rotMat(1,0), rotMat(0,0));
|
||||||
|
m_boneEuler[0] = ex * 180.f / (float)M_PI;
|
||||||
|
m_boneEuler[1] = ey * 180.f / (float)M_PI;
|
||||||
|
m_boneEuler[2] = ez * 180.f / (float)M_PI;
|
||||||
|
m_boneTrans[0] = p.translation.x();
|
||||||
|
m_boneTrans[1] = p.translation.y();
|
||||||
|
m_boneTrans[2] = p.translation.z();
|
||||||
|
} else {
|
||||||
memset(m_boneEuler, 0, sizeof(m_boneEuler));
|
memset(m_boneEuler, 0, sizeof(m_boneEuler));
|
||||||
memset(m_boneTrans, 0, sizeof(m_boneTrans));
|
memset(m_boneTrans, 0, sizeof(m_boneTrans));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (sel) ImGui::PopStyleColor();
|
if (sel) ImGui::PopStyleColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -646,8 +667,27 @@ void ImGuiLayer::renderBoneTree(const std::string& boneName, int depth) {
|
|||||||
if (ImGui::IsItemClicked()) {
|
if (ImGui::IsItemClicked()) {
|
||||||
m_selectedBone = boneName;
|
m_selectedBone = boneName;
|
||||||
if (m_boneSel) m_boneSel->setSelected(boneName);
|
if (m_boneSel) m_boneSel->setSelected(boneName);
|
||||||
memset(m_boneEuler, 0, sizeof(m_boneEuler));
|
if (m_poseMgr) {
|
||||||
memset(m_boneTrans, 0, sizeof(m_boneTrans));
|
const auto& p = m_poseMgr->getBonePose(boneName);
|
||||||
|
if (p.modified) {
|
||||||
|
// Extract euler angles from quaternion via rotation matrix
|
||||||
|
osg::Matrix rotMat; rotMat.makeRotate(p.rotation);
|
||||||
|
// Extract XYZ euler: atan2 from rotation matrix elements
|
||||||
|
float ex = std::atan2( rotMat(2,1), rotMat(2,2));
|
||||||
|
float ey = std::atan2(-rotMat(2,0),
|
||||||
|
std::sqrt(rotMat(2,1)*rotMat(2,1)+rotMat(2,2)*rotMat(2,2)));
|
||||||
|
float ez = std::atan2( rotMat(1,0), rotMat(0,0));
|
||||||
|
m_boneEuler[0] = ex * 180.f / (float)M_PI;
|
||||||
|
m_boneEuler[1] = ey * 180.f / (float)M_PI;
|
||||||
|
m_boneEuler[2] = ez * 180.f / (float)M_PI;
|
||||||
|
m_boneTrans[0] = p.translation.x();
|
||||||
|
m_boneTrans[1] = p.translation.y();
|
||||||
|
m_boneTrans[2] = p.translation.z();
|
||||||
|
} else {
|
||||||
|
memset(m_boneEuler, 0, sizeof(m_boneEuler));
|
||||||
|
memset(m_boneTrans, 0, sizeof(m_boneTrans));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open) {
|
if (open) {
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
void PoseManager::init(
|
void PoseManager::init(
|
||||||
osgAnimation::Skeleton* skeleton,
|
osgAnimation::Skeleton* skeleton,
|
||||||
const std::unordered_map<std::string,
|
const std::unordered_map<std::string,
|
||||||
osg::ref_ptr<osgAnimation::Bone>>& boneMap) {
|
osg::ref_ptr<osgAnimation::Bone>>& boneMap,
|
||||||
|
const std::unordered_map<std::string, osg::Vec3>& bindPositions) {
|
||||||
|
|
||||||
m_skeleton = skeleton;
|
m_skeleton = skeleton;
|
||||||
m_boneMap = boneMap;
|
m_boneMap = boneMap;
|
||||||
|
|
||||||
// Run one update traversal so all bones get their initial matrices computed
|
// Run one update traversal to get initial bone matrices
|
||||||
// from their StackedTransform, then we capture those as bind matrices.
|
|
||||||
{
|
{
|
||||||
osg::NodeVisitor nv(osg::NodeVisitor::UPDATE_VISITOR,
|
osg::NodeVisitor nv(osg::NodeVisitor::UPDATE_VISITOR,
|
||||||
osg::NodeVisitor::TRAVERSE_ALL_CHILDREN);
|
osg::NodeVisitor::TRAVERSE_ALL_CHILDREN);
|
||||||
@@ -24,36 +24,28 @@ void PoseManager::init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto& [name, bone] : boneMap) {
|
for (auto& [name, bone] : boneMap) {
|
||||||
// Capture bind pose AFTER the first update so it reflects the
|
|
||||||
// fully-composed local matrix from StackedTransform
|
|
||||||
m_bindMatrices[name] = bone->getMatrix();
|
m_bindMatrices[name] = bone->getMatrix();
|
||||||
m_poses[name] = BonePose{};
|
m_poses[name] = BonePose{};
|
||||||
|
bone->setUpdateCallback(nullptr); // we drive bones manually
|
||||||
// Remove the UpdateBone callback permanently — we drive all bone
|
|
||||||
// matrices manually from now on. This prevents the callback from
|
|
||||||
// ever overwriting our pose values.
|
|
||||||
bone->setUpdateCallback(nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build hierarchy from root bones only
|
// Build hierarchy from root bones only
|
||||||
for (auto& [name, bone] : boneMap) {
|
for (auto& [name, bone] : boneMap) {
|
||||||
bool hasParentInMap = false;
|
bool hasParentInMap = false;
|
||||||
for (unsigned i = 0; i < bone->getNumParents(); ++i) {
|
for (unsigned i = 0; i < bone->getNumParents(); ++i) {
|
||||||
auto* parent = dynamic_cast<osgAnimation::Bone*>(bone->getParent(i));
|
auto* p = dynamic_cast<osgAnimation::Bone*>(bone->getParent(i));
|
||||||
if (parent && boneMap.count(parent->getName())) {
|
if (p && boneMap.count(p->getName())) { hasParentInMap = true; break; }
|
||||||
hasParentInMap = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!hasParentInMap)
|
if (!hasParentInMap) buildHierarchy(bone.get(), "");
|
||||||
buildHierarchy(bone.get(), "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_boneNames.clear();
|
m_boneNames.clear();
|
||||||
for (auto& [name, _] : boneMap)
|
for (auto& [name, _] : boneMap) m_boneNames.push_back(name);
|
||||||
m_boneNames.push_back(name);
|
|
||||||
std::sort(m_boneNames.begin(), m_boneNames.end());
|
std::sort(m_boneNames.begin(), m_boneNames.end());
|
||||||
|
|
||||||
|
// Store Assimp bind positions as authoritative overlay positions at rest
|
||||||
|
m_bindWorldPositions = bindPositions;
|
||||||
|
|
||||||
m_traversalCount = 1000;
|
m_traversalCount = 1000;
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
std::cout << "[pose] PoseManager initialized: " << boneMap.size() << " bones.\n";
|
std::cout << "[pose] PoseManager initialized: " << boneMap.size() << " bones.\n";
|
||||||
@@ -62,7 +54,7 @@ void PoseManager::init(
|
|||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
void PoseManager::buildHierarchy(osgAnimation::Bone* bone,
|
void PoseManager::buildHierarchy(osgAnimation::Bone* bone,
|
||||||
const std::string& parentName) {
|
const std::string& parentName) {
|
||||||
std::string name = bone->getName();
|
std::string name = bone->getName();
|
||||||
if (!parentName.empty()) {
|
if (!parentName.empty()) {
|
||||||
m_parents[name] = parentName;
|
m_parents[name] = parentName;
|
||||||
@@ -74,8 +66,6 @@ void PoseManager::buildHierarchy(osgAnimation::Bone* bone,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
std::string PoseManager::boneParent(const std::string& name) const {
|
std::string PoseManager::boneParent(const std::string& name) const {
|
||||||
auto it = m_parents.find(name);
|
auto it = m_parents.find(name);
|
||||||
return it != m_parents.end() ? it->second : "";
|
return it != m_parents.end() ? it->second : "";
|
||||||
@@ -110,23 +100,24 @@ const PoseManager::BonePose& PoseManager::getBonePose(const std::string& name) c
|
|||||||
return it != m_poses.end() ? it->second : m_defaultPose;
|
return it != m_poses.end() ? it->second : m_defaultPose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
osg::Vec3 PoseManager::getBoneWorldPos(const std::string& name) const {
|
osg::Vec3 PoseManager::getBoneWorldPos(const std::string& name) const {
|
||||||
auto it = m_boneMap.find(name);
|
// Use Assimp bind positions — correct at rest, matched to Blender
|
||||||
if (it == m_boneMap.end()) return {};
|
auto it = m_bindWorldPositions.find(name);
|
||||||
return it->second->getMatrixInSkeletonSpace().getTrans();
|
if (it != m_bindWorldPositions.end()) return it->second;
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
void PoseManager::updateSkeletonMatrices() {
|
void PoseManager::updateSkeletonMatrices() {
|
||||||
// Since all UpdateBone callbacks are removed, we manually propagate
|
|
||||||
// bone matrices through the hierarchy by walking parent→child.
|
|
||||||
if (!m_initialized || !m_skeleton) return;
|
if (!m_initialized || !m_skeleton) return;
|
||||||
|
|
||||||
// Walk the skeleton tree and compute skeleton-space matrices
|
// Propagate FK: compute skeleton-space matrices for OSG skinning
|
||||||
std::function<void(osgAnimation::Bone*, const osg::Matrix&)> propagate =
|
std::function<void(osgAnimation::Bone*, const osg::Matrix&)> propagate =
|
||||||
[&](osgAnimation::Bone* bone, const osg::Matrix& parentSkelMat) {
|
[&](osgAnimation::Bone* bone, const osg::Matrix& parentMat) {
|
||||||
osg::Matrix skelMat = bone->getMatrix() * parentSkelMat;
|
osg::Matrix skelMat = bone->getMatrix() * parentMat;
|
||||||
bone->setMatrixInSkeletonSpace(skelMat);
|
bone->setMatrixInSkeletonSpace(skelMat);
|
||||||
for (unsigned i = 0; i < bone->getNumChildren(); ++i) {
|
for (unsigned i = 0; i < bone->getNumChildren(); ++i) {
|
||||||
auto* child = dynamic_cast<osgAnimation::Bone*>(bone->getChild(i));
|
auto* child = dynamic_cast<osgAnimation::Bone*>(bone->getChild(i));
|
||||||
@@ -134,7 +125,6 @@ void PoseManager::updateSkeletonMatrices() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Skeleton's own matrix is identity by default
|
|
||||||
for (unsigned i = 0; i < m_skeleton->getNumChildren(); ++i) {
|
for (unsigned i = 0; i < m_skeleton->getNumChildren(); ++i) {
|
||||||
auto* root = dynamic_cast<osgAnimation::Bone*>(m_skeleton->getChild(i));
|
auto* root = dynamic_cast<osgAnimation::Bone*>(m_skeleton->getChild(i));
|
||||||
if (root) propagate(root, m_skeleton->getMatrix());
|
if (root) propagate(root, m_skeleton->getMatrix());
|
||||||
@@ -146,7 +136,6 @@ void PoseManager::updateSkeletonMatrices() {
|
|||||||
void PoseManager::applyPose() {
|
void PoseManager::applyPose() {
|
||||||
if (!m_initialized) return;
|
if (!m_initialized) return;
|
||||||
|
|
||||||
// Set each bone's local matrix: bind pose + pose delta
|
|
||||||
for (auto& [name, pose] : m_poses) {
|
for (auto& [name, pose] : m_poses) {
|
||||||
auto boneIt = m_boneMap.find(name);
|
auto boneIt = m_boneMap.find(name);
|
||||||
if (boneIt == m_boneMap.end()) continue;
|
if (boneIt == m_boneMap.end()) continue;
|
||||||
@@ -161,13 +150,16 @@ void PoseManager::applyPose() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3 bindTrans = bind.getTrans();
|
osg::Vec3 bindTrans, sc; osg::Quat bindRot, so;
|
||||||
osg::Quat bindRot; osg::Vec3 sc; osg::Quat so;
|
|
||||||
bind.decompose(bindTrans, bindRot, sc, so);
|
bind.decompose(bindTrans, bindRot, sc, so);
|
||||||
|
|
||||||
|
// Apply pose rotation in bone-local space: bindRot * poseRot
|
||||||
|
osg::Quat newRot = bindRot * pose.rotation;
|
||||||
|
osg::Vec3 newTrans = bindTrans + pose.translation;
|
||||||
|
|
||||||
|
// Build clean TRS matrix — no manual scale baking which causes shear
|
||||||
bone->setMatrix(
|
bone->setMatrix(
|
||||||
osg::Matrix::scale(sc) *
|
osg::Matrix::rotate(newRot) *
|
||||||
osg::Matrix::rotate(pose.rotation * bindRot) *
|
osg::Matrix::translate(newTrans));
|
||||||
osg::Matrix::translate(bindTrans + pose.translation));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,8 +48,24 @@ static osg::ref_ptr<osg::Texture2D> loadTex(const std::string& path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static osg::Matrix aiToOsg(const aiMatrix4x4& m) {
|
static osg::Matrix aiToOsg(const aiMatrix4x4& m) {
|
||||||
return osg::Matrix(m.a1,m.b1,m.c1,m.d1, m.a2,m.b2,m.c2,m.d2,
|
// Assimp: row-major, row[i] = (a,b,c,d)[i], translation in col 4 (a4,b4,c4)
|
||||||
m.a3,m.b3,m.c3,m.d3, m.a4,m.b4,m.c4,m.d4);
|
// OSG Matrix(a,b,c,...) fills row-by-row, translation in row 3 (indices [12],[13],[14])
|
||||||
|
// Transpose is needed to convert between the two conventions.
|
||||||
|
return osg::Matrix(
|
||||||
|
m.a1, m.b1, m.c1, m.d1,
|
||||||
|
m.a2, m.b2, m.c2, m.d2,
|
||||||
|
m.a3, m.b3, m.c3, m.d3,
|
||||||
|
m.a4, m.b4, m.c4, m.d4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correctly transposed version for inverse bind matrices
|
||||||
|
// (offset matrix goes mesh→bone, needs proper convention)
|
||||||
|
static osg::Matrix aiToOsgTransposed(const aiMatrix4x4& m) {
|
||||||
|
return osg::Matrix(
|
||||||
|
m.a1, m.a2, m.a3, m.a4,
|
||||||
|
m.b1, m.b2, m.b3, m.b4,
|
||||||
|
m.c1, m.c2, m.c3, m.c4,
|
||||||
|
m.d1, m.d2, m.d3, m.d4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── applyMaterial ─────────────────────────────────────────────────────────────
|
// ── applyMaterial ─────────────────────────────────────────────────────────────
|
||||||
@@ -364,13 +380,28 @@ SkeletonLoader::Result SkeletonLoader::load(
|
|||||||
const aiBone* bone = mesh->mBones[b];
|
const aiBone* bone = mesh->mBones[b];
|
||||||
boneNames[bone->mName.C_Str()] = true;
|
boneNames[bone->mName.C_Str()] = true;
|
||||||
if (!invBindMatrices.count(bone->mName.C_Str()))
|
if (!invBindMatrices.count(bone->mName.C_Str()))
|
||||||
invBindMatrices[bone->mName.C_Str()] = aiToOsg(bone->mOffsetMatrix);
|
invBindMatrices[bone->mName.C_Str()] = aiToOsgTransposed(bone->mOffsetMatrix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boneNames.empty()) { std::cerr << "[skel] No bones.\n"; return result; }
|
if (boneNames.empty()) { std::cerr << "[skel] No bones.\n"; return result; }
|
||||||
std::cout << "[skel] Found " << boneNames.size() << " bones.\n";
|
std::cout << "[skel] Found " << boneNames.size() << " bones.\n";
|
||||||
|
|
||||||
|
// Invert offset matrices in Assimp space to get bind-pose world positions.
|
||||||
|
// This must be done BEFORE any OSG conversion to avoid matrix convention issues.
|
||||||
|
for (unsigned m = 0; m < scene->mNumMeshes; ++m) {
|
||||||
|
const aiMesh* mesh = scene->mMeshes[m];
|
||||||
|
for (unsigned b = 0; b < mesh->mNumBones; ++b) {
|
||||||
|
const aiBone* bone = mesh->mBones[b];
|
||||||
|
std::string name = bone->mName.C_Str();
|
||||||
|
if (result.bindPositions.count(name)) continue;
|
||||||
|
aiMatrix4x4 inv = bone->mOffsetMatrix;
|
||||||
|
inv.Inverse();
|
||||||
|
// Translation is in (a4, b4, c4) in Assimp row-major convention
|
||||||
|
result.bindPositions[name] = osg::Vec3(inv.a4, inv.b4, inv.c4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build skeleton
|
// Build skeleton
|
||||||
auto skeleton = new osgAnimation::Skeleton;
|
auto skeleton = new osgAnimation::Skeleton;
|
||||||
skeleton->setName("Skeleton");
|
skeleton->setName("Skeleton");
|
||||||
|
|||||||
Reference in New Issue
Block a user