1 #define NOMINMAX
2 #include <maya/MAnimControl.h>
3 #include <maya/MDagPath.h>
4 #include <maya/MDagPathArray.h>
5 #include <maya/MDistance.h>
6 #include <maya/MEulerRotation.h>
7 #include <maya/MFnEnumAttribute.h>
8 #include <maya/MFnIkJoint.h>
9 #include <maya/MFnMesh.h>
10 #include <maya/MFnSet.h>
11 #include <maya/MFnSingleIndexedComponent.h>
12 #include <maya/MFnSkinCluster.h>
13 #include <maya/MFnTransform.h>
14 #include <maya/MGlobal.h>
15 #include <maya/MIntArray.h>
16 #include <maya/MItDag.h>
17 #include <maya/MItDependencyNodes.h>
18 #include <maya/MItMeshEdge.h>
19 #include <maya/MItMeshPolygon.h>
20 #include <maya/MItMeshVertex.h>
21 #include <maya/MItSelectionList.h>
22 #include <maya/MPlug.h>
23 #include <maya/MPlugArray.h>
24 #include <maya/MPointArray.h>
25 #include <maya/MSelectionList.h>
26 #include "maya_export_tools.h"
27 #include "xr_object.h"
28 #include "xr_skl_motion.h"
29 #include "xr_envelope.h"
30 #include "xr_utils.h"
31 
32 using namespace xray_re;
33 
extract_bones(MFnSkinCluster & skin_fn,xr_bone_vec & bones)34 static MStatus extract_bones(MFnSkinCluster& skin_fn, xr_bone_vec& bones)
35 {
36 	MStatus status;
37 
38 	MDagPathArray joints;
39 	skin_fn.influenceObjects(joints, &status);
40 	unsigned num_joints = joints.length();
41 	if (num_joints == 0) {
42 		msg("can't find any influence object");
43 		return MS::kInvalidParameter;
44 	} else if (num_joints > MAX_BONES) {
45 		msg("too many bones (%u > %u)", num_joints, MAX_BONES);
46 	}
47 
48 	bones.resize(num_joints);
49 
50 	MString command("dagPose -r -g -bp ");
51 	for (unsigned i = num_joints; i != 0;) {
52 		MFnIkJoint joint_fn(joints[--i], &status);
53 		if (!status) {
54 			msg("can't handle non-joint node %s",
55 					joints[i].partialPathName().asChar());
56 			return status;
57 		}
58 		command += joint_fn.partialPathName();
59 		command += " ";
60 
61 		xr_bone* bone = new xr_bone;
62 		bones[i] = bone;
63 		const char* name = joint_fn.name().asChar();
64 		bone->vmap_name() = bone->name() = name;
65 
66 		unsigned num_parents = joint_fn.parentCount();
67 		if (num_parents > 1) {
68 			msg("can't handle multi-parented joint %s", name);
69 			return MS::kInvalidParameter;
70 		} else if (num_parents == 1) {
71 			MObject parent_obj = joint_fn.parent(0);
72 			if (parent_obj.hasFn(MFn::kJoint) &&
73 					joint_fn.setObject(parent_obj)) {
74 				bone->parent_name() = joint_fn.name().asChar();
75 			}
76 		}
77 	}
78 
79 	xr_bone* root = 0;
80 	for (xr_bone_vec_it it = bones.begin(), end = bones.end(); it != end; ++it) {
81 		xr_bone* bone = *it;
82 		xr_assert(bone);
83 		if (bone->parent_name().empty()) {
84 			if (root == 0) {
85 				root = bone;
86 				continue;
87 			} else {
88 				msg("can't handle multiple root joints in skeleton");
89 				return MS::kInvalidParameter;
90 			}
91 		}
92 		xr_bone* parent = find_by_name(bones, bone->parent_name());
93 		if (parent == 0) {
94 			msg("can't find parent bone %s", bone->parent_name().c_str());
95 			return MS::kFailure;
96 		}
97 		parent->children().push_back(bone);
98 	}
99 	if (root == 0) {
100 		msg("can't find root joint");
101 		return MS::kInvalidParameter;
102 	}
103 
104 	if (!(status = MGlobal::executeCommand(command))) {
105 		msg("can't set skeleton to bind pose");
106 		return MS::kFailure;
107 	}
108 
109 	for (unsigned i = num_joints; i != 0;) {
110 		MFnIkJoint joint_fn(joints[--i]);
111 		xr_bone* bone = bones[i];
112 
113 		MVector t = joint_fn.getTranslation(MSpace::kTransform, &status);
114 		CHECK_MSTATUS(status);
115 		bone->bind_offset().set(float(MDistance(t.x, MDistance::kCentimeters).asMeters()),
116 				float(MDistance(t.y, MDistance::kCentimeters).asMeters()),
117 				float(MDistance(-t.z, MDistance::kCentimeters).asMeters()));
118 
119 		MEulerRotation r;
120 		status = joint_fn.getRotation(r);
121 		CHECK_MSTATUS(status);
122 		r.reorderIt(MEulerRotation::kZXY);
123 		bone->bind_rotate().set(float(-r.x), float(-r.y), float(r.z));
124 	}
125 
126 	return status;
127 }
128 
extract_points(MFnMesh & mesh_fn,std::vector<fvector3> & points,fbox & aabb)129 static MStatus extract_points(MFnMesh& mesh_fn, std::vector<fvector3>& points, fbox& aabb)
130 {
131 	MStatus status;
132 
133 	int num_points = mesh_fn.numVertices();
134 	if (num_points < 4) {
135 		msg("can't export mesh %s with less than four vertices",
136 				mesh_fn.name().asChar());
137 		return MS::kInvalidParameter;
138 	}
139 	points.reserve(size_t(num_points & INT_MAX));
140 	aabb.invalidate();
141 	MPoint p0;
142 	fvector3 p;
143 	for (int i = 0; i != num_points; ++i) {
144 		mesh_fn.getPoint(i, p0);
145 		p0.cartesianize();
146 		aabb.extend(p.set(float(MDistance(p0.x, MDistance::kCentimeters).asMeters()),
147 				float(MDistance(p0.y, MDistance::kCentimeters).asMeters()),
148 				float(MDistance(-p0.z, MDistance::kCentimeters).asMeters())));
149 		points.push_back(p);
150 	}
151 	return status;
152 }
153 
extract_faces(MFnMesh & mesh_fn,lw_face_vec & faces)154 static MStatus extract_faces(MFnMesh& mesh_fn, lw_face_vec& faces)
155 {
156 	MStatus status;
157 
158 	// FIXME: it would be nice to support automatic triangulation using getTriangles() etc.
159 	int num_polys = mesh_fn.numPolygons(&status);
160 	if (num_polys < 2) {
161 		msg("can't export mesh %s with less than two faces",
162 				mesh_fn.name().asChar());
163 		return MS::kInvalidParameter;
164 	}
165 	faces.reserve(size_t(num_polys & INT_MAX));
166 	MIntArray verts;
167 	for (int i = 0; i != num_polys; ++i) {
168 		status = mesh_fn.getPolygonVertices(i, verts);
169 		if (!status || verts.length() != 3) {
170 			msg("can't handle polygons with 4 or more sides");
171 			return MS::kInvalidParameter;
172 		}
173 		lw_face face(verts[2], verts[1], verts[0]);
174 		faces.push_back(face);
175 	}
176 	return status;
177 }
178 
extract_uvs(MFnMesh & mesh_fn,lw_face_vec & faces,lw_vmref_vec & vmrefs,xr_vmap_vec & vmaps)179 static MStatus extract_uvs(MFnMesh& mesh_fn, lw_face_vec& faces,
180 		lw_vmref_vec& vmrefs, xr_vmap_vec& vmaps)
181 {
182 	MStatus status;
183 
184 	xr_uv_vmap* uv_vmap = 0;
185 	xr_face_uv_vmap* face_uv_vmap = 0;
186 
187 	for (MItMeshVertex it(mesh_fn.object()); !it.isDone(); it.next()) {
188 		uint32_t vert_idx = uint32_t(it.index() & INT_MAX);
189 
190 		fvector2 uv0;
191 		if (!it.getUV(uv0.xy)) {
192 			msg("can't extract shared UVs for vert %"PRIu32, vert_idx);
193 			return MS::kInvalidParameter;
194 		}
195 		uv0.v = 1.f - uv0.v;
196 
197 		if (uv_vmap == 0) {
198 			uv_vmap = new xr_uv_vmap("Texture");
199 			uv_vmap->reserve(size_t(mesh_fn.numVertices() & INT_MAX));
200 			vmaps.push_back(uv_vmap);
201 		}
202 		lw_vmref vmref0;
203 		vmref0.push_back(lw_vmref_entry(0, uv_vmap->add_uv(uv0, vert_idx)));
204 
205 		uint32_t vmref0_idx = uint32_t(vmrefs.size() & UINT32_MAX);
206 		vmrefs.push_back(vmref0);
207 
208 		MIntArray adjacents;
209 		it.getConnectedFaces(adjacents);
210 		for (unsigned i = adjacents.length(); i != 0;) {
211 			uint32_t face_idx = uint32_t(adjacents[--i] & INT_MAX), vmref_idx;
212 
213 			fvector2 uv;
214 			if (!it.getUV(face_idx, uv.xy)) {
215 				msg("can't extract UVs for vert %" PRIu32 " face %"PRIu32, vert_idx, face_idx);
216 				uv = uv0;
217 			}
218 			uv.v = 1.f - uv.v;
219 
220 			lw_face& face = faces[face_idx];
221 			if (uv == uv0) {
222 				vmref_idx = vmref0_idx;
223 			} else {
224 				if (face_uv_vmap == 0) {
225 					face_uv_vmap = new xr_face_uv_vmap("Texture");
226 					vmaps.push_back(face_uv_vmap);
227 				}
228 				lw_vmref vmref;
229 				vmref.push_back(lw_vmref_entry(1, face_uv_vmap->add_uv(uv, vert_idx, face_idx)));
230 				vmref_idx = uint32_t(vmrefs.size() & UINT32_MAX);
231 				vmrefs.push_back(vmref);
232 			}
233 			for (uint_fast32_t j = 3; j != 0;) {
234 				if (face.v[--j] == vert_idx) {
235 					face.ref[j] = vmref_idx;
236 					vmref_idx = UINT32_MAX;
237 					break;
238 				}
239 			}
240 			xr_assert(vmref_idx == UINT32_MAX);
241 		}
242 	}
243 	return status;
244 }
245 
extract_weights(MFnMesh & mesh_fn,MFnSkinCluster & skin_fn,lw_face_vec & faces,lw_vmref_vec & vmrefs,xr_vmap_vec & vmaps)246 static MStatus extract_weights(MFnMesh& mesh_fn, MFnSkinCluster& skin_fn,
247 		lw_face_vec& faces, lw_vmref_vec& vmrefs, xr_vmap_vec& vmaps)
248 {
249 	MStatus status;
250 
251 	MDagPathArray joints;
252 	skin_fn.influenceObjects(joints, &status);
253 	CHECK_MSTATUS(status);
254 
255 	// collect bone weights in vmaps and build vertex-ordered refs
256 	lw_vmref_vec weight_vmrefs(size_t(mesh_fn.numVertices() & INT_MAX));
257 	for (unsigned joint_idx = joints.length(); joint_idx != 0;) {
258 		MDoubleArray weights;
259 		MSelectionList affected;
260 		status = skin_fn.getPointsAffectedByInfluence(joints[--joint_idx], affected, weights);
261 		CHECK_MSTATUS(status);
262 		if (affected.isEmpty())
263 			continue;
264 
265 		MFnIkJoint joint_fn(joints[joint_idx], &status);
266 		CHECK_MSTATUS(status);
267 
268 msg("joint=%s", joint_fn.name().asChar());
269 
270 		xr_weight_vmap* weight_vmap = new xr_weight_vmap(joint_fn.name().asChar());
271 		weight_vmap->reserve(weights.length());
272 		uint32_t vmap_idx = uint32_t(vmaps.size() & UINT32_MAX);
273 		vmaps.push_back(weight_vmap);
274 
275 msg("  num_affected=%u, num_weights=%u", affected.length(), weights.length());
276 
277 		// FIXME: is it enough to expect the single element in the list here?
278 		for (unsigned i = affected.length(), k = weights.length(); i != 0;) {
279 			MDagPath dag_path;
280 			MObject component_obj;
281 			status = affected.getDagPath(--i, dag_path, component_obj);
282 			CHECK_MSTATUS(status);
283 			MFnSingleIndexedComponent component_fn(component_obj, &status);
284 			CHECK_MSTATUS(status);
285 			for (int j = component_fn.elementCount() - 1; j >= 0; --j) {
286 				int vert_idx = component_fn.element(j, &status);
287 				CHECK_MSTATUS(status);
288 				uint32_t weight_idx = weight_vmap->add_weight(float(weights[--k]), uint32_t(vert_idx & INT_MAX));
289 				xr_assert(!weight_vmrefs[vert_idx].full());
290 				weight_vmrefs[vert_idx].push_back(lw_vmref_entry(vmap_idx, weight_idx));
291 			}
292 		}
293 	}
294 
295 	// merge weight vmrefs with the already extracted uv vmrefs
296 	for (lw_face_vec_it it = faces.begin(), end = faces.end(); it != end; ++it) {
297 		for (uint_fast32_t i = 3; i != 0;) {
298 			lw_vmref& vmref = vmrefs[it->ref[--i]];
299 			if (vmref.size() > 1)
300 				continue;
301 			vmref.append(weight_vmrefs[it->v[i]]);
302 		}
303 	}
304 
305 	return status;
306 }
307 
get_xraymtl_attr(MFnDependencyNode & dep_fn,const char * name,std::string & value)308 static void get_xraymtl_attr(MFnDependencyNode& dep_fn, const char* name, std::string& value)
309 {
310 	MStatus status;
311 	MPlug plug = dep_fn.findPlug(name, &status);
312 	if (status) {
313 		MFnEnumAttribute attr_fn(plug.attribute(&status), &status);
314 		if (status) {
315 			MString temp = attr_fn.fieldName(plug.asShort(), &status);
316 			if (status) {
317 				value = temp.asChar();
318 				for (std::string::size_type i = 0; (i = value.find('/', i)) != std::string::npos; ++i)
319 					value[i] = '\\';
320 				return;
321 			}
322 		}
323 	}
324 	msg("can't get attribute %s", name);
325 }
326 
create_surface(const char * surf_name,MFnSet & set_fn)327 xr_surface* maya_export_tools::create_surface(const char* surf_name, MFnSet& set_fn)
328 {
329 	xr_surface* surface = new xr_surface(m_skeletal);
330 	surface->name() = surf_name;
331 
332 	MStatus status;
333 	MPlugArray connected_plugs;
334 	set_fn.findPlug("ss").connectedTo(connected_plugs, true, false, &status);
335 	MObject shader_obj;
336 	for (unsigned i = connected_plugs.length(); i != 0;) {
337 		MObject obj = connected_plugs[--i].node();
338 		MFnDependencyNode dep_fn(obj);
339 		if (dep_fn.typeName() == "XRayMtl") {
340 			shader_obj = obj;
341 			get_xraymtl_attr(dep_fn, "xrayGameMaterial", surface->gamemtl());
342 			get_xraymtl_attr(dep_fn, "xrayEngineShader", surface->eshader());
343 			get_xraymtl_attr(dep_fn, "xrayCompilerShader", surface->cshader());
344 			MPlug plug = dep_fn.findPlug("xrayDoubleSide", &status);
345 			if (status && plug.asBool())
346 				surface->set_two_sided();
347 			break;
348 		} else if (obj.hasFn(MFn::kLambert) || obj.hasFn(MFn::kPhong) || obj.hasFn(MFn::kBlinn)) {
349 			if (shader_obj.isNull())
350 				shader_obj = obj;
351 		}
352 	}
353 	if (shader_obj.isNull()) {
354 		msg("can't find shader node for surface %s", surf_name);
355 		return surface;
356 	}
357 
358 	MFnDependencyNode shader_fn(shader_obj);
359 	shader_fn.findPlug("c").connectedTo(connected_plugs, true, false);
360 	for (unsigned i = connected_plugs.length(); i != 0;) {
361 		MFnDependencyNode dep_fn(connected_plugs[--i].node());
362 		if (dep_fn.typeName() == "file") {
363 			MString file_path = dep_fn.findPlug("ftn").asString();
364 			if (file_path.numChars()) {
365 				int i, j;
366 				if ((i = file_path.rindexW('/')) < 0)
367 					i = file_path.rindexW('\\');
368 				if ((j = file_path.rindexW('.')) < 0)
369 					j = file_path.numChars();
370 				MString name(file_path.substringW(i + 1, j - 1));
371 				if ((i = name.indexW('_')) > 0) {
372 					surface->texture() = (name.substringW(0, i - 1) + "\\" + name).asChar();
373 				} else {
374 					surface->texture() = name.asChar();
375 				}
376 			}
377 			break;
378 		}
379 	}
380 	return surface;
381 }
382 
extract_surfaces(MFnMesh & mesh_fn,xr_surfmap_vec & surfmaps)383 MStatus maya_export_tools::extract_surfaces(MFnMesh& mesh_fn, xr_surfmap_vec& surfmaps)
384 {
385 	MObjectArray shading_groups;
386 	MIntArray faces;
387 	MStatus status = mesh_fn.getConnectedShaders(0, shading_groups, faces);
388 	if (!status) {
389 		msg("can't get connected shaders for mesh %s", mesh_fn.name().asChar());
390 		return MS::kInvalidParameter;
391 	}
392 	surfmaps.resize(shading_groups.length());
393 	for (unsigned i = faces.length(); i != 0;) {
394 		xr_surfmap* smap = surfmaps[faces[--i]];
395 		if (smap == 0) {
396 			MFnSet set_fn(shading_groups[faces[i]], &status);
397 			CHECK_MSTATUS(status);
398 			const char* surf_name = set_fn.name().asChar();
399 			xr_surface*& surface = m_shared_surfaces[surf_name];
400 			if (surface == 0) {
401 				smap = new xr_surfmap(create_surface(surf_name, set_fn));
402 				surface = smap->surface;
403 			} else {
404 				smap = new xr_surfmap(surface);
405 			}
406 			surfmaps[faces[i]] = smap;
407 		}
408 		smap->faces.push_back(i);
409 	}
410 	return status;
411 }
412 
413 struct temp_edge {
414 			temp_edge();
415 	uint32_t	faces[2];
416 };
417 
temp_edge()418 inline temp_edge::temp_edge() { faces[0] = UINT32_MAX; faces[1] = UINT32_MAX; }
419 
420 struct temp_face {
421 	uint32_t	edges[3];
422 };
423 
424 // FIXME: consider using 3ds Max smoothing groups.
extract_smoothing_groups(MFnMesh & mesh_fn,std::vector<uint32_t> & sgroups)425 static MStatus extract_smoothing_groups(MFnMesh& mesh_fn, std::vector<uint32_t>& sgroups)
426 {
427 	MStatus status = MS::kSuccess;
428 
429 	MIntArray connected;
430 
431 	temp_edge* temp_edges = new temp_edge[unsigned(mesh_fn.numEdges() & INT_MAX)];
432 	for (MItMeshEdge it(mesh_fn.object()); !it.isDone(); it.next()) {
433 		if (it.isSmooth()) {
434 			it.getConnectedFaces(connected, &status);
435 			CHECK_MSTATUS(status);
436 			unsigned n = connected.length();
437 			if (n <= 2) {
438 				temp_edge& edge = temp_edges[it.index()];
439 				while (n) {
440 					--n;
441 					edge.faces[n] = uint32_t(connected[n] & INT_MAX);
442 				}
443 			}
444 		}
445 	}
446 
447 	unsigned num_faces = unsigned(mesh_fn.numPolygons() & INT_MAX);
448 	temp_face* temp_faces = new temp_face[num_faces];
449 	for (MItMeshPolygon it(mesh_fn.object()); !it.isDone(); it.next()) {
450 		status = it.getEdges(connected);
451 		CHECK_MSTATUS(status);
452 		unsigned n = connected.length();
453 		if (n != 3) {
454 			msg("can't build smoothing groups");
455 			delete[] temp_edges;
456 			delete[] temp_faces;
457 			return MS::kInvalidParameter;
458 		}
459 		temp_face& face = temp_faces[it.index(&status)];
460 		CHECK_MSTATUS(status);
461 		while (n) {
462 			--n;
463 			face.edges[n] = uint32_t(connected[n] & INT_MAX);
464 		}
465 	}
466 
467 	sgroups.assign(num_faces, EMESH_NO_SG);
468 	std::vector<uint32_t> adjacents;
469 	adjacents.reserve(512);
470 	uint32_t sgroup = 0;
471 	for (uint_fast32_t base_idx = num_faces; base_idx != 0;) {
472 		if (sgroups[--base_idx] != EMESH_NO_SG)
473 			continue;
474 		bool new_sgroup = false;
475 		for (uint_fast32_t face_idx = base_idx;;) {
476 			const temp_face& face = temp_faces[face_idx];
477 			for (uint_fast32_t i = 3; i != 0;) {
478 				temp_edge& edge = temp_edges[face.edges[--i]];
479 				if (edge.faces[0] == UINT32_MAX || edge.faces[1] == UINT32_MAX)
480 					continue;
481 				if (!new_sgroup) {
482 					new_sgroup = true;
483 					sgroups[face_idx] = sgroup;
484 				}
485 				uint32_t adj_face_idx = edge.faces[0] == face_idx ?
486 						edge.faces[1] : edge.faces[0];
487 				if (sgroups[adj_face_idx] == EMESH_NO_SG) {
488 					adjacents.push_back(adj_face_idx);
489 					sgroups[adj_face_idx] = sgroup;
490 				}
491 			}
492 			if (adjacents.empty())
493 				break;
494 			face_idx = adjacents.back();
495 			adjacents.pop_back();
496 		}
497 		if (new_sgroup)
498 			++sgroup;
499 	}
500 
501 	delete[] temp_edges;
502 	delete[] temp_faces;
503 
504 	return status;
505 }
506 
commit_surfaces(xr_surface_vec & surfaces)507 void maya_export_tools::commit_surfaces(xr_surface_vec& surfaces)
508 {
509 	surfaces.reserve(m_shared_surfaces.size());
510 	for (xr_surface_map_it it = m_shared_surfaces.begin(),
511 			end = m_shared_surfaces.end(); it != end; ++it) {
512 		surfaces.push_back(it->second);
513 	}
514 }
515 
create_object(MObjectArray & mesh_objs)516 xr_object* maya_export_tools::create_object(MObjectArray& mesh_objs)
517 {
518 	MStatus status;
519 
520 	xr_object* object = new xr_object;
521 	object->flags() = EOF_STATIC;
522 	object->meshes().reserve(mesh_objs.length());
523 
524 	for (unsigned i = mesh_objs.length(); i != 0;) {
525 		MFnMesh mesh_fn(mesh_objs[--i]);
526 
527 		xr_mesh* mesh = new xr_mesh;
528 		object->meshes().push_back(mesh);
529 		mesh->name() = mesh_fn.name().asChar();
530 
531 		if (!(status = extract_points(mesh_fn, mesh->points(), mesh->bbox())))
532 			goto fail;
533 
534 		if (!(status = extract_faces(mesh_fn, mesh->faces())))
535 			goto fail;
536 
537 		if (!(status = extract_uvs(mesh_fn, mesh->faces(), mesh->vmrefs(), mesh->vmaps())))
538 			goto fail;
539 
540 		if (!(status = extract_surfaces(mesh_fn, mesh->surfmaps())))
541 			goto fail;
542 
543 		if (!(status = extract_smoothing_groups(mesh_fn, mesh->sgroups())))
544 			goto fail;
545 	}
546 
547 	commit_surfaces(object->surfaces());
548 
549 	return object;
550 
551 fail:
552 	delete object;
553 	return 0;
554 }
555 
create_skl_object(MObject & mesh_obj,MObject & skin_obj)556 xr_object* maya_export_tools::create_skl_object(MObject& mesh_obj, MObject& skin_obj)
557 {
558 	MStatus status;
559 
560 	xr_object* object = new xr_object;
561 	object->flags() = EOF_DYNAMIC;
562 
563 	MFnMesh mesh_fn(mesh_obj);
564 
565 	xr_mesh* mesh = new xr_mesh;
566 	// attach now to allow auto-deletion in case of error
567 	object->meshes().push_back(mesh);
568 	mesh->name() = mesh_fn.name().asChar();
569 
570 	MFnSkinCluster skin_fn(skin_obj);
571 	if (!(status = extract_bones(skin_fn, object->bones())))
572 		goto fail;
573 
574 	if (!(status = extract_points(mesh_fn, mesh->points(), mesh->bbox())))
575 		goto fail;
576 
577 	if (!(status = extract_faces(mesh_fn, mesh->faces())))
578 		goto fail;
579 
580 	if (!(status = extract_uvs(mesh_fn, mesh->faces(), mesh->vmrefs(), mesh->vmaps())))
581 		goto fail;
582 
583 	if (!(status = extract_weights(mesh_fn, skin_fn, mesh->faces(), mesh->vmrefs(), mesh->vmaps())))
584 		goto fail;
585 
586 	if (!(status = extract_surfaces(mesh_fn, mesh->surfmaps())))
587 		goto fail;
588 
589 	if (!(status = extract_smoothing_groups(mesh_fn, mesh->sgroups())))
590 		goto fail;
591 
592 	object->partitions().push_back(new xr_partition(object->bones()));
593 
594 	commit_surfaces(object->surfaces());
595 
596 	return object;
597 
598 fail:
599 	delete object;
600 	return 0;
601 }
602 
collect_meshes(MObjectArray & mesh_objs,MObject & root_obj=MObject::kNullObj)603 static void collect_meshes(MObjectArray& mesh_objs, MObject& root_obj = MObject::kNullObj)
604 {
605 	MItDag dag_it;
606 	if (!root_obj.isNull() && !dag_it.reset(root_obj))
607 		return;
608 
609 	MStatus status;
610 	for (MDagPath dag_path; !dag_it.isDone(); dag_it.next()) {
611 		status = dag_it.getPath(dag_path);
612 		if (!status)
613 			continue;
614 		MFnDagNode dag_fn(dag_path);
615 		if (dag_fn.isIntermediateObject())
616 			continue;
617 		if (!dag_path.hasFn(MFn::kTransform))
618 			continue;
619 		for (unsigned i = dag_fn.childCount(); i != 0;) {
620 			MObject obj = dag_fn.child(--i);
621 			if (obj.hasFn(MFn::kMesh)) {
622 				MFnMesh mesh_fn(obj);
623 				if (!mesh_fn.isIntermediateObject())
624 					mesh_objs.append(obj);
625 			}
626 		}
627 	}
628 }
629 
collect_meshes(MObjectArray & mesh_objs,bool selection_only)630 static void collect_meshes(MObjectArray& mesh_objs, bool selection_only)
631 {
632 	if (selection_only) {
633 		MSelectionList selection;
634 		MStatus status = MGlobal::getActiveSelectionList(selection);
635 		if (!status || selection.isEmpty())
636 			return;
637 		MDagPath dag_path;
638 		for (MItSelectionList it(selection); !it.isDone(); it.next()) {
639 			if (!it.getDagPath(dag_path))
640 				continue;
641 			MObject root_obj = dag_path.node(&status);
642 			if (status)
643 				collect_meshes(mesh_objs, root_obj);
644 		}
645 	} else {
646 		collect_meshes(mesh_objs);
647 	}
648 }
649 
export_object(const char * path,bool selection_only)650 MStatus maya_export_tools::export_object(const char* path, bool selection_only)
651 {
652 	MObjectArray mesh_objs;
653 	collect_meshes(mesh_objs, selection_only);
654 	if (mesh_objs.length() == 0) {
655 		msg("can't find any mesh to export");
656 		return MS::kFailure;
657 	}
658 
659 	m_skeletal = false;
660 
661 	MStatus status = MS::kFailure;
662 	if (xr_object* object = create_object(mesh_objs)) {
663 		if (object->save_object(path))
664 			status = MS::kSuccess;
665 		delete object;
666 	}
667 
668 	return status;
669 }
670 
find_mesh_and_skin(MObject * mesh_obj,MObject * skin_obj,bool selection_only)671 static MStatus find_mesh_and_skin(MObject* mesh_obj, MObject* skin_obj, bool selection_only)
672 {
673 	MObjectArray mesh_objs;
674 	collect_meshes(mesh_objs, selection_only);
675 	switch (mesh_objs.length()) {
676 	case 0:
677 		msg("can't find any mesh to export");
678 		return MS::kFailure;
679 
680 	case 1:
681 		break;
682 
683 	default:
684 		msg("can't handle multiple meshes in skeletal object");
685 		return MS::kFailure;
686 	}
687 
688 	MObjectArray skin_objs;
689 	for (MItDependencyNodes dep_it(MFn::kSkinClusterFilter); !dep_it.isDone(); dep_it.next()) {
690 		MObject skin_obj = dep_it.thisNode();
691 		MFnSkinCluster skin_fn(skin_obj);
692 		MObjectArray affected;
693 		skin_fn.getOutputGeometry(affected);
694 		msg("skin cluster %s", skin_fn.name().asChar());
695 		for (unsigned i = affected.length(); i != 0;) {
696 			if (affected[--i] == mesh_objs[0])
697 				skin_objs.append(skin_obj);
698 		}
699 	}
700 	switch (skin_objs.length()) {
701 	case 0:
702 		msg("can't find skin cluster for mesh");
703 		return MS::kFailure;
704 
705 	case 1:
706 		break;
707 
708 	default:
709 		msg("can't handle multiple skin clusters in skeletal object");
710 		return MS::kFailure;
711 	}
712 
713 	if (mesh_obj)
714 		*mesh_obj = mesh_objs[0];
715 	if (skin_obj)
716 		*skin_obj = skin_objs[0];
717 	return MS::kSuccess;
718 }
719 
export_skl_object(const char * path,bool selection_only)720 MStatus maya_export_tools::export_skl_object(const char* path, bool selection_only)
721 {
722 	MObject mesh_obj, skin_obj;
723 	MStatus status = find_mesh_and_skin(&mesh_obj, &skin_obj, selection_only);
724 	if (!status)
725 		return status;
726 
727 	m_skeletal = true;
728 
729 	status = MS::kFailure;
730 	if (xr_object* object = create_skl_object(mesh_obj, skin_obj)) {
731 		if (object->save_object(path))
732 			status = MS::kSuccess;
733 		delete object;
734 	}
735 	return status;
736 }
737 
export_skl(const char * path,bool selection_only)738 MStatus maya_export_tools::export_skl(const char* path, bool selection_only)
739 {
740 	if (MTime::uiUnit() != MTime::kNTSCFrame)
741 		msg("warning: motion export with non-NTSC frame frequency was not tested");
742 
743 	MObject skin_obj;
744 	MStatus status = find_mesh_and_skin(0, &skin_obj, selection_only);
745 	if (!status)
746 		return status;
747 
748 	MFnSkinCluster skin_fn(skin_obj);
749 	MDagPathArray joints;
750 	skin_fn.influenceObjects(joints, &status);
751 	unsigned num_joints = joints.length();
752 	if (num_joints == 0) {
753 		msg("can't find any influence object");
754 		return MS::kFailure;
755 	}
756 	xr_bone_motion_vec bmotions(num_joints);
757 	for (unsigned i = num_joints; i != 0;) {
758 		MFnIkJoint joint_fn(joints[--i], &status);
759 		if (!status) {
760 			msg("can't handle non-joint node %s",
761 					joints[i].partialPathName().asChar());
762 			return status;
763 		}
764 		xr_bone_motion* bmotion = new xr_bone_motion(joint_fn.name().asChar());
765 		bmotion->create_envelopes();
766 		bmotions[i] = bmotion;
767 	}
768 
769 	MTime saved_time(MAnimControl::currentTime());
770 
771 	int32_t frame_start = int32_t(MAnimControl::minTime().as(MTime::kNTSCFrame));
772 	int32_t frame_end = int32_t(MAnimControl::maxTime().as(MTime::kNTSCFrame));
773 	msg("range=%d - %d", frame_start, frame_end);
774 
775 	for (int32_t frame = frame_start; frame != frame_end; ++frame) {
776 		MGlobal::viewFrame(MTime(double(frame), MTime::kNTSCFrame));
777 		float time = frame/30.f;
778 		for (unsigned i = num_joints; i != 0;) {
779 			MFnIkJoint joint_fn(joints[--i]);
780 			xr_envelope* const* envelopes = bmotions[i]->envelopes();
781 
782 			MVector t = joint_fn.getTranslation(MSpace::kTransform, &status);
783 			CHECK_MSTATUS(status);
784 			envelopes[0]->insert_key(time, float(MDistance(t.x, MDistance::kCentimeters).asMeters()));
785 			envelopes[1]->insert_key(time, float(MDistance(t.y, MDistance::kCentimeters).asMeters()));
786 			envelopes[2]->insert_key(time, float(MDistance(-t.z, MDistance::kCentimeters).asMeters()));
787 
788 			MEulerRotation r;
789 			status = joint_fn.getRotation(r);
790 			CHECK_MSTATUS(status);
791 			r.reorderIt(MEulerRotation::kZXY);
792 			envelopes[4]->insert_key(time, float(-r.x));
793 			envelopes[3]->insert_key(time, float(-r.y));
794 			envelopes[5]->insert_key(time, float(r.z));
795 		}
796 	}
797 	xr_skl_motion* smotion = new xr_skl_motion;
798 	smotion->name() = "unnamed";
799 	smotion->fps() = 30.f;
800 	smotion->set_frame_range(frame_start, frame_end);
801 	smotion->bone_motions().swap(bmotions);
802 	status = smotion->save_skl(path) ? MS::kSuccess : MS::kFailure;
803 	delete smotion;
804 
805 	MAnimControl::setCurrentTime(saved_time);
806 
807 	return status;
808 }
809