/src/aerys/minko/type/parser/collada/resource/controller/Skin.as
ActionScript | 389 lines | 285 code | 66 blank | 38 comment | 47 complexity | 1d6682a59b57b74f9cc9a929c4379180 MD5 | raw file
1package aerys.minko.type.parser.collada.resource.controller
2{
3 import aerys.minko.ns.minko_collada;
4 import aerys.minko.render.geometry.stream.format.VertexComponent;
5 import aerys.minko.render.geometry.stream.format.VertexFormat;
6 import aerys.minko.type.loader.parser.ParserOptions;
7 import aerys.minko.type.math.Matrix4x4;
8 import aerys.minko.type.parser.collada.ColladaDocument;
9 import aerys.minko.type.parser.collada.helper.MeshTemplate;
10 import aerys.minko.type.parser.collada.helper.NumberListParser;
11 import aerys.minko.type.parser.collada.helper.Source;
12 import aerys.minko.type.parser.collada.resource.Geometry;
13
14 import flash.utils.ByteArray;
15 import flash.utils.Endian;
16
17 public class Skin
18 {
19 use namespace minko_collada;
20
21 private static const NS : Namespace = new Namespace("http://www.collada.org/2005/11/COLLADASchema");
22
23 private var _document : ColladaDocument;
24 private var _sourceId : String;
25 private var _bindShapeMatrix : Matrix4x4;
26 private var _jointNames : Vector.<String>;
27 private var _invBindMatrices : Vector.<Matrix4x4>;
28
29 private var _boneWeights : Vector.<Number>
30 private var _numBonesPerVertex : uint;
31
32 private var _meshTemplates : Vector.<MeshTemplate>;
33
34 public function get sourceId() : String { return _sourceId; }
35 public function get bindShapeMatrix() : Matrix4x4 { return _bindShapeMatrix; }
36 public function get jointNames() : Vector.<String> { return _jointNames; }
37 public function get invBindMatrices() : Vector.<Matrix4x4> { return _invBindMatrices; }
38 public function get boneWeights() : Vector.<Number> { return _boneWeights; }
39 public function get numBonesPerVertex() : uint { return _numBonesPerVertex; }
40
41 public function get meshTemplates() : Vector.<MeshTemplate>
42 {
43 return _meshTemplates;
44 }
45
46 public static function createFromXML(xmlNode : XML,
47 document : ColladaDocument) : Skin
48 {
49 var sourceId : String = String(xmlNode.@source).substr(1);
50
51 // retrieve bind shape matrix
52 var xmlBindShapeMatrix : XML = xmlNode.NS::bind_shape_matrix[0];
53 var bindShapeMatrix : Matrix4x4 = xmlBindShapeMatrix != null
54 ? NumberListParser.parseMatrix3D(xmlBindShapeMatrix)
55 : new Matrix4x4();
56
57 // retrieve joints
58 var jointSourceId : String = xmlNode..NS::joints.NS::input.(@semantic == 'JOINT').@source.substring(1);
59 var jointSource : XML = xmlNode..NS::source.(@id == jointSourceId)[0];
60 var jointNames : Vector.<String> = Vector.<String>(Source.createFromXML(jointSource).data);
61
62 // retrieve inverse bind matrices
63 var invBindSourceId : String = xmlNode..NS::joints.NS::input.(@semantic == 'INV_BIND_MATRIX').@source.substring(1);
64 var invBindSource : XML = xmlNode..NS::source.(@id == invBindSourceId)[0];
65 var invBindMatrices : Vector.<Matrix4x4> = Vector.<Matrix4x4>(Source.createFromXML(invBindSource).data);
66
67 // retrieve weights
68 var boneWeights : Vector.<Number> = new Vector.<Number>();
69 var numBonesPerVertex : uint = 0;
70 numBonesPerVertex = parseBoneData(xmlNode, jointNames.length, boneWeights);
71
72 var optimizedNumBonesPerVertex : uint = computeMinBonesPerVertex(boneWeights, numBonesPerVertex);
73 if (optimizedNumBonesPerVertex != numBonesPerVertex)
74 {
75 var optimizedBoneWeights : Vector.<Number> = new Vector.<Number>();
76 reduceNumInfluences(boneWeights, numBonesPerVertex, optimizedNumBonesPerVertex, optimizedBoneWeights);
77
78 boneWeights = optimizedBoneWeights;
79 numBonesPerVertex = optimizedNumBonesPerVertex;
80 }
81 // @fixme creates errors in shader compiler
82 //reduceNumBones(boneWeights, jointNames, invBindMatrices);
83
84 return new Skin(
85 sourceId,
86 bindShapeMatrix,
87 jointNames,
88 invBindMatrices,
89 boneWeights,
90 numBonesPerVertex,
91 document
92 );
93 }
94
95 public function Skin(sourceId : String,
96 bindShapeMatrix : Matrix4x4,
97 jointNames : Vector.<String>,
98 invBindMatrices : Vector.<Matrix4x4>,
99 boneWeight : Vector.<Number>,
100 boneCountPerVertex : uint,
101 document : ColladaDocument)
102 {
103 _sourceId = sourceId;
104 _bindShapeMatrix = bindShapeMatrix;
105 _jointNames = jointNames;
106 _invBindMatrices = invBindMatrices;
107 _boneWeights = boneWeight;
108 _numBonesPerVertex = boneCountPerVertex;
109 _document = document;
110 }
111
112 private static function parseBoneData(xmlSkin : XML,
113 boneCount : uint,
114 bonesData : Vector.<Number>) : uint
115 {
116 var weightsSourceId : String = xmlSkin..NS::vertex_weights.NS::input.(@semantic == 'WEIGHT').@source.substring(1);
117 var weightsSource : XML = xmlSkin..NS::source.(@id == weightsSourceId)[0];
118 var weights : Vector.<Number> = Vector.<Number>(Source.createFromXML(weightsSource).data);
119 var vcount : Vector.<int> = NumberListParser.parseIntList(xmlSkin.NS::vertex_weights.NS::vcount[0]);
120 var v : Vector.<int> = NumberListParser.parseIntList(xmlSkin.NS::vertex_weights.NS::v[0]);
121 var offsetJoint : int = xmlSkin.NS::vertex_weights.NS::input.(@semantic == 'JOINT').@offset;
122 var offsetWeight : int = xmlSkin.NS::vertex_weights.NS::input.(@semantic == 'WEIGHT').@offset;
123 var numInputs : int = xmlSkin..NS::vertex_weights.NS::input.length();
124 var vCountLength : uint = vcount.length;
125 var maxVcount : uint = 0;
126 var i : int = 0;
127 var k : int = 0;
128
129 for (i = 0; i < vCountLength; i++)
130 if (maxVcount < vcount[i])
131 maxVcount = vcount[i];
132
133 for (i = 0; i < vCountLength; i++)
134 {
135 var vc : int = vcount[i];
136
137 for (var j : int = 0; j < vc; j++)
138 {
139 // in collada, the bone numbered -1 references to the bind shape matrix
140 // we push boneId | boneWeight
141
142 if (v[int(k + offsetJoint)] != -1)
143 bonesData.push(v[int(k + offsetJoint)], weights[v[int(k + offsetWeight)]]);
144 else
145 bonesData.push(boneCount, 0);
146 // bonesData.push(boneCount, weights[v[int(k + offsetWeight)]]);
147
148 k += numInputs;
149 }
150
151 for (; j < maxVcount; j++)
152 bonesData.push(0, 0); // boneId, weight
153 }
154
155 return maxVcount;
156 }
157
158 /**
159 * Given a bone weights vector, and the numBonesPerVertex value, determine
160 * if it is possible to optimize it (have a lower numBonesPerVertexValue).
161 *
162 * Some collada exporters are (very) lazy, and put weights for all bones on
163 * all vertices, so that numBonesPerVertex == numBones.
164 *
165 * This is not OK with minko, which is limited by agal to have
166 * at most 8 vertex attribute on shaders (hence the need for this method).
167 */
168 private static function computeMinBonesPerVertex(inBoneWeights : Vector.<Number>,
169 inBoneCountPerVertex : uint) : uint
170 {
171 var vertexCount : uint = inBoneWeights.length / inBoneCountPerVertex / 2;
172 var newBoneCountPerVertex : uint = 0;
173 var vertexIndex : uint;
174 var boneIndex : uint; // iterators
175 var boneInfluenceIndex : uint; // indexes in boneWeights vector
176 var boneInfluence : Number; // values
177 var localBoneCount : uint;
178
179 // start counting how must bones we need in each vertex.
180 for (vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
181 {
182 localBoneCount = 0;
183 for (boneIndex = 0; boneIndex < inBoneCountPerVertex; ++boneIndex)
184 {
185 boneInfluenceIndex = 2 * (vertexIndex * inBoneCountPerVertex + boneIndex) + 1;
186 boneInfluence = inBoneWeights[boneInfluenceIndex];
187
188 if (boneInfluence > 0)
189 ++localBoneCount;
190 }
191
192 if (localBoneCount > newBoneCountPerVertex)
193 newBoneCountPerVertex = localBoneCount;
194 }
195
196 return newBoneCountPerVertex;
197 }
198
199 /**
200 * When the computeMinBonesPerVertex method indicates that it is possible to
201 * optimize the bone weights vector, this method does the work.
202 */
203 private static function reduceNumInfluences(inBoneWeights : Vector.<Number>,
204 inBoneCountPerVertex : uint,
205 newBoneCountPerVertex : uint,
206 outBoneWeights : Vector.<Number>) : void
207 {
208 var vertexCount : uint = inBoneWeights.length / inBoneCountPerVertex / 2;
209
210 var vertexIndex : uint, boneIndex : uint; // iterators
211 var boneIdIndex : uint, boneInfluenceIndex : uint; // indexes in boneWeights vector
212 var boneId : Number, boneInfluence : Number; // values
213 var localBoneCount : uint;
214
215 // rewrite a new boneWeights vector
216 for (vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
217 {
218 localBoneCount = 0;
219 for (boneIndex = 0; boneIndex < inBoneCountPerVertex; ++boneIndex)
220 {
221 boneIdIndex = 2 * (vertexIndex * inBoneCountPerVertex + boneIndex)
222 boneInfluenceIndex = boneIdIndex + 1;
223
224 boneId = inBoneWeights[boneIdIndex];
225 boneInfluence = inBoneWeights[boneInfluenceIndex];
226
227 if (boneInfluence > 0)
228 {
229 outBoneWeights.push(boneId, boneInfluence);
230 ++localBoneCount;
231 }
232 }
233
234 for (; localBoneCount < newBoneCountPerVertex; ++localBoneCount)
235 outBoneWeights.push(0, 0);
236 }
237 }
238
239 private static function reduceNumBones(bonesWeights : Vector.<Number>,
240 jointsNames : Vector.<String>,
241 invBindMatrices : Vector.<Matrix4x4>) : void
242 {
243 var numBones : uint = jointsNames.length;
244 var boneIsUsed : Vector.<Boolean> = new Vector.<Boolean>(numBones, true);
245
246 var weightId : int;
247 var numWeights : uint = bonesWeights.length;
248 for (weightId = 0; weightId < numWeights; weightId += 2)
249 {
250 // if the influence is != 0. (if the bone is used for skinning)
251 if (bonesWeights[uint(weightId + 1)] != 0.)
252 boneIsUsed[bonesWeights[weightId]] = true;
253 }
254
255 // map old to new bone ids
256 var oldBoneIdToNew : Vector.<Number> = new Vector.<Number>(numBones, true);
257 var oldBoneId : int = 0;
258 var newBoneId : int = 0;
259
260 for (oldBoneId = 0; oldBoneId < numBones; ++oldBoneId)
261 if (boneIsUsed[oldBoneId])
262 oldBoneIdToNew[oldBoneId] = newBoneId++;
263 else
264 oldBoneIdToNew[oldBoneId] = -1;
265
266 // update vertexdata
267 for (weightId = 0; weightId < numWeights; weightId += 2)
268 {
269 newBoneId = oldBoneIdToNew[bonesWeights[weightId]];
270
271 if (newBoneId != -1)
272 bonesWeights[weightId] = newBoneId;
273 }
274
275 // update joints list and bind matrices
276 for (oldBoneId = 0; oldBoneId < numBones; ++oldBoneId)
277 {
278 newBoneId = oldBoneIdToNew[oldBoneId];
279
280 if (newBoneId != -1)
281 {
282 jointsNames[newBoneId] = jointsNames[oldBoneId];
283 invBindMatrices[newBoneId] = invBindMatrices[oldBoneId];
284 }
285 }
286
287 jointsNames.length = newBoneId + 1;
288 invBindMatrices.length = newBoneId + 1;
289 }
290
291 /**
292 * Does the same thing that Geometry.computeMeshTemplates, but add bone weights
293 */
294 public function computeMeshTemplates(options : ParserOptions) : void
295 {
296 if (_meshTemplates != null)
297 return;
298
299 var geometry : Geometry = _document.getGeometryById(_sourceId);
300 geometry.computeMeshTemplates(options);
301
302 var meshTemplates : Vector.<MeshTemplate> = geometry.meshTemplates;
303 var numMeshTemplates : uint = meshTemplates.length;
304
305 _meshTemplates = new Vector.<MeshTemplate>(numMeshTemplates, true);
306 for (var meshId : uint = 0; meshId < numMeshTemplates; ++meshId)
307 {
308 var meshTemplate : MeshTemplate = meshTemplates[meshId];
309
310 var oldVertexData : ByteArray = meshTemplate.vertexData;
311 var oldFormat : VertexFormat = meshTemplate.vertexFormat;
312
313 // create vertex format
314 var vertexFormat : VertexFormat = oldFormat.clone();
315
316 // check if loadSkin option is available
317 if (options.loadSkin)
318 {
319 for (var k : uint = 0; k < (_numBonesPerVertex >> 1); ++k)
320 vertexFormat.addComponent(VertexComponent.BONES[k]);
321
322 if (_numBonesPerVertex % 2 == 1)
323 vertexFormat.addComponent(VertexComponent.BONE_S);
324
325 _meshTemplates[meshId] = new MeshTemplate(
326 _sourceId + 'skin',
327 addBoneData(oldVertexData, oldFormat),
328 meshTemplate.indexData,
329 meshTemplate.materialName,
330 vertexFormat
331 );
332 }
333 // otherwise, we use the old vertex data instead of the bone vertex data
334 else {
335 _meshTemplates[meshId] = new MeshTemplate(
336 _sourceId + 'skin',
337 oldVertexData,
338 meshTemplate.indexData,
339 meshTemplate.materialName,
340 vertexFormat
341 );
342 }
343 }
344 }
345
346 private function addBoneData(oldBuffer : ByteArray,
347 oldFormat : VertexFormat) : ByteArray
348 {
349 var startPosition : uint = oldBuffer.position;
350 var oldBytesPerVertex : uint = oldFormat.numBytesPerVertex;
351 var boneBytesPerVertex : uint = _numBonesPerVertex << 3;
352
353 var newBytesPerVertex : uint = oldBytesPerVertex + boneBytesPerVertex;
354
355 var numVertices : uint = oldBuffer.length / oldBytesPerVertex;
356 var newBuffer : ByteArray = new ByteArray();
357
358 var oldReadOffset : uint = 0;
359 var oldVertexIdOffset : uint = oldFormat.getBytesOffsetForComponent(VertexComponent.ID);
360 var newWriteOffset : uint = 0;
361
362 newBuffer.endian = Endian.LITTLE_ENDIAN;
363
364 for (var i : uint = 0; i < numVertices; ++i)
365 {
366 // copy old vertex to new one
367 newBuffer.writeBytes(oldBuffer, oldReadOffset, oldBytesPerVertex);
368
369 // read from old buffer where to read the new bone
370 oldBuffer.position = oldVertexIdOffset;
371
372 var boneReadOffset : uint = (boneBytesPerVertex >>> 2) * oldBuffer.readFloat();
373 var boneReadLimit : uint = boneReadOffset + (boneBytesPerVertex >>> 2);
374
375 for (; boneReadOffset < boneReadLimit; ++boneReadOffset)
376 newBuffer.writeFloat(_boneWeights[boneReadOffset]);
377
378 // increment iterators
379 oldReadOffset += oldBytesPerVertex
380 oldVertexIdOffset += oldBytesPerVertex;
381 }
382
383 oldBuffer.position = startPosition;
384 newBuffer.position = 0;
385
386 return newBuffer;
387 }
388 }
389}