Skip to content

Commit

Permalink
Implemented posing (#251)
Browse files Browse the repository at this point in the history
- Added GUI elements that only appear when the bone tab is selected
that allow the user to turn posing on and off, select a bone to pose,
adjust the six DOFs of the pose, and reset one or all bone poses.
Posing is always off when the bone tab is not selected.  (Changes to
OutfitStudio.{xrc,h,cpp}.)

- Fixed custom bone added without transforms.

This patch makes Outfit Studio preserve the skeleton tree structure
of custom bones and new bones when nif files are loaded and saved.

Co-authored-by: ousnius <[email protected]>
  • Loading branch information
ousnius authored Mar 5, 2020
1 parent 9bf2f9a commit c338568
Show file tree
Hide file tree
Showing 11 changed files with 754 additions and 103 deletions.
13 changes: 7 additions & 6 deletions lib/NIF/NifFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -435,20 +435,21 @@ bool NifFile::DeleteUnreferencedBlocks() {
return hadDeletions;
}

int NifFile::AddNode(const std::string& nodeName, const MatTransform& xformToParent) {
auto root = GetRootNode();
if (!root)
return 0xFFFFFFFF;
NiNode* NifFile::AddNode(const std::string& nodeName, const MatTransform& xformToParent, NiNode* parent) {
if (!parent)
parent = GetRootNode();
if (!parent)
return nullptr;

auto newNode = new NiNode();
newNode->SetName(nodeName);
newNode->SetTransformToParent(xformToParent);

int newNodeId = hdr.AddBlock(newNode);
if (newNodeId != 0xFFFFFFFF)
root->GetChildren().AddBlockRef(newNodeId);
parent->GetChildren().AddBlockRef(newNodeId);

return newNodeId;
return newNode;
}

void NifFile::DeleteNode(const std::string& nodeName) {
Expand Down
2 changes: 1 addition & 1 deletion lib/NIF/NifFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class NifFile {
void LinkGeomData();
void RemoveInvalidTris();

int AddNode(const std::string& nodeName, const MatTransform& xformToParent);
NiNode* AddNode(const std::string& nodeName, const MatTransform& xformToParent, NiNode* parent = nullptr);
void DeleteNode(const std::string& nodeName);
bool CanDeleteNode(const std::string& nodeName);
std::string GetNodeName(const int blockID);
Expand Down
46 changes: 46 additions & 0 deletions lib/NIF/utils/Object3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,49 @@ MatTransform MatTransform::ComposeTransforms(const MatTransform &other) const {
comp.translation = translation + rotation * (scale * other.translation);
return comp;
}

Matrix3 RotVecToMat(const Vector3 &v) {
double angle = std::sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
double cosang = std::cos(angle);
double sinang = std::sin(angle);
double onemcosang; // One minus cosang
// Avoid loss of precision from cancellation in calculating onemcosang
if (cosang > .5)
onemcosang = sinang * sinang / (1 + cosang);
else
onemcosang = 1 - cosang;
Vector3 n = angle != 0 ? v / angle : Vector3(1,0,0);
Matrix3 m;
m[0][0] = n.x * n.x * onemcosang + cosang;
m[1][1] = n.y * n.y * onemcosang + cosang;
m[2][2] = n.z * n.z * onemcosang + cosang;
m[0][1] = n.x * n.y * onemcosang + n.z * sinang;
m[1][0] = n.x * n.y * onemcosang - n.z * sinang;
m[1][2] = n.y * n.z * onemcosang + n.x * sinang;
m[2][1] = n.y * n.z * onemcosang - n.x * sinang;
m[2][0] = n.z * n.x * onemcosang + n.y * sinang;
m[0][2] = n.z * n.x * onemcosang - n.y * sinang;
return m;
}

Vector3 RotMatToVec(const Matrix3 &m) {
double cosang = (m[0][0] + m[1][1] + m[2][2] - 1) * 0.5;
if (cosang >= 1)
return Vector3(0,0,0);
else if (cosang > -1) {
Vector3 v(m[1][2] - m[2][1], m[2][0] - m[0][2], m[0][1] - m[1][0]);
v.Normalize();
return v * std::acos(cosang);
}
else { // cosang <= -1, sinang == 0
Vector3 v(
std::sqrt((m[0][0] - cosang) * 0.5),
std::sqrt((m[1][1] - cosang) * 0.5),
std::sqrt((m[2][2] - cosang) * 0.5));
v.Normalize();
if (m[1][2] < m[2][1]) v.x = -v.x;
if (m[2][0] < m[0][2]) v.y = -v.y;
if (m[0][1] < m[1][0]) v.z = -v.z;
return v * PI;
}
}
16 changes: 14 additions & 2 deletions lib/NIF/utils/Object3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct Vector2 {
return false;
}
bool operator != (const Vector2& other) {
if (u != other.u && v != other.v)
if (u != other.u || v != other.v)
return true;
return false;
}
Expand Down Expand Up @@ -145,7 +145,7 @@ struct Vector3 {
return false;
}
bool operator != (const Vector3& other) {
if (x != other.x && y != other.y && z != other.z)
if (x != other.x || y != other.y || z != other.z)
return true;
return false;
}
Expand Down Expand Up @@ -541,6 +541,18 @@ class Matrix3 {
}
};

// RotVecToMat: converts a rotation vector to a rotation matrix.
// (A rotation vector has direction the axis of the rotation
// and magnitude the angle of rotation.)
Matrix3 RotVecToMat(const Vector3 &v);

// RotMatToVec: converts a rotation matrix into a rotation vector.
// (A rotation vector has direction the axis of the rotation
// and magnitude the angle of rotation.)
// Note that this function is unstable for angles near pi, but it
// should still work.
Vector3 RotMatToVec(const Matrix3 &m);

// 4D Matrix class for calculating and applying transformations.
class Matrix4 {
float m[16];
Expand Down
250 changes: 250 additions & 0 deletions res/xrc/OutfitStudio.xrc
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,256 @@
</object>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxALL|wxEXPAND|wxLEFT</flag>
<border>2</border>
<object class="wxCollapsiblePane" name="posePane">
<style>wxCP_DEFAULT_STYLE</style>
<collapsed>1</collapsed>
<label>Posing</label>
<bg>wxSYS_COLOUR_MENU</bg>
<hidden>1</hidden>
<object class="panewindow">
<object class="wxBoxSizer">
<orient>wxVERTICAL</orient>
<object class="sizeritem">
<option>0</option>
<flag>wxALL|wxEXPAND</flag>
<object class="wxBoxSizer">
<orient>wxHORIZONTAL</orient>
<object class="sizeritem">
<option>0</option>
<flag>wxLEFT|wxRIGHT|wxEXPAND</flag>
<border>5</border>
<object class="wxCheckBox" name="cbPose">
<label>Show Pose</label>
<checked>0</checked>
</object>
</object>
<object class="sizeritem">
<option>1</option>
<flag>wxLEFT|wxRIGHT|wxEXPAND</flag>
<border>5</border>
<object class="wxChoice" name="cPoseBone">
<selection>0</selection>
<content />
</object>
</object>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxLEFT|wxRIGHT|wxEXPAND</flag>
<border>5</border>
<object class="wxBoxSizer">
<orient>wxHORIZONTAL</orient>
<object class="sizeritem">
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
<object class="wxButton" name="resetBonePose">
<size>-1,25</size>
<label>Reset Bone</label>
</object>
</object>
<object class="sizeritem">
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
<object class="wxButton" name="resetAllPose">
<size>-1,25</size>
<label>Reset All</label>
</object>
</object>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxLEFT|wxRIGHT|wxEXPAND</flag>
<border>5</border>
<object class="wxFlexGridSizer">
<rows>0</rows>
<cols>3</cols>
<vgap>0</vgap>
<hgap>0</hgap>
<growablecols>1</growablecols>
<growablerows></growablerows>
<object class="sizeritem">
<option>0</option>
<flag>wxRIGHT|wxALIGN_CENTER</flag>
<object class="wxStaticText" name="rxLabel">
<label>Rotation X</label>
</object>
</object>
<object class="sizeritem">
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
<object class="wxSlider" name="rxPoseSlider">
<style>wxSL_HORIZONTAL</style>
<size>-1,10</size>
<value>0</value>
<min>-300</min>
<max>300</max>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxALIGN_CENTER_VERTICAL|wxALL</flag>
<object class="wxTextCtrl" name="rxPoseText">
<style>wxTE_RIGHT|wxSIMPLE_BORDER</style>
<size>40,-1</size>
<value>0</value>
<maxlength>0</maxlength>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxRIGHT|wxALIGN_CENTER</flag>
<object class="wxStaticText" name="ryLabel">
<label>Rotation Y</label>
</object>
</object>
<object class="sizeritem">
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
<object class="wxSlider" name="ryPoseSlider">
<style>wxSL_HORIZONTAL</style>
<size>-1,10</size>
<value>0</value>
<min>-300</min>
<max>300</max>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxALIGN_CENTER_VERTICAL|wxALL</flag>
<object class="wxTextCtrl" name="ryPoseText">
<style>wxTE_RIGHT|wxSIMPLE_BORDER</style>
<size>40,-1</size>
<value>0</value>
<maxlength>0</maxlength>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxRIGHT|wxALIGN_CENTER</flag>
<object class="wxStaticText" name="rzLabel">
<label>Rotation Z</label>
</object>
</object>
<object class="sizeritem">
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
<object class="wxSlider" name="rzPoseSlider">
<style>wxSL_HORIZONTAL</style>
<size>-1,10</size>
<value>0</value>
<min>-300</min>
<max>300</max>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxALIGN_CENTER_VERTICAL|wxALL</flag>
<object class="wxTextCtrl" name="rzPoseText">
<style>wxTE_RIGHT|wxSIMPLE_BORDER</style>
<size>40,-1</size>
<value>0</value>
<maxlength>0</maxlength>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxRIGHT|wxALIGN_CENTER</flag>
<object class="wxStaticText" name="txLabel">
<label>Offset X</label>
</object>
</object>
<object class="sizeritem">
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
<object class="wxSlider" name="txPoseSlider">
<style>wxSL_HORIZONTAL</style>
<size>-1,10</size>
<value>0</value>
<min>-1000</min>
<max>1000</max>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxALIGN_CENTER_VERTICAL|wxALL</flag>
<object class="wxTextCtrl" name="txPoseText">
<style>wxTE_RIGHT|wxSIMPLE_BORDER</style>
<size>40,-1</size>
<value>0</value>
<maxlength>0</maxlength>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxRIGHT|wxALIGN_CENTER</flag>
<object class="wxStaticText" name="tyLabel">
<label>Offset Y</label>
</object>
</object>
<object class="sizeritem">
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
<object class="wxSlider" name="tyPoseSlider">
<style>wxSL_HORIZONTAL</style>
<size>-1,10</size>
<value>0</value>
<min>-1000</min>
<max>1000</max>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<flag>wxALIGN_CENTER_VERTICAL|wxALL</flag>
<object class="wxTextCtrl" name="tyPoseText">
<style>wxTE_RIGHT|wxSIMPLE_BORDER</style>
<size>40,-1</size>
<value>0</value>
<maxlength>0</maxlength>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<border>5</border>
<flag>wxBOTTOM|wxALIGN_CENTER</flag>
<object class="wxStaticText" name="tzLabel">
<label>Offset Z</label>
</object>
</object>
<object class="sizeritem">
<option>1</option>
<border>5</border>
<flag>wxBOTTOM|wxEXPAND</flag>
<object class="wxSlider" name="tzPoseSlider">
<style>wxSL_HORIZONTAL</style>
<size>-1,10</size>
<value>0</value>
<min>-1000</min>
<max>1000</max>
</object>
</object>
<object class="sizeritem">
<option>0</option>
<border>5</border>
<flag>wxBOTTOM|wxALIGN_CENTER_VERTICAL</flag>
<object class="wxTextCtrl" name="tzPoseText">
<style>wxTE_RIGHT|wxSIMPLE_BORDER</style>
<size>40,-1</size>
<value>0</value>
<maxlength>0</maxlength>
</object>
</object>
</object>
</object>
</object>
</object>
</object>
</object>
<object class="sizeritem">
<option>3</option>
<flag>wxEXPAND</flag>
Expand Down
Loading

0 comments on commit c338568

Please sign in to comment.