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 : #include "precompiled.h"
19 :
20 : #include "libxml/parser.h"
21 : #include "libxml/xmlerror.h"
22 :
23 : #include "StdSkeletons.h"
24 :
25 : #include "CommonConvert.h"
26 :
27 : #include "FUtils/FUXmlParser.h"
28 :
29 : #include <map>
30 :
31 : namespace
32 : {
33 2 : struct SkeletonMap : public std::map<std::string, const Skeleton*>
34 : {
35 2 : ~SkeletonMap()
36 2 : {
37 2 : for (iterator it = begin(); it != end(); ++it)
38 0 : delete it->second;
39 2 : }
40 : };
41 :
42 1 : SkeletonMap g_StandardSkeletons;
43 1 : SkeletonMap g_MappedSkeletons;
44 :
45 0 : struct Bone
46 : {
47 : std::string parent;
48 : std::string name;
49 : int targetId;
50 : int realTargetId;
51 : };
52 : }
53 :
54 0 : struct Skeleton_impl
55 : {
56 : std::string title;
57 : std::vector<Bone> bones;
58 : const Skeleton* target;
59 : };
60 :
61 0 : Skeleton::Skeleton() : m(new Skeleton_impl) { }
62 0 : Skeleton::~Skeleton() { }
63 :
64 0 : const Skeleton* Skeleton::FindSkeleton(const std::string& name)
65 : {
66 0 : return g_MappedSkeletons[name];
67 : }
68 :
69 0 : int Skeleton::GetBoneID(const std::string& name) const
70 : {
71 0 : for (size_t i = 0; i < m->bones.size(); ++i)
72 0 : if (m->bones[i].name == name)
73 0 : return m->bones[i].targetId;
74 0 : return -1;
75 : }
76 :
77 0 : int Skeleton::GetRealBoneID(const std::string& name) const
78 : {
79 0 : for (size_t i = 0; i < m->bones.size(); ++i)
80 0 : if (m->bones[i].name == name)
81 0 : return m->bones[i].realTargetId;
82 0 : return -1;
83 : }
84 :
85 0 : int Skeleton::GetBoneCount() const
86 : {
87 0 : return (int)m->target->m->bones.size();
88 : }
89 :
90 : namespace
91 : {
92 0 : bool AlreadyUsedTargetBone(const std::vector<Bone>& bones, int targetId)
93 : {
94 0 : for (size_t i = 0; i < bones.size(); ++i)
95 0 : if (bones[i].targetId == targetId)
96 0 : return true;
97 0 : return false;
98 : }
99 :
100 : // Recursive helper function used by LoadSkeletonData
101 0 : void LoadSkeletonBones(xmlNode* parent, std::vector<Bone>& bones, const Skeleton* targetSkeleton, const std::string& targetName)
102 : {
103 0 : xmlNodeList boneNodes;
104 0 : FUXmlParser::FindChildrenByType(parent, "bone", boneNodes);
105 0 : for (xmlNodeList::iterator boneNode = boneNodes.begin(); boneNode != boneNodes.end(); ++boneNode)
106 : {
107 0 : std::string name (FUXmlParser::ReadNodeProperty(*boneNode, "name"));
108 :
109 0 : Bone b;
110 0 : b.name = name;
111 :
112 0 : std::string newTargetName = targetName;
113 :
114 0 : if (targetSkeleton)
115 : {
116 0 : xmlNode* targetNode = FUXmlParser::FindChildByType(*boneNode, "target");
117 0 : if (targetNode)
118 0 : newTargetName = FUXmlParser::ReadNodeContentFull(targetNode);
119 : // else fall back to the parent node's target
120 :
121 0 : b.targetId = targetSkeleton->GetBoneID(newTargetName);
122 0 : REQUIRE(b.targetId != -1, "skeleton bone target matches some standard_skeleton bone name");
123 :
124 0 : if (AlreadyUsedTargetBone(bones, b.targetId))
125 0 : b.realTargetId = -1;
126 : else
127 0 : b.realTargetId = b.targetId;
128 : }
129 : else
130 : {
131 : // No target - this is a standard skeleton
132 :
133 0 : b.targetId = (int)bones.size();
134 0 : b.realTargetId = b.targetId;
135 : }
136 :
137 0 : bones.push_back(b);
138 :
139 0 : LoadSkeletonBones(*boneNode, bones, targetSkeleton, newTargetName);
140 : }
141 0 : }
142 :
143 5 : void LoadSkeletonData(xmlNode* root)
144 : {
145 10 : xmlNodeList skeletonNodes;
146 5 : FUXmlParser::FindChildrenByType(root, "standard_skeleton", skeletonNodes);
147 5 : FUXmlParser::FindChildrenByType(root, "skeleton", skeletonNodes);
148 5 : for (xmlNodeList::iterator skeletonNode = skeletonNodes.begin();
149 5 : skeletonNode != skeletonNodes.end(); ++skeletonNode)
150 : {
151 0 : std::unique_ptr<Skeleton> skeleton = std::make_unique<Skeleton>();
152 :
153 0 : std::string title (FUXmlParser::ReadNodeProperty(*skeletonNode, "title"));
154 :
155 0 : skeleton->m->title = title;
156 :
157 0 : if (IsEquivalent((*skeletonNode)->name, "standard_skeleton"))
158 : {
159 0 : skeleton->m->target = NULL;
160 :
161 0 : LoadSkeletonBones(*skeletonNode, skeleton->m->bones, NULL, "");
162 :
163 0 : std::string id (FUXmlParser::ReadNodeProperty(*skeletonNode, "id"));
164 0 : REQUIRE(! id.empty(), "standard_skeleton has id");
165 :
166 0 : g_StandardSkeletons[id] = skeleton.release();
167 : }
168 : else
169 : {
170 : // Non-standard skeletons need to choose a standard skeleton
171 : // as their target to be mapped onto
172 :
173 0 : std::string target (FUXmlParser::ReadNodeProperty(*skeletonNode, "target"));
174 0 : const Skeleton* targetSkeleton = g_StandardSkeletons[target];
175 0 : REQUIRE(targetSkeleton != NULL, "skeleton target matches some standard_skeleton id");
176 :
177 0 : skeleton->m->target = targetSkeleton;
178 :
179 0 : LoadSkeletonBones(*skeletonNode, skeleton->m->bones, targetSkeleton, "");
180 :
181 : // Currently the only supported identifier is a precise name match,
182 : // so just look for that
183 0 : xmlNode* identifier = FUXmlParser::FindChildByType(*skeletonNode, "identifier");
184 0 : REQUIRE(identifier != NULL, "skeleton has <identifier>");
185 0 : xmlNode* identRoot = FUXmlParser::FindChildByType(identifier, "root");
186 0 : REQUIRE(identRoot != NULL, "skeleton identifier has <root>");
187 0 : std::string identRootName (FUXmlParser::ReadNodeContentFull(identRoot));
188 :
189 0 : g_MappedSkeletons[identRootName] = skeleton.release();
190 : }
191 : }
192 5 : }
193 : }
194 :
195 : void errorHandler(void* ctx, const char* msg, ...);
196 :
197 6 : void Skeleton::LoadSkeletonDataFromXml(const char* xmlData, size_t xmlLength, std::string& xmlErrors)
198 : {
199 6 : xmlDoc* doc = NULL;
200 : try
201 : {
202 6 : xmlSetGenericErrorFunc(&xmlErrors, &errorHandler);
203 6 : doc = xmlParseMemory(xmlData, (int)xmlLength);
204 6 : if (doc)
205 : {
206 5 : xmlNode* root = xmlDocGetRootElement(doc);
207 5 : LoadSkeletonData(root);
208 5 : xmlFreeDoc(doc);
209 5 : doc = NULL;
210 : }
211 6 : xmlSetGenericErrorFunc(NULL, NULL);
212 : }
213 0 : catch (const ColladaException&)
214 : {
215 0 : if (doc)
216 0 : xmlFreeDoc(doc);
217 0 : xmlSetGenericErrorFunc(NULL, NULL);
218 0 : throw;
219 : }
220 :
221 6 : if (! xmlErrors.empty())
222 1 : throw ColladaException("XML parsing failed");
223 8 : }
|