Line data Source code
1 : /* Copyright (C) 2021 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : /*
19 : * Raw description of a skeleton animation
20 : */
21 :
22 : #include "precompiled.h"
23 :
24 : #include "SkeletonAnimDef.h"
25 : #include "maths/MathUtil.h"
26 : #include "maths/Matrix3D.h"
27 : #include "ps/CStr.h"
28 : #include "ps/CLogger.h"
29 : #include "ps/FileIo.h"
30 :
31 : namespace
32 : {
33 : // Start IDs at 1 to leave 0 as a special value.
34 : u32 g_NextSkeletonDefUID = 1;
35 : }
36 :
37 : ///////////////////////////////////////////////////////////////////////////////////////////
38 : // CSkeletonAnimDef constructor
39 0 : CSkeletonAnimDef::CSkeletonAnimDef() : m_FrameTime(0), m_NumKeys(0), m_NumFrames(0)
40 : {
41 0 : m_UID = g_NextSkeletonDefUID++;
42 : // Log a warning if we ever overflow. Should that not result from a bug, bumping to u64 ought to suffice.
43 0 : if (g_NextSkeletonDefUID == 0)
44 : {
45 : // Reset to 1.
46 0 : g_NextSkeletonDefUID++;
47 0 : LOGWARNING("CSkeletonAnimDef unique ID overflowed to 0 - model-animation bounds may be incorrect.");
48 : }
49 0 : }
50 :
51 : ///////////////////////////////////////////////////////////////////////////////////////////
52 : // CSkeletonAnimDef destructor
53 0 : CSkeletonAnimDef::~CSkeletonAnimDef()
54 : {
55 0 : }
56 :
57 : ///////////////////////////////////////////////////////////////////////////////////////////
58 : // BuildBoneMatrices: build matrices for all bones at the given time (in MS) in this
59 : // animation
60 0 : void CSkeletonAnimDef::BuildBoneMatrices(float time, CMatrix3D* matrices, bool loop) const
61 : {
62 0 : float fstartframe = time/m_FrameTime;
63 0 : size_t startframe = (size_t)(int)(time/m_FrameTime);
64 0 : float deltatime = fstartframe-startframe;
65 :
66 0 : startframe %= m_NumFrames;
67 :
68 0 : size_t endframe = startframe + 1;
69 0 : endframe %= m_NumFrames;
70 :
71 0 : if (!loop && endframe == 0)
72 : {
73 : // This might be something like a death animation, and interpolating
74 : // between the final frame and the initial frame is wrong, because they're
75 : // totally different. So if we've looped around to endframe==0, just display
76 : // the animation's final frame with no interpolation.
77 0 : for (size_t i = 0; i < m_NumKeys; i++)
78 : {
79 0 : const Key& key = GetKey(startframe, i);
80 0 : matrices[i].SetIdentity();
81 0 : matrices[i].Rotate(key.m_Rotation);
82 0 : matrices[i].Translate(key.m_Translation);
83 0 : }
84 : }
85 : else
86 : {
87 0 : for (size_t i = 0; i < m_NumKeys; i++)
88 : {
89 0 : const Key& startkey = GetKey(startframe, i);
90 0 : const Key& endkey = GetKey(endframe, i);
91 :
92 0 : CVector3D trans = Interpolate(startkey.m_Translation, endkey.m_Translation, deltatime);
93 : // TODO: is slerp the best thing to use here?
94 0 : CQuaternion rot;
95 0 : rot.Slerp(startkey.m_Rotation, endkey.m_Rotation, deltatime);
96 :
97 0 : rot.ToMatrix(matrices[i]);
98 0 : matrices[i].Translate(trans);
99 : }
100 : }
101 0 : }
102 :
103 : ///////////////////////////////////////////////////////////////////////////////////////////
104 : // Load: try to load the anim from given file; return a new anim if successful
105 0 : std::unique_ptr<CSkeletonAnimDef> CSkeletonAnimDef::Load(const VfsPath& filename)
106 : {
107 0 : CFileUnpacker unpacker;
108 0 : unpacker.Read(filename,"PSSA");
109 :
110 : // check version
111 0 : if (unpacker.GetVersion()<FILE_READ_VERSION) {
112 0 : throw PSERROR_File_InvalidVersion();
113 : }
114 :
115 : // unpack the data
116 0 : auto anim = std::make_unique<CSkeletonAnimDef>();
117 : try {
118 0 : CStr name; // unused - just here to maintain compatibility with the animation files
119 0 : unpacker.UnpackString(name);
120 0 : unpacker.UnpackRaw(&anim->m_FrameTime,sizeof(anim->m_FrameTime));
121 0 : anim->m_NumKeys = unpacker.UnpackSize();
122 0 : anim->m_NumFrames = unpacker.UnpackSize();
123 0 : anim->m_Keys.resize(anim->m_NumKeys*anim->m_NumFrames);
124 0 : unpacker.UnpackRaw(anim->m_Keys.data(), anim->m_Keys.size() * sizeof(decltype(anim->m_Keys)::value_type));
125 0 : } catch (PSERROR_File&) {
126 0 : anim.reset();
127 0 : throw;
128 : }
129 :
130 0 : return anim;
131 : }
132 :
133 : ///////////////////////////////////////////////////////////////////////////////////////////
134 : // Save: try to save anim to file
135 0 : void CSkeletonAnimDef::Save(const VfsPath& pathname, const CSkeletonAnimDef& anim)
136 : {
137 0 : CFilePacker packer(FILE_VERSION, "PSSA");
138 :
139 : // pack up all the data
140 0 : packer.PackString("");
141 0 : packer.PackRaw(&anim.m_FrameTime,sizeof(anim.m_FrameTime));
142 0 : const size_t numKeys = anim.m_NumKeys;
143 0 : packer.PackSize(numKeys);
144 0 : const size_t numFrames = anim.m_NumFrames;
145 0 : packer.PackSize(numFrames);
146 0 : packer.PackRaw(anim.m_Keys.data(), anim.m_Keys.size() * sizeof(decltype(anim.m_Keys)::value_type));
147 :
148 : // now write it
149 0 : packer.Write(pathname);
150 3 : }
|