diff --git a/pandatool/src/maxegg/maxNodeDesc.cxx b/pandatool/src/maxegg/maxNodeDesc.cxx index 4c7d243b17..ccf0c234ba 100755 --- a/pandatool/src/maxegg/maxNodeDesc.cxx +++ b/pandatool/src/maxegg/maxNodeDesc.cxx @@ -4,3 +4,219 @@ // //////////////////////////////////////////////////////////////////// // +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "maxNodeDesc.h" +#include "maxLogger.h" +#define MTEC Logger::ST_MAP_ME_TO_APP_SPECIFIC_SYSTEM4 + +TypeHandle MaxNodeDesc::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeDesc::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +MaxNodeDesc:: +MaxNodeDesc(MaxNodeDesc *parent, const string &name) : + Namable(name), + _parent(parent) +{ + _max_node = (INode *)NULL; + _egg_group = (EggGroup *)NULL; + _egg_table = (EggTable *)NULL; + _anim = (EggXfmSAnim *)NULL; + _joint_type = JT_none; + _joint_entry = NULL; + + // Add ourselves to our parent. + if (_parent != (MaxNodeDesc *)NULL) { + _parent->_children.push_back(this); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeDesc::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +MaxNodeDesc:: +~MaxNodeDesc() {} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeDesc::from_INode +// Access: Public +// Description: Indicates an associated between the MaxNodeDesc and +// some Max Node instance. +//////////////////////////////////////////////////////////////////// +void MaxNodeDesc:: +from_INode(INode *max_node) { + if (_max_node == (INode *)NULL) { + _max_node = max_node; + + // This is how I decided to check to see if this max node is a + // joint. It works in all instances I've seen so far, but this + // may be a good starting place to look if joints are not being + // picked up correctly in the future. + + //Check to see if the node's controller is a biped + //If so treat it as a joint + // Get the node's transform control + Control *c = max_node->GetTMController(); + if (_max_node->GetBoneNodeOnOff() || + (c && //c exists and it's type is a biped + ((c->ClassID() == BIPSLAVE_CONTROL_CLASS_ID) || + (c->ClassID() == BIPBODY_CONTROL_CLASS_ID) || + (c->ClassID() == FOOTPRINT_CLASS_ID)))) { + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Found a joint." ); + + // This node is a joint. + _joint_type = JT_node_joint; + if (_parent != (MaxNodeDesc *)NULL) { + _parent->mark_joint_parent(); + } + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeDesc::has_max_node +// Access: Public +// Description: Returns true if a Max INode has been associated +// with this node, false otherwise. +//////////////////////////////////////////////////////////////////// +bool MaxNodeDesc:: +has_max_node() const { + return (_max_node != (INode *)NULL); +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeDesc::get_max_node +// Access: Public +// Description: Returns the INode associated with this node. It +// is an error to call this unless has_max_node() +// returned true. +//////////////////////////////////////////////////////////////////// +INode *MaxNodeDesc:: +get_max_node() const { + nassertr(_max_node != (INode *)NULL, _max_node); + return _max_node; +} + + +void MaxNodeDesc:: +set_joint(bool onoff) { + if (onoff) + _joint_type = JT_joint; + else + _joint_type = JT_none; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeDesc::is_joint +// Access: Private +// Description: Returns true if the node should be treated as a joint +// by the converter. +//////////////////////////////////////////////////////////////////// +bool MaxNodeDesc:: +is_joint() const { + return _joint_type == JT_joint || _joint_type == JT_pseudo_joint; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeDesc::is_joint_parent +// Access: Private +// Description: Returns true if the node is the parent or ancestor of +// a joint. +//////////////////////////////////////////////////////////////////// +bool MaxNodeDesc:: +is_joint_parent() const { + return _joint_type == JT_joint_parent; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeDesc::is_joint_parent +// Access: Private +// Description: Returns true if the node is the parent or ancestor of +// a joint. +//////////////////////////////////////////////////////////////////// +bool MaxNodeDesc:: +is_node_joint() const { + return _joint_type == JT_node_joint; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeDesc::clear_egg +// Access: Private +// Description: Recursively clears the egg pointers from this node +// and all children. +//////////////////////////////////////////////////////////////////// +void MaxNodeDesc:: +clear_egg() { + _egg_group = (EggGroup *)NULL; + _egg_table = (EggTable *)NULL; + _anim = (EggXfmSAnim *)NULL; + + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + MaxNodeDesc *child = (*ci); + child->clear_egg(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeDesc::mark_joint_parent +// Access: Private +// Description: Indicates that this node has at least one child that +// is a joint or a pseudo-joint. +//////////////////////////////////////////////////////////////////// +void MaxNodeDesc:: +mark_joint_parent() { + if (_joint_type == JT_none) { + _joint_type = JT_joint_parent; + if (_parent != (MaxNodeDesc *)NULL) { + _parent->mark_joint_parent(); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeDesc::check_pseudo_joints +// Access: Private +// Description: Walks the hierarchy, looking for non-joint nodes that +// are both children and parents of a joint. These +// nodes are deemed to be pseudo joints, since the +// converter must treat them as joints. +//////////////////////////////////////////////////////////////////// +void MaxNodeDesc:: +check_pseudo_joints(bool joint_above) { + if (_joint_type == JT_joint_parent && joint_above) { + // This is one such node: it is the parent of a joint + // (JT_joint_parent is set), and it is the child of a joint + // (joint_above is set). + _joint_type = JT_pseudo_joint; + } + + if (_joint_type == JT_joint) { + // If this node is itself a joint, then joint_above is true for + // all child nodes. + joint_above = true; + } + + // Don't bother traversing further if _joint_type is none, since + // that means this node has no joint children. + if (_joint_type != JT_none) { + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + MaxNodeDesc *child = (*ci); + child->check_pseudo_joints(joint_above); + } + } +} diff --git a/pandatool/src/maxegg/maxNodeDesc.h b/pandatool/src/maxegg/maxNodeDesc.h index ef67c99e1d..00c54fe6d1 100755 --- a/pandatool/src/maxegg/maxNodeDesc.h +++ b/pandatool/src/maxegg/maxNodeDesc.h @@ -4,3 +4,108 @@ // //////////////////////////////////////////////////////////////////// // +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef MAXNODEDESC_H +#define MAXNODEDESC_H + +#pragma conform(forScope, off) + +#include "pandatoolbase.h" + +#include "referenceCount.h" +#include "pointerTo.h" +#include "namable.h" + +#ifdef MAX5 +//Disable the "Too many actual parameters in istdplug.h" warning in Max5 +#pragma warning(push) +#pragma warning(disable: 4002) +#include "max_pre_include.h" +#endif +#include +#include "bipexp.h" +#ifdef MAX5 +#include "max_post_include.h" +#pragma warning(pop) +#endif + +class EggGroup; +class EggTable; +class EggXfmSAnim; + +//////////////////////////////////////////////////////////////////// +// Class : MaxNodeDesc +// Description : Describes a single instance of a node in the Max +// scene graph, relating it to the corresponding egg +// structures (e.g. node, group, or table entry) that +// will be created. +//////////////////////////////////////////////////////////////////// +class MaxNodeDesc : public ReferenceCount, public Namable { + public: + MaxNodeDesc(MaxNodeDesc *parent = NULL, const string &name = string()); + ~MaxNodeDesc(); + + void from_INode(INode *max_node); + bool has_max_node() const; + INode *get_max_node() const; + + void set_joint(bool onoff); + bool is_joint() const; + bool is_joint_parent() const; + bool is_node_joint() const; + + MaxNodeDesc *_parent; + MaxNodeDesc *_joint_entry; + typedef pvector< MaxNodeDesc* > Children; + Children _children; + + private: + void clear_egg(); + void mark_joint_parent(); + void check_pseudo_joints(bool joint_above); + + INode *_max_node; + + EggGroup *_egg_group; + EggTable *_egg_table; + EggXfmSAnim *_anim; + + enum JointType { + JT_none, // Not a joint. + JT_node_joint, // Node that represents a joint in the geometry + // but not the actual joint itself + JT_joint, // An actual joint in Max. + JT_pseudo_joint, // Not a joint in Max, but treated just like a + // joint for the purposes of the converter. + JT_joint_parent, // A parent or ancestor of a joint or pseudo joint. + }; + JointType _joint_type; + + + public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + ReferenceCount::init_type(); + Namable::init_type(); + register_type(_type_handle, "MaxNodeDesc", + ReferenceCount::get_class_type(), + Namable::get_class_type()); + } + + private: + static TypeHandle _type_handle; + + friend class MaxNodeTree; +}; + +#endif diff --git a/pandatool/src/maxegg/maxNodeTree.cxx b/pandatool/src/maxegg/maxNodeTree.cxx index 968f7d5caa..a9844a6a2e 100755 --- a/pandatool/src/maxegg/maxNodeTree.cxx +++ b/pandatool/src/maxegg/maxNodeTree.cxx @@ -4,3 +4,488 @@ // //////////////////////////////////////////////////////////////////// // +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "maxNodeTree.h" +#include "eggGroup.h" +#include "eggTable.h" +#include "eggXfmSAnim.h" +#include "eggData.h" + +#ifdef MAX5 +//Disable the "Too many actual parameters in istdplug.h" warning in Max5 +#pragma warning(push) +#pragma warning(disable: 4002) +#include "max_pre_include.h" +#endif +#include "Max.h" +#ifdef MAX5 +#include "max_post_include.h" +#pragma warning(pop) +#endif +#include "maxToEggConverter.h" + + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +MaxNodeTree:: +MaxNodeTree() { + _root = new MaxNodeDesc; + _fps = 0.0; + _egg_data = (EggData *)NULL; + _egg_root = (EggGroupNode *)NULL; + _skeleton_node = (EggGroupNode *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::build_node +// Access: Public +// Description: Returns a pointer to the node corresponding to the +// indicated INode object, creating it first if +// necessary. +//////////////////////////////////////////////////////////////////// +MaxNodeDesc *MaxNodeTree:: +build_node(INode *max_node) { + MaxNodeDesc *node_desc = r_build_node(max_node); + node_desc->from_INode(max_node); + if (node_desc->is_node_joint()) + node_desc->_joint_entry = build_joint(max_node, node_desc); + return node_desc; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::build_node +// Access: Public +// Description: Returns a pointer to the node corresponding to the +// indicated INode object, creating it first if +// necessary. +//////////////////////////////////////////////////////////////////// +MaxNodeDesc *MaxNodeTree:: +build_joint(INode *max_node, MaxNodeDesc *node_joint) { + MaxNodeDesc *node_desc = r_build_joint(node_joint, max_node); + node_desc->from_INode(max_node); + node_desc->set_joint(true); + return node_desc; +} + +bool MaxNodeTree::node_in_list(ULONG handle, ULONG *list, int len) { + if (!list) return true; + for (int i = 0; i < len; i++) + if (list[i] == handle) return true; + return false; +} + +bool MaxNodeTree::is_joint(INode *node) { + Control *c = node->GetTMController(); + return (node->GetBoneNodeOnOff() || //joints + (c && //bipeds + ((c->ClassID() == BIPSLAVE_CONTROL_CLASS_ID) || + (c->ClassID() == BIPBODY_CONTROL_CLASS_ID) || + (c->ClassID() == FOOTPRINT_CLASS_ID)))); +} + +bool MaxNodeTree:: +r_build_hierarchy(INode *root, ULONG *selection_list, int len) { + if (node_in_list(root->GetHandle(), selection_list, len)) + build_node(root); + // Export children + for ( int i = 0; i < root->NumberOfChildren(); i++ ) { + // *** Should probably be checking the return value of the following line + r_build_hierarchy(root->GetChildNode(i), selection_list, len); + } + return true; +} +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::build_complete_hierarchy +// Access: Public +// Description: Walks through the complete Max hierarchy and builds +// up the corresponding tree. +//////////////////////////////////////////////////////////////////// +bool MaxNodeTree:: +build_complete_hierarchy(INode *root, ULONG *selection_list, int len) { + + // Get the entire Max scene. + if (root == NULL) { + // *** Log an error + return false; + } + + bool all_ok = true; + r_build_hierarchy(root, selection_list, len); + + if (all_ok) { + _root->check_pseudo_joints(false); + } + + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, + "finished building complete hierarchy" ); + + return all_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::build_selected_hierarchy +// Access: Public +// Description: Walks through the selected subset of the Max +// hierarchy (or the complete hierarchy, if nothing is +// selected) and builds up the corresponding tree. +//////////////////////////////////////////////////////////////////// +bool MaxNodeTree:: +build_selected_hierarchy(INode *root) { + // *** Write this later when it's time to do selection + /* + MStatus status; + + MItDag dag_iterator(MItDag::kDepthFirst, MFn::kTransform, &status); + if (!status) { + status.perror("MItDag constructor"); + return false; + } + + // Get only the selected geometry. + MSelectionList selection; + status = MGlobal::getActiveSelectionList(selection); + if (!status) { + status.perror("MGlobal::getActiveSelectionList"); + return false; + } + + // Get the selected geometry only if the selection is nonempty; + // otherwise, get the whole scene anyway. + if (selection.isEmpty()) { + mayaegg_cat.info() + << "Selection list is empty.\n"; + return build_complete_hierarchy(); + } + */ + bool all_ok = true; + /* + unsigned int length = selection.length(); + for (unsigned int i = 0; i < length; i++) { + MDagPath root_path; + status = selection.getDagPath(i, root_path); + if (!status) { + status.perror("MSelectionList::getDagPath"); + } else { + // Now traverse through the selected dag path and all nested + // dag paths. + dag_iterator.reset(root_path); + while (!dag_iterator.isDone()) { + MDagPath dag_path; + status = dag_iterator.getPath(dag_path); + if (!status) { + status.perror("MItDag::getPath"); + } else { + build_node(dag_path); + } + + dag_iterator.next(); + } + } + } + + if (all_ok) { + _root->check_pseudo_joints(false); + } + */ + return all_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::get_num_nodes +// Access: Public +// Description: Returns the total number of nodes in the hierarchy, +// not counting the root node. +//////////////////////////////////////////////////////////////////// +int MaxNodeTree:: +get_num_nodes() const { + return _nodes.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::get_node +// Access: Public +// Description: Returns the nth node in the hierarchy, in an +// arbitrary ordering. +//////////////////////////////////////////////////////////////////// +MaxNodeDesc *MaxNodeTree:: +get_node(int n) const { + nassertr(n >= 0 && n < (int)_nodes.size(), NULL); + return _nodes[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::clear_egg +// Access: Public +// Description: Removes all of the references to generated egg +// structures from the tree, and prepares the tree for +// generating new egg structures. +//////////////////////////////////////////////////////////////////// +void MaxNodeTree:: +clear_egg(EggData *egg_data, EggGroupNode *egg_root, + EggGroupNode *skeleton_node) { + _root->clear_egg(); + _egg_data = egg_data; + _egg_root = egg_root; + _skeleton_node = skeleton_node; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::get_egg_group +// Access: Public +// Description: Returns the EggGroupNode corresponding to the group +// or joint for the indicated node. Creates the group +// node if it has not already been created. +//////////////////////////////////////////////////////////////////// +EggGroup *MaxNodeTree:: +get_egg_group(MaxNodeDesc *node_desc) { + nassertr(_egg_root != (EggGroupNode *)NULL, NULL); + + if (node_desc->_egg_group == (EggGroup *)NULL) { + // We need to make a new group node. + EggGroup *egg_group; + + nassertr(node_desc->_parent != (MaxNodeDesc *)NULL, NULL); + egg_group = new EggGroup(node_desc->get_name()); + if (node_desc->is_joint()) { + egg_group->set_group_type(EggGroup::GT_joint); + } + + if (node_desc->_parent == _root) { + // The parent is the root. + _egg_root->add_child(egg_group); + + } else { + // The parent is another node. + EggGroup *parent_egg_group = get_egg_group(node_desc->_parent); + parent_egg_group->add_child(egg_group); + } + + // *** This is probably something that a Max plugin would need to be + // written for. May want to ask Disney about it + /* + if (node_desc->has_dag_path()) { + // Check for an object type setting, from Oliver's plug-in. + MObject dag_object = node_desc->get_dag_path().node(); + string object_type; + if (get_enum_attribute(dag_object, "eggObjectTypes1", object_type)) { + egg_group->add_object_type(object_type); + } + if (get_enum_attribute(dag_object, "eggObjectTypes2", object_type)) { + egg_group->add_object_type(object_type); + } + if (get_enum_attribute(dag_object, "eggObjectTypes3", object_type)) { + egg_group->add_object_type(object_type); + } + + // We treat the object type "billboard" as a special case: we + // apply this one right away and also flag the group as an + // instance. + if (egg_group->has_object_type("billboard")) { + egg_group->remove_object_type("billboard"); + egg_group->set_group_type(EggGroup::GT_instance); + egg_group->set_billboard_type(EggGroup::BT_axis); + + } else if (egg_group->has_object_type("billboard-point")) { + egg_group->remove_object_type("billboard-point"); + egg_group->set_group_type(EggGroup::GT_instance); + egg_group->set_billboard_type(EggGroup::BT_point_camera_relative); + } + + // We also treat the object type "dcs" and "model" as a special + // case, so we can test for these flags later. + if (egg_group->has_object_type("dcs")) { + egg_group->remove_object_type("dcs"); + egg_group->set_dcs_type(EggGroup::DC_default); + } + if (egg_group->has_object_type("model")) { + egg_group->remove_object_type("model"); + egg_group->set_model_flag(true); + } + + // And "vertex-color" has meaning only to this converter. + if (egg_group->has_object_type("vertex-color")) { + egg_group->remove_object_type("vertex-color"); + MaxEggGroupUserData *user_data = new MaxEggGroupUserData; + user_data->_vertex_color = true; + egg_group->set_user_data(user_data); + } + } + */ + node_desc->_egg_group = egg_group; + } + + return node_desc->_egg_group; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::get_egg_table +// Access: Public +// Description: Returns the EggTable corresponding to the joint +// for the indicated node. Creates the table node if it +// has not already been created. +//////////////////////////////////////////////////////////////////// +EggTable *MaxNodeTree:: +get_egg_table(MaxNodeDesc *node_desc) { + nassertr(_skeleton_node != (EggGroupNode *)NULL, NULL); + nassertr(node_desc->is_joint(), NULL); + + if (node_desc->_egg_table == (EggTable *)NULL) { + // We need to make a new table node. + nassertr(node_desc->_parent != (MaxNodeDesc *)NULL, NULL); + + EggTable *egg_table = new EggTable(node_desc->get_name()); + node_desc->_anim = new EggXfmSAnim("xform", + _egg_data->get_coordinate_system()); + node_desc->_anim->set_fps(_fps); + egg_table->add_child(node_desc->_anim); + + if (!node_desc->_parent->is_joint()) { + // The parent is not a joint; put it at the top. + _skeleton_node->add_child(egg_table); + + } else { + // The parent is another joint. + EggTable *parent_egg_table = get_egg_table(node_desc->_parent); + parent_egg_table->add_child(egg_table); + } + + node_desc->_egg_table = egg_table; + } + + return node_desc->_egg_table; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::get_egg_anim +// Access: Public +// Description: Returns the anim table corresponding to the joint +// for the indicated node. Creates the table node if it +// has not already been created. +//////////////////////////////////////////////////////////////////// +EggXfmSAnim *MaxNodeTree:: +get_egg_anim(MaxNodeDesc *node_desc) +{ + get_egg_table(node_desc); + return node_desc->_anim; +} + + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::r_build_node +// Access: Private +// Description: The recursive implementation of build_node(). +//////////////////////////////////////////////////////////////////// +MaxNodeDesc *MaxNodeTree:: +r_build_node(INode* max_node) +{ + // If we have already encountered this pathname, return the + // corresponding MaxNodeDesc immediately. + + ULONG node_handle = 0; + + if (max_node) { + node_handle = max_node->GetHandle(); + } + + NodesByPath::const_iterator ni = _nodes_by_path.find(node_handle); + if (ni != _nodes_by_path.end()) { + return (*ni).second; + } + + // Otherwise, we have to create it. Do this recursively, so we + // create each node along the path. + MaxNodeDesc *node_desc; + + if (!max_node) { + // This is the top. + node_desc = _root; + + } else { + INode *parent_node; + string local_name = max_node->GetName(); + if (max_node->IsRootNode()) { + parent_node = NULL; + } else { + parent_node = max_node->GetParentNode(); + } + + MaxNodeDesc *parent_node_desc = r_build_node(parent_node); + node_desc = new MaxNodeDesc(parent_node_desc, local_name); + _nodes.push_back(node_desc); + } + + _nodes_by_path.insert(NodesByPath::value_type(node_handle, node_desc)); + return node_desc; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::r_build_joint +// Access: Private +// Description: The recursive implementation of build_joint(). +//////////////////////////////////////////////////////////////////// +MaxNodeDesc *MaxNodeTree:: +r_build_joint(MaxNodeDesc *node_desc, INode *max_node) +{ + MaxNodeDesc *node_joint; + if (node_desc == _root) { + node_joint = new MaxNodeDesc(_root, max_node->GetName()); + _nodes.push_back(node_joint); + return node_joint; + } else if (node_desc->is_node_joint() && node_desc->_joint_entry) { + node_joint = new MaxNodeDesc(node_desc->_joint_entry, max_node->GetName()); + _nodes.push_back(node_joint); + return node_joint; + } else { + return r_build_joint(node_desc->_parent, max_node); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::find_node +// Access: Private +// Description: The recursive implementation of build_node(). +//////////////////////////////////////////////////////////////////// +MaxNodeDesc *MaxNodeTree:: +find_node(INode* max_node) +{ + // If we have already encountered this pathname, return the + // corresponding MaxNodeDesc immediately. + + ULONG node_handle = 0; + + if (max_node) { + node_handle = max_node->GetHandle(); + } + + NodesByPath::const_iterator ni = _nodes_by_path.find(node_handle); + if (ni != _nodes_by_path.end()) { + return (*ni).second; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxNodeTree::find_joint +// Access: Private +// Description: The recursive implementation of build_node(). +//////////////////////////////////////////////////////////////////// +MaxNodeDesc *MaxNodeTree:: +find_joint(INode* max_node) +{ + MaxNodeDesc *node = find_node(max_node); + if (!node || (is_joint(max_node) && !node->is_node_joint())) + node = build_node(max_node); + return node->_joint_entry; +} diff --git a/pandatool/src/maxegg/maxNodeTree.h b/pandatool/src/maxegg/maxNodeTree.h index ce654c68bd..63de263c90 100755 --- a/pandatool/src/maxegg/maxNodeTree.h +++ b/pandatool/src/maxegg/maxNodeTree.h @@ -4,3 +4,68 @@ // //////////////////////////////////////////////////////////////////// // +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef MAXNODETREE_H +#define MAXNODETREE_H + +#include "pandatoolbase.h" + +#include "maxNodeDesc.h" + +class EggData; +class EggGroupNode; + +//////////////////////////////////////////////////////////////////// +// Class : MaxNodeTree +// Description : Describes a complete tree of max nodes for +// conversion. +//////////////////////////////////////////////////////////////////// +class MaxNodeTree { +public: + MaxNodeTree(); + MaxNodeDesc *build_node(INode *max_node); + MaxNodeDesc *build_joint(INode *max_node, MaxNodeDesc *node_joint); + bool build_complete_hierarchy(INode *root, ULONG *selection_list, int len); + bool build_selected_hierarchy(INode *root); + MaxNodeDesc *find_node(INode *max_node); + MaxNodeDesc *find_joint(INode *max_node); + + int get_num_nodes() const; + MaxNodeDesc *get_node(int n) const; + + void clear_egg(EggData *egg_data, EggGroupNode *egg_root, + EggGroupNode *skeleton_node); + EggGroup *get_egg_group(MaxNodeDesc *node_desc); + EggTable *get_egg_table(MaxNodeDesc *node_desc); + EggXfmSAnim *get_egg_anim(MaxNodeDesc *node_desc); + + MaxNodeDesc* _root; + float _fps; + +private: + EggData *_egg_data; + EggGroupNode *_egg_root; + EggGroupNode *_skeleton_node; + + MaxNodeDesc *r_build_node(INode *max_node); + MaxNodeDesc *r_build_joint(MaxNodeDesc *node_desc, INode *max_node); + bool node_in_list(ULONG handle, ULONG *list, int len); + bool r_build_hierarchy(INode *root, ULONG *selection_list, int len); + bool is_joint(INode *node); + + typedef pmap NodesByPath; + NodesByPath _nodes_by_path; + + typedef pvector Nodes; + Nodes _nodes; +}; + +#endif diff --git a/pandatool/src/maxegg/maxToEggConverter.cxx b/pandatool/src/maxegg/maxToEggConverter.cxx index 32e3041076..99b9f9d195 100755 --- a/pandatool/src/maxegg/maxToEggConverter.cxx +++ b/pandatool/src/maxegg/maxToEggConverter.cxx @@ -4,3 +4,2448 @@ // //////////////////////////////////////////////////////////////////// // +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "maxToEggConverter.h" +#include "modstack.h" +#include "eggtable.h" + +#define MNEG Logger::ST_MAP_ME_TO_APP_SPECIFIC_SYSTEM2 +#define MNEG_GEOMETRY_GENERATION Logger::ST_MAP_ME_TO_APP_SPECIFIC_SYSTEM3 + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +MaxToEggConverter:: +MaxToEggConverter(const string &program_name) : + _program_name(program_name) +{ + _from_selection = false; + _polygon_output = false; + _polygon_tolerance = 0.01; + _transform_type = TT_model; + _cur_tref = 0; + _current_frame = 0; + _selection_list = NULL; + _selection_len = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::Copy Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +MaxToEggConverter:: +MaxToEggConverter(const MaxToEggConverter ©) : + maxInterface(copy.maxInterface) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +MaxToEggConverter:: +~MaxToEggConverter() +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::make_copy +// Access: Public, Virtual +// Description: Allocates and returns a new copy of the converter. +//////////////////////////////////////////////////////////////////// +SomethingToEggConverter *MaxToEggConverter:: +make_copy() +{ + return new MaxToEggConverter(*this); +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::get_name +// Access: Public, Virtual +// Description: Returns the English name of the file type this +// converter supports. +//////////////////////////////////////////////////////////////////// +string MaxToEggConverter:: +get_name() const +{ + return "Max"; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::get_extension +// Access: Public, Virtual +// Description: Returns the common extension of the file type this +// converter supports. +//////////////////////////////////////////////////////////////////// +string MaxToEggConverter:: +get_extension() const +{ + return "max"; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::convert_file +// Access: Public, Virtual +// Description: Handles the reading of the input file and converting +// it to egg. Returns true if successful, false +// otherwise. +// +// This is designed to be as generic as possible, +// generally in support of run-time loading. +// Also see convert_max(). +//////////////////////////////////////////////////////////////////// +bool MaxToEggConverter:: +convert_file(const Filename &filename) +{ + + //If there is no Max interface to speak of, we log an error and exit. + if ( !maxInterface ) { + Logger::Log( MTEC, Logger::SAT_NULL_ERROR, "pMaxInterface is null!" ); + // Logger::FunctionExit(); + return false; + } + + if (_character_name.empty()) { + _character_name = Filename(maxInterface->GetCurFileName()).get_basename_wo_extension(); + } + + return convert_max(false); +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::convert_max +// Access: Public +// Description: Fills up the egg_data structure according to the +// global Max model data. Returns true if successful, +// false if there is an error. If from_selection is +// true, the converted geometry is based on that which +// is selected; otherwise, it is the entire Max scene. +//////////////////////////////////////////////////////////////////// +bool MaxToEggConverter:: +convert_max(bool from_selection) { + + _from_selection = from_selection; + _textures.clear(); + // *** I don't know if we're going to handle shaders in Max + //_shaders.clear(); + + if ( !maxInterface ) { + Logger::Log( MTEC, Logger::SAT_NULL_ERROR, "pMaxInterface is null!" ); + // Logger::FunctionExit(); + return false; + } + // *** Should probably figure out how to use the progress bar effectively + // and also figure out why it is having linkage errors + //maxInterface->ProgressStart( "Exporting", TRUE, ProgressBarFunction, NULL ); + + // *** May want to make this something that is calculated as apposed to + // set explicitly, but it's not a priority + if (_egg_data->get_coordinate_system() == CS_default) { + _egg_data->set_coordinate_system(CS_zup_right); + } + + // Figure out the animation parameters. + double start_frame, end_frame, frame_inc, input_frame_rate, output_frame_rate; + + // Get the start and end frames and the animation frame rate from Max + Interval anim_range = maxInterface->GetAnimRange(); + start_frame = anim_range.Start()/GetTicksPerFrame(); + end_frame = anim_range.End()/GetTicksPerFrame(); + frame_inc = 1; + input_frame_rate = GetFrameRate(); + + if (has_start_frame() && get_start_frame() > start_frame) { + start_frame = get_start_frame(); + } + + if (has_end_frame() && get_end_frame() < end_frame) { + end_frame = get_end_frame(); + } + + if (has_frame_inc()) { + frame_inc = get_frame_inc(); + } + + if (has_input_frame_rate()) { + input_frame_rate = get_input_frame_rate(); + } + + if (has_output_frame_rate()) { + output_frame_rate = get_output_frame_rate(); + } else { + output_frame_rate = input_frame_rate; + } + + bool all_ok = true; + + if (_from_selection) { + all_ok = _tree.build_selected_hierarchy(maxInterface->GetRootNode()); + } else { + all_ok = _tree.build_complete_hierarchy(maxInterface->GetRootNode(), _selection_list, _selection_len); + } + + if (all_ok) { + switch (get_animation_convert()) { + case AC_pose: + // pose: set to a specific frame, then get out the static geometry. + sprintf(Logger::GetLogString(), "Extracting geometry from frame #%d.", + start_frame); + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, Logger::GetLogString() ); + _current_frame = start_frame; + // fall through + + case AC_none: + // none: just get out a static model, no animation. + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Converting static model." ); + all_ok = convert_hierarchy(get_egg_data()); + break; + + case AC_flip: + case AC_strobe: + // flip or strobe: get out a series of static models, one per + // frame, under a sequence node for AC_flip. + all_ok = convert_flip(start_frame, end_frame, frame_inc, + output_frame_rate); + break; + + case AC_model: + // model: get out an animatable model with joints and vertex + // membership. + all_ok = convert_char_model(); + break; + + case AC_chan: + // chan: get out a series of animation tables. + all_ok = convert_char_chan(start_frame, end_frame, frame_inc, + output_frame_rate); + break; + + case AC_both: + // both: Put a model and its animation into the same egg file. + _animation_convert = AC_model; + if (!convert_char_model()) { + all_ok = false; + } + _animation_convert = AC_chan; + if (!convert_char_chan(start_frame, end_frame, frame_inc, + output_frame_rate)) { + all_ok = false; + } + break; + }; + + reparent_decals(get_egg_data()); + } + + if (all_ok) { + Logger::Log(MTEC, Logger::SAT_MEDIUM_LEVEL, "Converted, no errors." ); + } else { + Logger::Log(MTEC, Logger::SAT_MEDIUM_LEVEL, + "Errors encountered in conversion." ); + } + + maxInterface->ProgressEnd(); + + // Logger::FunctionExit(); + return all_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::setMaxInterface +// Access: Public +// Description: Sets the interface to 3D Studio Max through +// which the scene graph is read. +//////////////////////////////////////////////////////////////////// +/* setMaxInterface(Interface *) - Sets the interface to 3D Studio Max through + * which the scene graph is read. + */ +void MaxToEggConverter::setMaxInterface(Interface *pInterface) +{ + maxInterface = pInterface; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::convert_flip +// Access: Private +// Description: Converts the animation as a series of models that +// cycle (flip) from one to the next at the appropriate +// frame rate. This is the most likely to convert +// precisely (since we ask Maya to tell us the vertex +// position each time) but it is the most wasteful in +// terms of memory utilization (since a complete of the +// model is stored for each frame). +//////////////////////////////////////////////////////////////////// +bool MaxToEggConverter:: +convert_flip(double start_frame, double end_frame, double frame_inc, + double output_frame_rate) { + bool all_ok = true; + + EggGroup *sequence_node = new EggGroup(_character_name); + get_egg_data()->add_child(sequence_node); + if (_animation_convert == AC_flip) { + sequence_node->set_switch_flag(true); + sequence_node->set_switch_fps(output_frame_rate / frame_inc); + } + + double frame = start_frame; + double frame_stop = end_frame; + while (frame <= frame_stop) { + sprintf(Logger::GetLogString(), "Extracting geometry from frame #%lf.", + frame); + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, Logger::GetLogString() ); + ostringstream name_strm; + name_strm << "frame" << frame; + EggGroup *frame_root = new EggGroup(name_strm.str()); + sequence_node->add_child(frame_root); + + // Set the frame and then export that frame + _current_frame = frame; + if (!convert_hierarchy(frame_root)) { + all_ok = false; + } + + frame += frame_inc; + } + + return all_ok; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::convert_char_model +// Access: Private +// Description: Converts the file as an animatable character +// model, with joints and vertex membership. +//////////////////////////////////////////////////////////////////// +bool MaxToEggConverter:: +convert_char_model() { + if (has_neutral_frame()) { + _current_frame = get_neutral_frame(); + } + + EggGroup *char_node = new EggGroup(_character_name); + get_egg_data()->add_child(char_node); + char_node->set_dart_type(EggGroup::DT_default); + + return convert_hierarchy(char_node); +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::convert_char_chan +// Access: Private +// Description: Converts the animation as a series of tables to apply +// to the character model, as retrieved earlier via +// AC_model. +//////////////////////////////////////////////////////////////////// +bool MaxToEggConverter:: +convert_char_chan(double start_frame, double end_frame, double frame_inc, + double output_frame_rate) { + + EggTable *root_table_node = new EggTable(); + get_egg_data()->add_child(root_table_node); + EggTable *bundle_node = new EggTable(_character_name); + bundle_node->set_table_type(EggTable::TT_bundle); + root_table_node->add_child(bundle_node); + EggTable *skeleton_node = new EggTable(""); + bundle_node->add_child(skeleton_node); + + // Set the frame rate before we start asking for anim tables to be + // created. + _tree._fps = output_frame_rate / frame_inc; + _tree.clear_egg(get_egg_data(), NULL, skeleton_node); + + // Now we can get the animation data by walking through all of the + // frames, one at a time, and getting the joint angles at each + // frame. + + // This is just a temporary EggGroup to receive the transform for + // each joint each frame. + EggGroup* tgroup; + + int num_nodes = _tree.get_num_nodes(); + int i; + + sprintf(Logger::GetLogString(), + "sf %lf ef %lf inc %lf ofr %lf.", + start_frame, end_frame, frame_inc, output_frame_rate ); + Logger::Log(MNEG_GEOMETRY_GENERATION, Logger::SAT_LOW_LEVEL, + Logger::GetLogString() ); + + TimeValue frame = start_frame; + TimeValue frame_stop = end_frame; + while (frame <= frame_stop) { + _current_frame = frame; + sprintf(Logger::GetLogString(), + "Current frame: %lf.", + _current_frame ); + Logger::Log(MNEG_GEOMETRY_GENERATION, Logger::SAT_LOW_LEVEL, + Logger::GetLogString() ); + + for (i = 0; i < num_nodes; i++) { + // Find all joints in the hierarchy + MaxNodeDesc *node_desc = _tree.get_node(i); + if (node_desc->is_joint()) { + tgroup = new EggGroup(); + INode *max_node = node_desc->get_max_node(); + + if (node_desc->_parent && node_desc->_parent->is_joint()) { + // If this joint also has a joint as a parent, the parent's + // transformation has to be divided out of this joint's TM + get_joint_transform(max_node, node_desc->_parent->get_max_node(), + tgroup); + } else { + get_joint_transform(max_node, NULL, tgroup); + } + + EggXfmSAnim *anim = _tree.get_egg_anim(node_desc); + if (!anim->add_data(tgroup->get_transform3d())) { + // *** log an error + } + delete tgroup; + } + } + + frame += frame_inc; + } + + // Now optimize all of the tables we just filled up, for no real + // good reason, except that it makes the resulting egg file a little + // easier to read. + for (i = 0; i < num_nodes; i++) { + MaxNodeDesc *node_desc = _tree.get_node(i); + if (node_desc->is_joint()) { + _tree.get_egg_anim(node_desc)->optimize(); + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::convert_hierarchy +// Access: Private +// Description: Generates egg structures for each node in the Max +// hierarchy. +//////////////////////////////////////////////////////////////////// +bool MaxToEggConverter:: +convert_hierarchy(EggGroupNode *egg_root) { + //int num_nodes = _tree.get_num_nodes(); + + _tree.clear_egg(get_egg_data(), egg_root, NULL); + for (int i = 0; i < _tree.get_num_nodes(); i++) { + if (!process_model_node(_tree.get_node(i))) { + return false; + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::process_model_node +// Access: Private +// Description: Converts the indicated Max node to the +// corresponding Egg structure. Returns true if +// successful, false if an error was encountered. +//////////////////////////////////////////////////////////////////// +bool MaxToEggConverter:: +process_model_node(MaxNodeDesc *node_desc) { + if (!node_desc->has_max_node()) { + // If the node has no Max equivalent, never mind. + return true; + } + + // Skip all nodes that represent joints in the geometry, but aren't + // the actual joints themselves + if (node_desc->is_node_joint()) { + return true; + } + + TimeValue time = 0; + INode *max_node = node_desc->get_max_node(); + + ObjectState state; + state = max_node->EvalWorldState(_current_frame * GetTicksPerFrame()); + + if (node_desc->is_joint()) { + EggGroup *egg_group = _tree.get_egg_group(node_desc); + // Don't bother with joints unless we're getting an animatable + // model. + if (_animation_convert == AC_model) { + get_joint_transform(max_node, egg_group); + } + } else { + if (state.obj) { + EggGroup *egg_group = NULL; + TriObject *myMaxTriObject; + Mesh max_mesh; + //Call the correct exporter based on what type of object this is. + switch( state.obj->SuperClassID() ){ + //A geometric object. + case GEOMOBJECT_CLASS_ID: + Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, + "Found a geometric object in the hierarchy!" ); + egg_group = _tree.get_egg_group(node_desc); + get_transform(max_node, egg_group); + + //Try converting this geometric object to a mesh we can use. + if (!state.obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0))) { + Logger::Log(MTEC, Logger::SAT_OTHER_ERROR, + "Cannot create geometry from state.obj!"); + // Logger::FunctionExit(); + return false; + } + //Convert our state object to a TriObject. + myMaxTriObject = (TriObject *) state.obj->ConvertToType(time, Class_ID(TRIOBJ_CLASS_ID, 0 )); + // *** Want to figure this problem out + // If actual conversion was required, then we want to delete this + // new mesh later to avoid mem leaks. **BROKEN. doesnt delete + + //Now, get the mesh. + max_mesh = myMaxTriObject->GetMesh(); + Logger::Log( MTEC, Logger::SAT_LOW_LEVEL, + "TriObject attached and mesh generated!" ); + + make_polyset(max_node, &max_mesh, egg_group); + + if (myMaxTriObject != state.obj) + delete myMaxTriObject; + break; + + case SHAPE_CLASS_ID: + if (state.obj->ClassID() == EDITABLE_SURF_CLASS_ID) { + Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, + "Found a NURB object in the hierarchy!" ); + NURBSSet getSet; + if (GetNURBSSet(state.obj, time, getSet, TRUE)) { + NURBSObject *nObj = getSet.GetNURBSObject(0); + if (nObj->GetType() == kNCVCurve) { + //It's a CV Curve, process it + egg_group = _tree.get_egg_group(node_desc); + get_transform(max_node, egg_group); + make_nurbs_curve((NURBSCVCurve *)nObj, string(max_node->GetName()), + time, egg_group); + } + } + } + break; + + case CAMERA_CLASS_ID: + Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, + "Found a camera object in the hierarchy!" ); + break; + + case LIGHT_CLASS_ID: + Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, + "Found a light object in the hierarchy!" ); + break; + + case HELPER_CLASS_ID: + Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, + "Found a helper object in the hierarchy!" ); + break; +/* default: + char buf[1024]; + sprintf(buf, "Unknown Superclass ID: %x, ClassID: %x,%x", state.obj->SuperClassID(), + state.obj->ClassID().PartA(), state.obj->ClassID().PartB()); + Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, + buf ); */ + + } + } + } + + // *** Check if any of these Maya options have a Max equivalent + /* + } else if (dag_node.inUnderWorld()) { + if (mayaegg_cat.is_debug()) { + mayaegg_cat.debug() + << "Ignoring underworld node " << path + << "\n"; + } + + } else if (dag_node.isIntermediateObject()) { + if (mayaegg_cat.is_debug()) { + mayaegg_cat.debug() + << "Ignoring intermediate object " << path + << "\n"; + } + + } else if (dag_path.hasFn(MFn::kNurbsSurface)) { + EggGroup *egg_group = _tree.get_egg_group(node_desc); + get_transform(dag_path, egg_group); + + MFnNurbsSurface surface(dag_path, &status); + if (!status) { + mayaegg_cat.info() + << "Error in node " << path + << ":\n" + << " it appears to have a NURBS surface, but does not.\n"; + } else { + make_nurbs_surface(dag_path, surface, egg_group); + } + + } else if (dag_path.hasFn(MFn::kNurbsCurve)) { + // Only convert NurbsCurves if we aren't making an animated model. + // Animated models, as a general rule, don't want these sorts of + // things in them. + if (_animation_convert != AC_model) { + EggGroup *egg_group = _tree.get_egg_group(node_desc); + get_transform(dag_path, egg_group); + + MFnNurbsCurve curve(dag_path, &status); + if (!status) { + mayaegg_cat.info() + << "Error in node " << path << ":\n" + << " it appears to have a NURBS curve, but does not.\n"; + } else { + make_nurbs_curve(dag_path, curve, egg_group); + } + } + + } else if (dag_path.hasFn(MFn::kMesh)) { + EggGroup *egg_group = _tree.get_egg_group(node_desc); + get_transform(dag_path, egg_group); + + MFnMesh mesh(dag_path, &status); + if (!status) { + mayaegg_cat.info() + << "Error in node " << path << ":\n" + << " it appears to have a polygon mesh, but does not.\n"; + } else { + make_polyset(dag_path, mesh, egg_group); + } + + } else if (dag_path.hasFn(MFn::kLocator)) { + EggGroup *egg_group = _tree.get_egg_group(node_desc); + + if (mayaegg_cat.is_debug()) { + mayaegg_cat.debug() + << "Locator at " << path << "\n"; + } + + // Presumably, the locator's position has some meaning to the + // end-user, so we will implicitly tag it with the DCS flag so it + // won't get flattened out. + if (_animation_convert != AC_model) { + // For now, don't set the DCS flag on locators within + // character models, since egg-optchar doesn't understand + // this. Perhaps there's no reason to ever change this, since + // locators within character models may not be meaningful. + egg_group->set_dcs_type(EggGroup::DC_net); + } + get_transform(dag_path, egg_group); + make_locator(dag_path, dag_node, egg_group); + + } else { + // Just a generic node. + EggGroup *egg_group = _tree.get_egg_group(node_desc); + get_transform(dag_path, egg_group); + } + */ + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::get_transform +// Access: Private +// Description: Extracts the transform on the indicated Maya node, +// and applies it to the corresponding Egg node. +//////////////////////////////////////////////////////////////////// +void MaxToEggConverter:: +get_transform(INode *max_node, EggGroup *egg_group) { + if (_animation_convert == AC_model) { + // When we're getting an animated model, we only get transforms + // for joints. + return; + } + + // The 3dsMax-style matrix that contains the pivot matrix...this pivot matrix + // encapsulates all the scales, rotates, and transforms it takes + // to "get to" the pivot point. + Matrix3 pivot; + //This is the Panda-flava-flav-style matrix we'll be exporting to. + Point3 row0; + Point3 row1; + Point3 row2; + Point3 row3; + + Logger::FunctionEntry("MaxNodeEggGroup::ApplyTransformFromMaxNodeToEggGroup"); + if ( !egg_group ) { + Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_NULL_ERROR, + "Destination EggGroup is null!" ); + Logger::FunctionExit(); + return; + } + + // *** A special case that I don't think we're supporting in Max + /* + // A special case: if the group is a billboard, we center the + // transform on the rotate pivot and ignore whatever transform might + // be there. + if (egg_group->get_billboard_type() != EggGroup::BT_none) { + MFnTransform transform(transformNode, &status); + if (!status) { + status.perror("MFnTransform constructor"); + return; + } + + MPoint pivot = transform.rotatePivot(MSpace::kObject, &status); + if (!status) { + status.perror("Can't get rotate pivot"); + return; + } + + // We need to convert the pivot to world coordinates. + // Unfortunately, Maya can only tell it to us in local + // coordinates. + MMatrix mat = dag_path.inclusiveMatrix(&status); + if (!status) { + status.perror("Can't get coordinate space for pivot"); + return; + } + LMatrix4d n2w(mat[0][0], mat[0][1], mat[0][2], mat[0][3], + mat[1][0], mat[1][1], mat[1][2], mat[1][3], + mat[2][0], mat[2][1], mat[2][2], mat[2][3], + mat[3][0], mat[3][1], mat[3][2], mat[3][3]); + LPoint3d p3d(pivot[0], pivot[1], pivot[2]); + p3d = p3d * n2w; + + if (egg_group->get_parent() != (EggGroupNode *)NULL) { + // Now convert the pivot point into the group's parent's space. + p3d = p3d * egg_group->get_parent()->get_vertex_frame_inv(); + } + + egg_group->clear_transform(); + egg_group->add_translate(p3d); + return; + } + */ + + // *** Make sure this is happening correctly; I'm not sure at the moment + // how these flags get specified +/* + switch (_transform_type) { + case TT_all: + break; + + case TT_model: + if (!egg_group->get_model_flag() && + !egg_group->has_dcs_type()) { + return; + } + break; + + case TT_dcs: + if (!egg_group->get_dcs_type()) { + return; + } + break; + + case TT_none: + case TT_invalid: + return; + } +*/ + + // Gets the TM for this node, a matrix which encapsulates all transformations + // it takes to get to the current node, including parent transformations. + pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame()); + row0 = pivot.GetRow(0); + row1 = pivot.GetRow(1); + row2 = pivot.GetRow(2); + row3 = pivot.GetRow(3); + + LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f, + row1.x, row1.y, row1.z, 0.0f, + row2.x, row2.y, row2.z, 0.0f, + row3.x, row3.y, row3.z, 1.0f ); + // Now here's the tricky part. I believe this command strips out the node + // "frame" which is the sum of all transformations enacted by the parent of + // this node. This should reduce to the transformation relative to this + // node's parent + m4d = m4d * egg_group->get_node_frame_inv(); + if (!m4d.almost_equal(LMatrix4d::ident_mat(), 0.0001)) { + egg_group->add_matrix4(m4d); + Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, + "Non-identity matrix applied to node!" ); + } else { + Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, + "Resultant matrix too close to identity; no transformation applied!" ); + } + Logger::FunctionExit(); +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::get_object_transform +// Access: Private +// Description: Extracts the transform on the indicated Maya node, +// and applies it to the corresponding Egg node. +//////////////////////////////////////////////////////////////////// +LMatrix4d MaxToEggConverter:: +get_object_transform(INode *max_node) { + + // The 3dsMax-style matrix that contains the pivot matrix...this pivot matrix + // encapsulates all the scales, rotates, and transforms it takes + // to "get to" the pivot point. + Matrix3 pivot; + //This is the Panda-flava-flav-style matrix we'll be exporting to. + Point3 row0; + Point3 row1; + Point3 row2; + Point3 row3; + + Logger::FunctionEntry("MaxNodeEggGroup::ApplyTransformFromMaxNodeToEggGroup"); + + // Gets the TM for this node, a matrix which encapsulates all transformations + // it takes to get to the current node, including parent transformations. + pivot = max_node->GetObjectTM(_current_frame * GetTicksPerFrame()); + row0 = pivot.GetRow(0); + row1 = pivot.GetRow(1); + row2 = pivot.GetRow(2); + row3 = pivot.GetRow(3); + + LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f, + row1.x, row1.y, row1.z, 0.0f, + row2.x, row2.y, row2.z, 0.0f, + row3.x, row3.y, row3.z, 1.0f ); + Logger::FunctionExit(); + return m4d; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::get_joint_transform +// Access: Private +// Description: Extracts the transform on the indicated Maya node, +// as appropriate for a joint in an animated character, +// and applies it to the indicated node. This is +// different from get_transform() in that it does not +// respect the _transform_type flag, and it does not +// consider the relative transforms within the egg file. +//////////////////////////////////////////////////////////////////// +void MaxToEggConverter:: +get_joint_transform(INode *max_node, EggGroup *egg_group) { + + // The 3dsMax-style matrix that contains the pivot matrix...this pivot matrix + // encapsulates all the scales, rotates, and transforms it takes + // to "get to" the pivot point. + Matrix3 pivot; + //This is the Panda-flava-flav-style matrix we'll be exporting to. + Point3 row0; + Point3 row1; + Point3 row2; + Point3 row3; + + Logger::FunctionEntry("MaxNodeEggGroup::ApplyTransformFromMaxNodeToEggGroup"); + if ( !egg_group ) { + Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_NULL_ERROR, + "Destination EggGroup is null!" ); + Logger::FunctionExit(); + return; + } + + // Gets the TM for this node, a matrix which encapsulates all transformations + // it takes to get to the current node, including parent transformations. + pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame()); + row0 = pivot.GetRow(0); + row1 = pivot.GetRow(1); + row2 = pivot.GetRow(2); + row3 = pivot.GetRow(3); + + LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f, + row1.x, row1.y, row1.z, 0.0f, + row2.x, row2.y, row2.z, 0.0f, + row3.x, row3.y, row3.z, 1.0f ); + // Now here's the tricky part. I believe this command strips out the node + // "frame" which is the sum of all transformations enacted by the parent of + // this node. This should reduce to the transformation relative to this + // node's parent + m4d = m4d * egg_group->get_node_frame_inv(); + if (!m4d.almost_equal(LMatrix4d::ident_mat(), 0.0001)) { + egg_group->add_matrix4(m4d); + Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, + "Non-identity matrix applied to node!" ); + } else { + Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, + "Resultant matrix too close to identity; no transformation applied!" ); + } + Logger::FunctionExit(); +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::get_joint_transform +// Access: Private +// Description: Extracts the transform on the indicated Maya node, +// as appropriate for a joint in an animated character, +// and applies it to the indicated node. This is +// different from get_transform() in that it does not +// respect the _transform_type flag, and it does not +// consider the relative transforms within the egg file. +//////////////////////////////////////////////////////////////////// +void MaxToEggConverter:: +get_joint_transform(INode *max_node, INode *parent_node, EggGroup *egg_group) { + + // The 3dsMax-style matrix that contains the pivot matrix...this pivot matrix + // encapsulates all the scales, rotates, and transforms it takes + // to "get to" the pivot point. + Matrix3 pivot; + Matrix3 parent_pivot; + //This is the Panda-flava-flav-style matrix we'll be exporting to. + Point3 row0; + Point3 row1; + Point3 row2; + Point3 row3; + + Logger::FunctionEntry("MaxNodeEggGroup::ApplyTransformFromMaxNodeToEggGroup"); + if ( !egg_group ) { + Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_NULL_ERROR, + "Destination EggGroup is null!" ); + Logger::FunctionExit(); + return; + } + + // Gets the TM for this node, a matrix which encapsulates all transformations + // it takes to get to the current node, including parent transformations. + pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame()); + row0 = pivot.GetRow(0); + row1 = pivot.GetRow(1); + row2 = pivot.GetRow(2); + row3 = pivot.GetRow(3); + + LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f, + row1.x, row1.y, row1.z, 0.0f, + row2.x, row2.y, row2.z, 0.0f, + row3.x, row3.y, row3.z, 1.0f ); + +if (parent_node) { + parent_pivot = parent_node->GetNodeTM(_current_frame * GetTicksPerFrame()); +// parent_pivot.Invert(); + row0 = parent_pivot.GetRow(0); + row1 = parent_pivot.GetRow(1); + row2 = parent_pivot.GetRow(2); + row3 = parent_pivot.GetRow(3); + + LMatrix4d pi_m4d(row0.x, row0.y, row0.z, 0.0f, + row1.x, row1.y, row1.z, 0.0f, + row2.x, row2.y, row2.z, 0.0f, + row3.x, row3.y, row3.z, 1.0f ); + + // Now here's the tricky part. I believe this command strips out the node + // "frame" which is the sum of all transformations enacted by the parent of + // this node. This should reduce to the transformation relative to this + // node's parent + pi_m4d.invert_in_place(); + m4d = m4d * pi_m4d; +} + if (!m4d.almost_equal(LMatrix4d::ident_mat(), 0.0001)) { + egg_group->add_matrix4(m4d); + Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, + "Non-identity matrix applied to node!" ); + } else { + Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, + "Resultant matrix too close to identity; no transformation applied!" ); + } + Logger::FunctionExit(); +} + +// *** I am skipping all NURBS stuff until I figure if Max can/needs to support +// it +/* +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::make_nurbs_surface +// Access: Private +// Description: Converts the indicated Maya NURBS surface to a +// corresponding egg structure, and attaches it to the +// indicated egg group. +//////////////////////////////////////////////////////////////////// +void MaxToEggConverter:: +make_nurbs_surface(const MDagPath &dag_path, MFnNurbsSurface &surface, + EggGroup *egg_group) { + MStatus status; + string name = surface.name().asChar(); + + if (mayaegg_cat.is_spam()) { + mayaegg_cat.spam() + << " numCVs: " + << surface.numCVsInU() + << " * " + << surface.numCVsInV() + << "\n"; + mayaegg_cat.spam() + << " numKnots: " + << surface.numKnotsInU() + << " * " + << surface.numKnotsInV() + << "\n"; + mayaegg_cat.spam() + << " numSpans: " + << surface.numSpansInU() + << " * " + << surface.numSpansInV() + << "\n"; + } + + MayaShader *shader = _shaders.find_shader_for_node(surface.object()); + + if (_polygon_output) { + // If we want polygon output only, tesselate the NURBS and output + // that. + MTesselationParams params; + params.setFormatType(MTesselationParams::kStandardFitFormat); + params.setOutputType(MTesselationParams::kQuads); + params.setStdFractionalTolerance(_polygon_tolerance); + + // We'll create the tesselation as a sibling of the NURBS surface. + // That way we inherit all of the transformations. + MDagPath polyset_path = dag_path; + MObject polyset_parent = polyset_path.node(); + MObject polyset = + surface.tesselate(params, polyset_parent, &status); + if (!status) { + status.perror("MFnNurbsSurface::tesselate"); + return; + } + + status = polyset_path.push(polyset); + if (!status) { + status.perror("MDagPath::push"); + } + + MFnMesh polyset_fn(polyset, &status); + if (!status) { + status.perror("MFnMesh constructor"); + return; + } + make_polyset(polyset_path, polyset_fn, egg_group, shader); + + // Now remove the polyset we created. + MFnDagNode parent_node(polyset_parent, &status); + if (!status) { + status.perror("MFnDagNode constructor"); + return; + } + status = parent_node.removeChild(polyset); + if (!status) { + status.perror("MFnDagNode::removeChild"); + } + + return; + } + + MPointArray cv_array; + status = surface.getCVs(cv_array, MSpace::kWorld); + if (!status) { + status.perror("MFnNurbsSurface::getCVs"); + return; + } + MDoubleArray u_knot_array, v_knot_array; + status = surface.getKnotsInU(u_knot_array); + if (!status) { + status.perror("MFnNurbsSurface::getKnotsInU"); + return; + } + status = surface.getKnotsInV(v_knot_array); + if (!status) { + status.perror("MFnNurbsSurface::getKnotsInV"); + return; + } + + + // We don't use these variables currently. + //MFnNurbsSurface::Form u_form = surface.formInU(); + //MFnNurbsSurface::Form v_form = surface.formInV(); + + + int u_degree = surface.degreeU(); + int v_degree = surface.degreeV(); + + int u_cvs = surface.numCVsInU(); + int v_cvs = surface.numCVsInV(); + + int u_knots = surface.numKnotsInU(); + int v_knots = surface.numKnotsInV(); + + assert(u_knots == u_cvs + u_degree - 1); + assert(v_knots == v_cvs + v_degree - 1); + + string vpool_name = name + ".cvs"; + EggVertexPool *vpool = new EggVertexPool(vpool_name); + egg_group->add_child(vpool); + + EggNurbsSurface *egg_nurbs = new EggNurbsSurface(name); + egg_nurbs->setup(u_degree + 1, v_degree + 1, + u_knots + 2, v_knots + 2); + + int i; + + egg_nurbs->set_u_knot(0, u_knot_array[0]); + for (i = 0; i < u_knots; i++) { + egg_nurbs->set_u_knot(i + 1, u_knot_array[i]); + } + egg_nurbs->set_u_knot(u_knots + 1, u_knot_array[u_knots - 1]); + + egg_nurbs->set_v_knot(0, v_knot_array[0]); + for (i = 0; i < v_knots; i++) { + egg_nurbs->set_v_knot(i + 1, v_knot_array[i]); + } + egg_nurbs->set_v_knot(v_knots + 1, v_knot_array[v_knots - 1]); + + LMatrix4d vertex_frame_inv = egg_group->get_vertex_frame_inv(); + + for (i = 0; i < egg_nurbs->get_num_cvs(); i++) { + int ui = egg_nurbs->get_u_index(i); + int vi = egg_nurbs->get_v_index(i); + + double v[4]; + MStatus status = cv_array[v_cvs * ui + vi].get(v); + if (!status) { + status.perror("MPoint::get"); + } else { + EggVertex vert; + LPoint4d p4d(v[0], v[1], v[2], v[3]); + p4d = p4d * vertex_frame_inv; + vert.set_pos(p4d); + egg_nurbs->add_vertex(vpool->create_unique_vertex(vert)); + } + } + + // Now consider the trim curves, if any. + unsigned num_trims = surface.numRegions(); + int trim_curve_index = 0; + for (unsigned ti = 0; ti < num_trims; ti++) { + unsigned num_loops = surface.numBoundaries(ti); + + if (num_loops > 0) { + egg_nurbs->_trims.push_back(EggNurbsSurface::Trim()); + EggNurbsSurface::Trim &egg_trim = egg_nurbs->_trims.back(); + + for (unsigned li = 0; li < num_loops; li++) { + egg_trim.push_back(EggNurbsSurface::Loop()); + EggNurbsSurface::Loop &egg_loop = egg_trim.back(); + + MFnNurbsSurface::BoundaryType type = + surface.boundaryType(ti, li, &status); + bool keep_loop = false; + + if (!status) { + status.perror("MFnNurbsSurface::BoundaryType"); + } else { + keep_loop = (type == MFnNurbsSurface::kInner || + type == MFnNurbsSurface::kOuter); + } + + if (keep_loop) { + unsigned num_edges = surface.numEdges(ti, li); + for (unsigned ei = 0; ei < num_edges; ei++) { + MObjectArray edge = surface.edge(ti, li, ei, true, &status); + if (!status) { + status.perror("MFnNurbsSurface::edge"); + } else { + unsigned num_segs = edge.length(); + for (unsigned si = 0; si < num_segs; si++) { + MObject segment = edge[si]; + if (segment.hasFn(MFn::kNurbsCurve)) { + MFnNurbsCurve curve(segment, &status); + if (!status) { + mayaegg_cat.error() + << "Trim curve appears to be a nurbs curve, but isn't.\n"; + } else { + // Finally, we have a valid curve! + EggNurbsCurve *egg_curve = + make_trim_curve(curve, name, egg_group, trim_curve_index); + trim_curve_index++; + if (egg_curve != (EggNurbsCurve *)NULL) { + egg_loop.push_back(egg_curve); + } + } + } else { + mayaegg_cat.error() + << "Trim curve segment is not a nurbs curve.\n"; + } + } + } + } + } + } + } + } + + // We add the NURBS to the group down here, after all of the vpools + // for the trim curves have been added. + egg_group->add_child(egg_nurbs); + + if (shader != (MayaShader *)NULL) { + set_shader_attributes(*egg_nurbs, *shader); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::make_trim_curve +// Access: Private +// Description: Converts the indicated Maya NURBS trim curve to a +// corresponding egg structure, and returns it, or NULL +// if there is a problem. +//////////////////////////////////////////////////////////////////// +EggNurbsCurve *MaxToEggConverter:: +make_trim_curve(const MFnNurbsCurve &curve, const string &nurbs_name, + EggGroupNode *egg_group, int trim_curve_index) { + if (mayaegg_cat.is_spam()) { + mayaegg_cat.spam() + << "Trim curve:\n"; + mayaegg_cat.spam() + << " numCVs: " + << curve.numCVs() + << "\n"; + mayaegg_cat.spam() + << " numKnots: " + << curve.numKnots() + << "\n"; + mayaegg_cat.spam() + << " numSpans: " + << curve.numSpans() + << "\n"; + } + + MStatus status; + + MPointArray cv_array; + status = curve.getCVs(cv_array, MSpace::kWorld); + if (!status) { + status.perror("MFnNurbsCurve::getCVs"); + return (EggNurbsCurve *)NULL; + } + MDoubleArray knot_array; + status = curve.getKnots(knot_array); + if (!status) { + status.perror("MFnNurbsCurve::getKnots"); + return (EggNurbsCurve *)NULL; + } + + // MFnNurbsCurve::Form form = curve.form(); + + int degree = curve.degree(); + int cvs = curve.numCVs(); + int knots = curve.numKnots(); + + assert(knots == cvs + degree - 1); + + string trim_name = "trim" + format_string(trim_curve_index); + + string vpool_name = nurbs_name + "." + trim_name; + EggVertexPool *vpool = new EggVertexPool(vpool_name); + egg_group->add_child(vpool); + + EggNurbsCurve *egg_curve = new EggNurbsCurve(trim_name); + egg_curve->setup(degree + 1, knots + 2); + + int i; + + egg_curve->set_knot(0, knot_array[0]); + for (i = 0; i < knots; i++) { + egg_curve->set_knot(i + 1, knot_array[i]); + } + egg_curve->set_knot(knots + 1, knot_array[knots - 1]); + + for (i = 0; i < egg_curve->get_num_cvs(); i++) { + double v[4]; + MStatus status = cv_array[i].get(v); + if (!status) { + status.perror("MPoint::get"); + } else { + EggVertex vert; + vert.set_pos(LPoint3d(v[0], v[1], v[3])); + egg_curve->add_vertex(vpool->create_unique_vertex(vert)); + } + } + + return egg_curve; +} */ + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::make_nurbs_curve +// Access: Private +// Description: Converts the indicated Maya NURBS curve (a standalone +// curve, not a trim curve) to a corresponding egg +// structure and attaches it to the indicated egg group. +//////////////////////////////////////////////////////////////////// +bool MaxToEggConverter:: +make_nurbs_curve(NURBSCVCurve *curve, const string &name, + TimeValue time, EggGroup *egg_group) +{ + +/* MStatus status; + string name = curve.name().asChar(); + + if (mayaegg_cat.is_spam()) { + mayaegg_cat.spam() + << " numCVs: " + << curve.numCVs() + << "\n"; + mayaegg_cat.spam() + << " numKnots: " + << curve.numKnots() + << "\n"; + mayaegg_cat.spam() + << " numSpans: " + << curve.numSpans() + << "\n"; + } + + MPointArray cv_array; + status = curve.getCVs(cv_array, MSpace::kWorld); + if (!status) { + status.perror("MFnNurbsCurve::getCVs"); + return; + } + MDoubleArray knot_array; + status = curve.getKnots(knot_array); + if (!status) { + status.perror("MFnNurbsCurve::getKnots"); + return; + } + + // MFnNurbsCurve::Form form = curve.form(); +*/ + + int degree = curve->GetOrder(); + int cvs = curve->GetNumCVs(); + int knots = curve->GetNumKnots(); + int i; + + if (knots != cvs + degree) { + char buf[1024]; + sprintf(buf, "NURBS knots count incorrect. Order %d, CVs %d, knots %d", degree, cvs, knots); + Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, buf); + return false; + } + + string vpool_name = name + ".cvs"; + EggVertexPool *vpool = new EggVertexPool(vpool_name); + egg_group->add_child(vpool); + + EggNurbsCurve *egg_curve = new EggNurbsCurve(name); + egg_group->add_child(egg_curve); + egg_curve->setup(degree, knots); + + for (i = 0; i < knots; i++) + egg_curve->set_knot(i, curve->GetKnot(i)); + + LMatrix4d vertex_frame_inv = egg_group->get_vertex_frame_inv(); + + for (i = 0; i < cvs; i++) { + NURBSControlVertex *cv = curve->GetCV(i); + if (!cv) { + char buf[1024]; + sprintf(buf, "Error getting CV %d", i); + Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, buf); + return false; + } else { + EggVertex vert; + LPoint4d p4d(0, 0, 0, 1.0); + cv->GetPosition(time, p4d[0], p4d[1], p4d[2]); + p4d = p4d * vertex_frame_inv; + vert.set_pos(p4d); + egg_curve->add_vertex(vpool->create_unique_vertex(vert)); + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::make_polyset +// Access: Private +// Description: Converts the indicated Maya polyset to a bunch of +// EggPolygons and parents them to the indicated egg +// group. +//////////////////////////////////////////////////////////////////// +void MaxToEggConverter:: +make_polyset(INode *max_node, Mesh *mesh, + EggGroup *egg_group, Shader *default_shader) { + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Entered make_poly_set." ); + + //bool double_sided = false; + + // *** I think this needs to have a plugin written to support + /* + MObject mesh_object = mesh.object(); + bool double_sided = false; + get_bool_attribute(mesh_object, "doubleSided", double_sided); + */ + + mesh->buildNormals(); + + if (mesh->getNumFaces() == 0) { + Logger::Log(MNEG_GEOMETRY_GENERATION, Logger::SAT_MEDIUM_LEVEL, + "Ignoring empty mesh "); + return; + } + + string vpool_name = string(max_node->GetName()) + ".verts"; + EggVertexPool *vpool = new EggVertexPool(vpool_name); + egg_group->add_child(vpool); + + // One way to convert the mesh would be to first get out all the + // vertices in the mesh and add them into the vpool, then when we + // traverse the polygons we would only have to index them into the + // vpool according to their Maya vertex index. + + // Unfortunately, since Maya may store multiple normals and/or + // colors for each vertex according to which polygon it is in, that + // approach won't necessarily work. In egg, those split-property + // vertices have to become separate vertices. So instead of adding + // all the vertices up front, we'll start with an empty vpool, and + // add vertices to it on the fly. + + // *** Need to find out if I even need to deal with shaders + /* + MObjectArray shaders; + MIntArray poly_shader_indices; + + status = mesh.getConnectedShaders(dag_path.instanceNumber(), + shaders, poly_shader_indices); + if (!status) { + status.perror("MFnMesh::getConnectedShaders"); + } + */ + + // We will need to transform all vertices from world coordinate + // space into the vertex space appropriate to this node. Usually, + // this is the same thing as world coordinate space, and this matrix + // will be identity; but if the node is under an instance + // (particularly, for instance, a billboard) then the vertex space + // will be different from world space. + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Before obtaining transform." ); + LMatrix4d vertex_frame = get_object_transform(max_node) * + egg_group->get_vertex_frame_inv(); + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "After obtaining transform." ); + + + // *** Not quite sure how this vertex color flag is handled. Check on later + /* + // Save this modeling flag for the vertex color check later (see the + // comment below). + bool egg_vertex_color = false; + if (egg_group->has_user_data(MayaEggGroupUserData::get_class_type())) { + egg_vertex_color = + DCAST(MayaEggGroupUserData, egg_group->get_user_data())->_vertex_color; + } + */ + + for ( int iFace=0; iFace < mesh->getNumFaces(); iFace++ ) { + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Getting face." ); + EggPolygon *egg_poly = new EggPolygon; + egg_group->add_child(egg_poly); + + egg_poly->set_bface_flag(double_sided); + + Face face = mesh->faces[iFace]; + + // *** Once again skipping shaders until I determine if I need them + /* + // Determine the shader for this particular polygon. + MayaShader *shader = NULL; + int index = pi.index(); + nassertv(index >= 0 && index < (int)poly_shader_indices.length()); + int shader_index = poly_shader_indices[index]; + if (shader_index != -1) { + nassertv(shader_index >= 0 && shader_index < (int)shaders.length()); + MObject engine = shaders[shader_index]; + shader = + _shaders.find_shader_for_shading_engine(engine); + + } else if (default_shader != (MayaShader *)NULL) { + shader = default_shader; + } + + const MayaShaderColorDef &color_def = shader->_color; + */ + + // Should we extract the color from the vertices? Normally, in + // Maya a texture completely replaces the vertex color, so we + // should ignore the vertex color if we have a texture. + + // However, this is an inconvenient property of Maya; sometimes we + // really do want both vertex color and texture applied to the + // same object. To allow this, we define the special egg flag + // "vertex-color", which when set indicates that we should + // respect the vertex color anyway. + + // *** Ignoring vertex colors for now + bool ignore_vertex_color = true; + /* + bool ignore_vertex_color = false; + if (shader != (MayaShader *)NULL) { + ignore_vertex_color = color_def._has_texture && !egg_vertex_color; + } + */ + + // *** More shader stuff to ignore + /* + LPoint3d centroid(0.0, 0.0, 0.0); + + if (shader != (MayaShader *)NULL && color_def.has_projection()) { + // If the shader has a projection, we may need to compute the + // polygon's centroid to avoid seams at the edges. + for (i = 0; i < num_verts; i++) { + MPoint p = pi.point(i, MSpace::kWorld); + LPoint3d p3d(p[0], p[1], p[2]); + p3d = p3d * vertex_frame_inv; + centroid += p3d; + } + centroid /= (double)num_verts; + } + */ + + // Get the vertices for the polygon. + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Before getting vertices." ); + + for ( int iVertex=0; iVertex < 3; iVertex++ ) { + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Getting vertex." ); + + EggVertex vert; + + // Get the vertex position + Point3 vertex = mesh->getVert(face.v[iVertex]); + LPoint3d p3d(vertex.x, vertex.y, vertex.z); + p3d = p3d * vertex_frame; + vert.set_pos(p3d); + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "After getting vertex pos before getting normal." ); + + + // Get the vertex normal + Point3 normal = get_max_vertex_normal(mesh, iFace, iVertex); + LVector3d n3d(normal.x, normal.y, normal.z); + // *** Not quite sure if this transform should be applied, but it may + // explain why normals were weird previously + n3d = n3d * vertex_frame; + vert.set_normal(n3d); + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "After getting normal." ); + + // *** More shader stuff to ignore for now + /* + if (shader != (MayaShader *)NULL && color_def.has_projection()) { + // If the shader has a projection, use it instead of the + // polygon's built-in UV's. + vert.set_uv(color_def.project_uv(p3d, centroid)); + + } else if (pi.hasUVs()) { + // Get the UV's from the polygon. + float2 uvs; + status = pi.getUV(i, uvs); + if (!status) { + status.perror("MItMeshPolygon::getUV"); + } else { + vert.set_uv(TexCoordd(uvs[0], uvs[1])); + } + } + */ + + // Get the UVs for this vertex + if (mesh->getNumTVerts()) { + UVVert vertTexCoord = mesh->getTVert(mesh->tvFace[iFace].t[iVertex]); + vert.set_uv( TexCoordd(vertTexCoord.x, vertTexCoord.y)); + sprintf(Logger::GetLogString(), + "Got tex vertex %d of %d from local tex data.", + iVertex, mesh->getNumTVerts() ); + Logger::Log(MNEG_GEOMETRY_GENERATION, Logger::SAT_LOW_LEVEL, + Logger::GetLogString() ); + } + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "after getting TVerts." ); + + + // *** Leaving out vertex colors for now + /* + if (pi.hasColor() && !ignore_vertex_color) { + MColor c; + status = pi.getColor(c, i); + if (!status) { + status.perror("MItMeshPolygon::getColor"); + } else { + vert.set_color(Colorf(c.r, c.g, c.b, 1.0)); + } + } + */ + + vert.set_external_index(face.v[iVertex]); + + egg_poly->add_vertex(vpool->create_unique_vertex(vert)); + } + + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Fixing windings" ); + //Max uses normals, not winding, to determine which way a + //polygon faces. Make sure the winding and that normal agree + EggVertex *verts[3]; + LPoint3d points[3]; + + for (int i = 0; i < 3; i++) { + verts[i] = egg_poly->get_vertex(i); + points[i] = verts[i]->get_pos3(); + } + + LVector3d realNorm = ((points[1] - points[0]).cross( + points[2] - points[0])); + Point3 maxNormTemp = mesh->getFaceNormal(iFace); + LVector3d maxNorm = (LVector3d(maxNormTemp.x, maxNormTemp.y, maxNormTemp.z) * + vertex_frame); + + if (realNorm.dot(maxNorm) < 0.0) { + egg_poly->set_vertex(0, verts[2]); + egg_poly->set_vertex(2, verts[0]); + } + + // *** More shader stuff to ignore + /* + // Now apply the shader. + if (shader != (MayaShader *)NULL) { + set_shader_attributes(*egg_poly, *shader); + } + */ + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Before set_material_attributes" ); + + set_material_attributes(*egg_poly, max_node->GetMtl(), &face); + } + + // Now that we've added all the polygons (and created all the + // vertices), go back through the vertex pool and set up the + // appropriate joint membership for each of the vertices. + + if (_animation_convert == AC_model) { + get_vertex_weights(max_node, vpool); + } + +} + +// *** I don't know if there is a Max equivalent to this. I will implement +// this if I find one +/* +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::make_locator +// Access: Private +// Description: Locators are used in Maya to indicate a particular +// position in space to the user or the modeler. We +// represent that in egg with an ordinary Group node, +// which we transform by the locator's position, so that +// the indicated point becomes the origin at this node +// and below. +//////////////////////////////////////////////////////////////////// +void MaxToEggConverter:: +make_locator(const MDagPath &dag_path, const MFnDagNode &dag_node, + EggGroup *egg_group) { + MStatus status; + + unsigned int num_children = dag_node.childCount(); + MObject locator; + bool found_locator = false; + for (unsigned int ci = 0; ci < num_children && !found_locator; ci++) { + locator = dag_node.child(ci); + found_locator = (locator.apiType() == MFn::kLocator); + } + + if (!found_locator) { + mayaegg_cat.error() + << "Couldn't find locator within locator node " + << dag_path.fullPathName().asChar() << "\n"; + return; + } + + LPoint3d p3d; + if (!get_vec3d_attribute(locator, "localPosition", p3d)) { + mayaegg_cat.error() + << "Couldn't get position of locator " + << dag_path.fullPathName().asChar() << "\n"; + return; + } + + // We need to convert the position to world coordinates. For some + // reason, Maya can only tell it to us in local coordinates. + MMatrix mat = dag_path.inclusiveMatrix(&status); + if (!status) { + status.perror("Can't get coordinate space for locator"); + return; + } + LMatrix4d n2w(mat[0][0], mat[0][1], mat[0][2], mat[0][3], + mat[1][0], mat[1][1], mat[1][2], mat[1][3], + mat[2][0], mat[2][1], mat[2][2], mat[2][3], + mat[3][0], mat[3][1], mat[3][2], mat[3][3]); + p3d = p3d * n2w; + + // Now convert the locator point into the group's space. + p3d = p3d * egg_group->get_node_frame_inv(); + + egg_group->add_translate(p3d); +} +*/ + +Point3 MaxToEggConverter::get_max_vertex_normal(Mesh *mesh, int faceNo, int vertNo) +{ + Face f = mesh->faces[faceNo]; + DWORD smGroup = f.smGroup; + int vert = f.getVert(vertNo); + RVertex *rv = mesh->getRVertPtr(vert); + + int numNormals; + Point3 vertexNormal; + + // Is normal specified + // SPCIFIED is not currently used, but may be used in future versions. + if (rv->rFlags & SPECIFIED_NORMAL) { + vertexNormal = rv->rn.getNormal(); + } + // If normal is not specified it's only available if the face belongs + // to a smoothing group + else if ((numNormals = rv->rFlags & NORCT_MASK) && smGroup) { + // If there is only one vertex is found in the rn member. + if (numNormals == 1) { + vertexNormal = rv->rn.getNormal(); + } + else { + // If two or more vertices are there you need to step through them + // and find the vertex with the same smoothing group as the current face. + // You will find multiple normals in the ern member. + for (int i = 0; i < numNormals; i++) { + if (rv->ern[i].getSmGroup() & smGroup) { + vertexNormal = rv->ern[i].getNormal(); + } + } + } + } + else { + // Get the normal from the Face if no smoothing groups are there + vertexNormal = mesh->getFaceNormal(faceNo); + } + + return vertexNormal; +} + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::get_vertex_weights +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +void MaxToEggConverter:: +get_vertex_weights(INode *max_node, EggVertexPool *vpool) { + //Try to get the weights out of a physique if one exists + Modifier *mod = FindSkinModifier(max_node, PHYSIQUE_CLASSID); + EggVertexPool::iterator vi; + + if (mod) { + // create a physique export interface + IPhysiqueExport *pPhysiqueExport = (IPhysiqueExport *)mod->GetInterface(I_PHYINTERFACE); + if (pPhysiqueExport) { + // create a context export interface + IPhyContextExport *pContextExport = + (IPhyContextExport *)pPhysiqueExport->GetContextInterface(max_node); + if (pContextExport) { + // set the flags in the context export interface + pContextExport->ConvertToRigid(TRUE); + pContextExport->AllowBlending(TRUE); + + for (vi = vpool->begin(); vi != vpool->end(); ++vi) { + EggVertex *vert = (*vi); + int max_vi = vert->get_external_index(); + + // get the vertex export interface + IPhyVertexExport *pVertexExport = + (IPhyVertexExport *)pContextExport->GetVertexInterface(max_vi); + if (pVertexExport) { + int vertexType = pVertexExport->GetVertexType(); + + // handle the specific vertex type + if(vertexType == RIGID_TYPE) { + // typecast to rigid vertex + IPhyRigidVertex *pTypeVertex = (IPhyRigidVertex *)pVertexExport; + INode *bone_node = pTypeVertex->GetNode(); + MaxNodeDesc *joint_node_desc = _tree.find_joint(bone_node); + if (joint_node_desc){ + EggGroup *joint = _tree.get_egg_group(joint_node_desc); + if (joint != (EggGroup *)NULL) + joint->ref_vertex(vert, 1.0f); + else + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, bone_node->GetName() ); + } + } + else if(vertexType == RIGID_BLENDED_TYPE) { + // typecast to blended vertex + IPhyBlendedRigidVertex *pTypeVertex = (IPhyBlendedRigidVertex *)pVertexExport; + + for (int ji = 0; ji < pTypeVertex->GetNumberNodes(); ++ji) { + float weight = pTypeVertex->GetWeight(ji); + if (weight > 0.0f) { + INode *bone_node = pTypeVertex->GetNode(ji); + MaxNodeDesc *joint_node_desc = _tree.find_joint(bone_node); + if (joint_node_desc){ + EggGroup *joint = _tree.get_egg_group(joint_node_desc); + if (joint != (EggGroup *)NULL) + joint->ref_vertex(vert, weight); + else + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, bone_node->GetName() ); + } + } + } + } + //Release the vertex interface + pContextExport->ReleaseVertexInterface(pVertexExport); + } + } + //Release the context interface + pPhysiqueExport->ReleaseContextInterface(pContextExport); + } + //Release the physique export interface + mod->ReleaseInterface(I_PHYINTERFACE, pPhysiqueExport); + } + } + else { + //No physique, try to find a skin + mod = FindSkinModifier(max_node, SKIN_CLASSID); + if (mod) { + ISkin *skin = (ISkin*)mod->GetInterface(I_SKIN); + if (skin) { + ISkinContextData *skinMC = skin->GetContextInterface(max_node); + if (skinMC) { + for (vi = vpool->begin(); vi != vpool->end(); ++vi) { + EggVertex *vert = (*vi); + int max_vi = vert->get_external_index(); + + for (int ji = 0; ji < skinMC->GetNumAssignedBones(max_vi); ++ji) { + float weight = skinMC->GetBoneWeight(max_vi, ji); + if (weight > 0.0f) { + INode *bone_node = skin->GetBone(skinMC->GetAssignedBone(max_vi, ji)); + MaxNodeDesc *joint_node_desc = _tree.find_joint(bone_node); + if (joint_node_desc){ + EggGroup *joint = _tree.get_egg_group(joint_node_desc); + if (joint != (EggGroup *)NULL) { + joint->ref_vertex(vert, weight); + } else { + Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, bone_node->GetName() ); + } + } + } + } + } + } + } + } + } +} + +// *** More ignored shader stuff. I am replacing this with +// set_material_attributes for now +/* +//////////////////////////////////////////////////////////////////// +// Function: MayaShader::set_shader_attributes +// Access: Private +// Description: Applies the known shader attributes to the indicated +// egg primitive. +//////////////////////////////////////////////////////////////////// +void MaxToEggConverter:: +set_shader_attributes(EggPrimitive &primitive, const MayaShader &shader) { + // In Maya, a polygon is either textured or colored. The texture, + // if present, replaces the color. + const MayaShaderColorDef &color_def = shader._color; + const MayaShaderColorDef &trans_def = shader._transparency; + if (color_def._has_texture || trans_def._has_texture) { + EggTexture tex(shader.get_name(), ""); + + if (color_def._has_texture) { + // If we have a texture on color, apply it as the filename. + Filename filename = Filename::from_os_specific(color_def._texture); + Filename fullpath,outpath; + _path_replace->full_convert_path(filename, get_texture_path(), + fullpath, outpath); + tex.set_filename(outpath); + tex.set_fullpath(fullpath); + apply_texture_properties(tex, color_def); + + // If we also have a texture on transparency, apply it as the + // alpha filename. + if (trans_def._has_texture) { + if (color_def._wrap_u != trans_def._wrap_u || + color_def._wrap_u != trans_def._wrap_u) { + mayaegg_cat.warning() + << "Shader " << shader.get_name() + << " has contradictory wrap modes on color and texture.\n"; + } + + if (!compare_texture_properties(tex, trans_def)) { + // Only report each broken shader once. + static pset bad_shaders; + if (bad_shaders.insert(shader.get_name()).second) { + mayaegg_cat.error() + << "Color and transparency texture properties differ on shader " + << shader.get_name() << "\n"; + } + } + tex.set_format(EggTexture::F_rgba); + + // We should try to be smarter about whether the transparency + // value is connected to the texture's alpha channel or to its + // grayscale channel. However, I'm not sure how to detect + // this at the moment; rather than spending days trying to + // figure out, for now I'll just assume that if the same + // texture image is used for both color and transparency, then + // the artist meant to use the alpha channel for transparency. + if (trans_def._texture == color_def._texture) { + // That means that we don't need to do anything special: use + // all the channels of the texture. + + } else { + // Otherwise, pull the alpha channel from the other image + // file. Ideally, we should figure out which channel from + // the other image supplies alpha (and specify this via + // set_alpha_file_channel()), but for now we assume it comes + // from the grayscale data. + filename = Filename::from_os_specific(trans_def._texture); + _path_replace->full_convert_path(filename, get_texture_path(), + fullpath, outpath); + tex.set_alpha_filename(outpath); + tex.set_alpha_fullpath(fullpath); + } + + } else { + // If there is no transparency texture specified, we don't + // have any transparency, so tell the egg format to ignore any + // alpha channel that might be on the color texture. + tex.set_format(EggTexture::F_rgb); + } + + } else { // trans_def._has_texture + // We have a texture on transparency only. Apply it as the + // primary filename, and set the format accordingly. + Filename filename = Filename::from_os_specific(trans_def._texture); + Filename fullpath, outpath; + _path_replace->full_convert_path(filename, get_texture_path(), + fullpath, outpath); + tex.set_filename(outpath); + tex.set_fullpath(fullpath); + tex.set_format(EggTexture::F_alpha); + apply_texture_properties(tex, trans_def); + } + + EggTexture *new_tex = + _textures.create_unique_texture(tex, ~EggTexture::E_tref_name); + + primitive.set_texture(new_tex); + + } + + // Also apply an overall color to the primitive. + Colorf rgba = shader.get_rgba(); + + // The existence of a texture on either color channel completely + // replaces the corresponding flat color. + if (color_def._has_texture) { + rgba[0] = 1.0f; + rgba[1] = 1.0f; + rgba[2] = 1.0f; + } + if (trans_def._has_texture) { + rgba[3] = 1.0f; + } + + // But the color gain always gets applied. + rgba[0] *= color_def._color_gain[0]; + rgba[1] *= color_def._color_gain[1]; + rgba[2] *= color_def._color_gain[2]; + rgba[3] *= color_def._color_gain[3]; + + primitive.set_color(rgba); +} +*/ + +/* +//////////////////////////////////////////////////////////////////// +// Function: MayaShader::set_material_attributes +// Access: Private +// Description: Applies the known shader attributes to the indicated +// egg primitive. +//////////////////////////////////////////////////////////////////// +void MaxToEggConverter:: +set_material_attributes(EggPrimitive &primitive, Mtl *maxMaterial, Face *face) { + Bitmap *maxBitmap; + BitmapTex *maxBitmapTex; +// Mtl *maxMaterial; + StdMat *maxStandardMaterial; + Texmap *maxTexmap; + EggTexture *myEggTexture = null; + string outString; + string outHandle; + bool has_diffuse_texture = false; + bool has_trans_texture = false; + + Point3 diffuseColor = Point3(1, 1, 1); + + Logger::FunctionEntry( "MaxToEggConverter::CreateEggTextureFromINode" ); + + //First, get the material data associated with this node. +// maxMaterial = max_node->GetMtl(); + if ( !maxMaterial ) { + Logger::Log(MTEC, Logger::SAT_NULL_ERROR, "maxMaterial is null!"); + Logger::FunctionExit(); + return; + } + + //Now, determine wether it's a standard or multi material + if ( maxMaterial->ClassID() == Class_ID(DMTL_CLASS_ID, 0 )) { + + // Access the Diffuse map and see if it's a Bitmap texture + maxStandardMaterial = (StdMat *)maxMaterial; + maxTexmap = maxMaterial->GetSubTexmap(ID_DI); + //Determine whether this texture is a bitmap. + if (maxTexmap && (maxTexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))) { + ostringstream name_strm; + name_strm << "Tex" << ++_cur_tref; + EggTexture tex(name_strm.str(), ""); + + // It is! + has_diffuse_texture = true; + + maxBitmapTex = (BitmapTex *) maxTexmap; + Filename filename = Filename::from_os_specific(maxBitmapTex->GetMapName()); + Filename fullpath, outpath; + _path_replace->full_convert_path(filename, get_texture_path(), + fullpath, outpath); + tex.set_filename(outpath); + tex.set_fullpath(fullpath); + apply_texture_properties(tex, maxStandardMaterial); + // *** Must add stuff here for looking for transparencies + maxBitmap = maxBitmapTex->GetBitmap(0); + //Query some parameters of the bitmap to get the format option. + if ( maxBitmap && maxBitmap->HasAlpha() ) { + has_trans_texture = true; + tex.set_format(EggTexture::F_rgba); + } else { + tex.set_format(EggTexture::F_rgb); + } + EggTexture *new_tex = + _textures.create_unique_texture(tex, ~EggTexture::E_tref_name); + + primitive.set_texture(new_tex); + } + + // Also apply an overall color to the primitive. + Colorf rgba(1.0f, 1.0f, 1.0f, 1.0f); + + // The existence of a texture on either color channel completely + // replaces the corresponding flat color. + if (!has_diffuse_texture) { + // Get the default diffuse color of the material without the texture map + diffuseColor = Point3(maxMaterial->GetDiffuse()); + rgba[0] = diffuseColor.x; + rgba[1] = diffuseColor.y; + rgba[2] = diffuseColor.z; + } + if (!has_trans_texture) { + // *** Figure out how to actually get the opacity here + rgba[3] = 1.0f; + } + + // *** May need color gain, but I don't know what it is + /* + // But the color gain always gets applied. + rgba[0] *= color_def._color_gain[0]; + rgba[1] *= color_def._color_gain[1]; + rgba[2] *= color_def._color_gain[2]; + rgba[3] *= color_def._color_gain[3]; + */ +/* + primitive.set_color(rgba); + + } else if ( maxMaterial->ClassID() == Class_ID(MULTI_CLASS_ID, 0 )) { + // It's a multi-material. Find the submaterial for this face. + // and call set_material_attributes again on the submaterial. + MtlID matID = face->getMatID(); + if (matID < maxMaterial->NumSubMtls()) { + set_material_attributes(primitive, maxMaterial->GetSubMtl(matID), face); + } else { + sprintf(Logger::GetLogString(), + "SubMaterial ID %d is greater than the total submaterial for this material", + matID); + Logger::Log(MTEC, Logger::SAT_NULL_ERROR, "maxMaterial is null!"); + } + } else { + // It's non-standard material. At the moment, let's just + // return + Logger::FunctionExit(); + return; + } + + Logger::FunctionExit(); +} +*/ + +//////////////////////////////////////////////////////////////////// +// Function: MaxToEggConverter::set_material_attributes +// Access: Private +// Description: Applies the known material attributes to the indicated +// egg primitive. +//////////////////////////////////////////////////////////////////// +void MaxToEggConverter:: +set_material_attributes(EggPrimitive &primitive, Mtl *maxMaterial, Face *face) { + Bitmap *diffuseBitmap; + BitmapTex *diffuseBitmapTex; + BitmapTex *transBitmapTex; +// Mtl *maxMaterial; + StdMat *maxStandardMaterial; + Texmap *diffuseTexmap; + Texmap *transTexmap; + EggTexture *myEggTexture = null; + string outString; + string outHandle; + bool has_diffuse_texture = false; + bool has_trans_texture = false; + + Point3 diffuseColor = Point3(1, 1, 1); + + Logger::FunctionEntry( "MaxToEggConverter::CreateEggTextureFromINode" ); + + //First, get the material data associated with this node. +// maxMaterial = max_node->GetMtl(); + if ( !maxMaterial ) { + Logger::Log(MTEC, Logger::SAT_NULL_ERROR, "maxMaterial is null!"); + Logger::FunctionExit(); + return; + } + + //Now, determine wether it's a standard or multi material + if ( maxMaterial->ClassID() == Class_ID(DMTL_CLASS_ID, 0 )) { + // *** Eventuall we should probably deal with multi-materials + + maxStandardMaterial = (StdMat *)maxMaterial; + + // Access the Diffuse map and see if it's a Bitmap texture + diffuseTexmap = maxMaterial->GetSubTexmap(ID_DI); + if (diffuseTexmap && (diffuseTexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))) { + has_diffuse_texture = true; + diffuseBitmapTex = (BitmapTex *) diffuseTexmap; + } + + // Access the Opacity map and see if it's a Bitmap texture + transTexmap = maxMaterial->GetSubTexmap(ID_OP); + if (transTexmap && (transTexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))) { + has_trans_texture = true; + transBitmapTex = (BitmapTex *) transTexmap; + } + + if (has_diffuse_texture || has_trans_texture) { + ostringstream name_strm; + name_strm << "Tex" << ++_cur_tref; + EggTexture tex(name_strm.str(), ""); + + if (has_diffuse_texture) { + // It is! + Filename filename = Filename::from_os_specific(diffuseBitmapTex->GetMapName()); + Filename fullpath, outpath; + _path_replace->full_convert_path(filename, get_texture_path(), + fullpath, outpath); + tex.set_filename(outpath); + tex.set_fullpath(fullpath); + apply_texture_properties(tex, maxStandardMaterial); + // *** Must add stuff here for looking for transparencies + diffuseBitmap = diffuseBitmapTex->GetBitmap(0); + //Query some parameters of the bitmap to get the format option. + if ( has_trans_texture ) { + tex.set_format(EggTexture::F_rgba); + if (stricmp(diffuseBitmapTex->GetMapName(), + transBitmapTex->GetMapName()) == 0) { + // nothing more needs to be done + } else { + filename = Filename::from_os_specific(transBitmapTex->GetMapName()); + _path_replace->full_convert_path(filename, get_texture_path(), + fullpath, outpath); + tex.set_alpha_filename(outpath); + tex.set_alpha_fullpath(fullpath); + } + } else { + if ( diffuseBitmap && diffuseBitmap->HasAlpha()) { + tex.set_format(EggTexture::F_rgba); + } else { + tex.set_format(EggTexture::F_rgb); + } + } + } else { + // We have a texture on transparency only. Apply it as the + // primary filename, and set the format accordingly. + Filename filename = Filename::from_os_specific(transBitmapTex->GetMapName()); + Filename fullpath, outpath; + _path_replace->full_convert_path(filename, get_texture_path(), + fullpath, outpath); + tex.set_filename(outpath); + tex.set_fullpath(fullpath); + tex.set_format(EggTexture::F_alpha); + apply_texture_properties(tex, maxStandardMaterial); + } + EggTexture *new_tex = + _textures.create_unique_texture(tex, ~EggTexture::E_tref_name); + + primitive.set_texture(new_tex); + } + + // Also apply an overall color to the primitive. + Colorf rgba(1.0f, 1.0f, 1.0f, 1.0f); + + // The existence of a texture on either color channel completely + // replaces the corresponding flat color. + if (!has_diffuse_texture) { + // Get the default diffuse color of the material without the texture map + diffuseColor = Point3(maxMaterial->GetDiffuse()); + rgba[0] = diffuseColor.x; + rgba[1] = diffuseColor.y; + rgba[2] = diffuseColor.z; + } + if (!has_trans_texture) { + // *** Figure out how to actually get the opacity here + rgba[3] = maxStandardMaterial->GetOpacity(_current_frame * GetTicksPerFrame()); + } + + // *** May need color gain, but I don't know what it is + /* + // But the color gain always gets applied. + rgba[0] *= color_def._color_gain[0]; + rgba[1] *= color_def._color_gain[1]; + rgba[2] *= color_def._color_gain[2]; + rgba[3] *= color_def._color_gain[3]; + */ + + primitive.set_color(rgba); + + } else if ( maxMaterial->ClassID() == Class_ID(MULTI_CLASS_ID, 0 )) { + // It's a multi-material. Find the submaterial for this face. + // and call set_material_attributes again on the submaterial. + MtlID matID = face->getMatID(); + if (matID < maxMaterial->NumSubMtls()) { + set_material_attributes(primitive, maxMaterial->GetSubMtl(matID), face); + } else { + sprintf(Logger::GetLogString(), + "SubMaterial ID %d is greater than the total submaterial for this material", + matID); + Logger::Log(MTEC, Logger::SAT_NULL_ERROR, "maxMaterial is null!"); + } + } else { + // It's another non-standard material. At the moment, let's just + // return + Logger::FunctionExit(); + return; + } + + Logger::FunctionExit(); +} + +//////////////////////////////////////////////////////////////////// +// Function: MayaShader::apply_texture_properties +// Access: Private +// Description: Applies all the appropriate texture properties to the +// EggTexture object, including wrap modes and texture +// matrix. +//////////////////////////////////////////////////////////////////// +void MaxToEggConverter:: +apply_texture_properties(EggTexture &tex, StdMat *maxMaterial) { + // Let's mipmap all textures by default. + tex.set_minfilter(EggTexture::FT_linear_mipmap_linear); + tex.set_magfilter(EggTexture::FT_linear); + + + // *** Need to figure out how to get the wrap options from Max + EggTexture::WrapMode wrap_u = EggTexture::WM_repeat; + EggTexture::WrapMode wrap_v = EggTexture::WM_repeat; + + /* + EggTexture::WrapMode wrap_u = color_def._wrap_u ? EggTexture::WM_repeat : EggTexture::WM_clamp; + EggTexture::WrapMode wrap_v = color_def._wrap_v ? EggTexture::WM_repeat : EggTexture::WM_clamp; + */ + + tex.set_wrap_u(wrap_u); + tex.set_wrap_v(wrap_v); + + // *** I may need to find this too + /* + LMatrix3d mat = color_def.compute_texture_matrix(); + if (!mat.almost_equal(LMatrix3d::ident_mat())) { + tex.set_transform(mat); + } + */ +} + +// *** I don't think I need this right now +/* +//////////////////////////////////////////////////////////////////// +// Function: MayaShader::compare_texture_properties +// Access: Private +// Description: Compares the texture properties already on the +// texture (presumably set by a previous call to +// apply_texture_properties()) and returns false if they +// differ from that specified by the indicated color_def +// object, or true if they match. +//////////////////////////////////////////////////////////////////// +bool MaxToEggConverter:: +compare_texture_properties(EggTexture &tex, + const MayaShaderColorDef &color_def) { + bool okflag = true; + + EggTexture::WrapMode wrap_u = color_def._wrap_u ? EggTexture::WM_repeat : EggTexture::WM_clamp; + EggTexture::WrapMode wrap_v = color_def._wrap_v ? EggTexture::WM_repeat : EggTexture::WM_clamp; + + if (wrap_u != tex.determine_wrap_u()) { + // Choose the more general of the two. + if (wrap_u == EggTexture::WM_repeat) { + tex.set_wrap_u(wrap_u); + } + okflag = false; + } + if (wrap_v != tex.determine_wrap_v()) { + if (wrap_v == EggTexture::WM_repeat) { + tex.set_wrap_v(wrap_v); + } + okflag = false; + } + + LMatrix3d mat = color_def.compute_texture_matrix(); + if (!mat.almost_equal(tex.get_transform())) { + okflag = false; + } + + return okflag; +} +*/ + + +//////////////////////////////////////////////////////////////////// +// Function: MayaShader::reparent_decals +// Access: Private +// Description: Recursively walks the egg hierarchy, reparenting +// "decal" type nodes below their corresponding +// "decalbase" type nodes, and setting the flags. +// +// Returns true on success, false if some nodes were +// incorrect. +//////////////////////////////////////////////////////////////////// +bool MaxToEggConverter:: +reparent_decals(EggGroupNode *egg_parent) { + bool okflag = true; + + // First, walk through all children of this node, looking for the + // one decal base, if any. + EggGroup *decal_base = (EggGroup *)NULL; + pvector decal_children; + + EggGroupNode::iterator ci; + for (ci = egg_parent->begin(); ci != egg_parent->end(); ++ci) { + EggNode *child = (*ci); + if (child->is_of_type(EggGroup::get_class_type())) { + EggGroup *child_group = (EggGroup *) child; + if (child_group->has_object_type("decalbase")) { + if (decal_base != (EggNode *)NULL) { + // error + okflag = false; + } + child_group->remove_object_type("decalbase"); + decal_base = child_group; + + } else if (child_group->has_object_type("decal")) { + child_group->remove_object_type("decal"); + decal_children.push_back(child_group); + } + } + } + + if (decal_base == (EggGroup *)NULL) { + if (!decal_children.empty()) { + // warning + } + + } else { + if (decal_children.empty()) { + // warning + + } else { + // All the decal children get moved to be a child of decal base. + // This usually will not affect the vertex positions, but it + // could if the decal base has a transform and the decal child + // is an instance node. So don't do that. + pvector::iterator di; + for (di = decal_children.begin(); di != decal_children.end(); ++di) { + EggGroup *child_group = (*di); + decal_base->add_child(child_group); + } + + // Also set the decal state on the base. + decal_base->set_decal_flag(true); + } + } + + // Now recurse on each of the child nodes. + for (ci = egg_parent->begin(); ci != egg_parent->end(); ++ci) { + EggNode *child = (*ci); + if (child->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *child_group = (EggGroupNode *) child; + if (!reparent_decals(child_group)) { + okflag = false; + } + } + } + + return okflag; +} + +//////////////////////////////////////////////////////////////////// +// Function: MayaShader::string_transform_type +// Access: Public, Static +// Description: Returns the TransformType value corresponding to the +// indicated string, or TT_invalid. +//////////////////////////////////////////////////////////////////// +MaxToEggConverter::TransformType MaxToEggConverter:: +string_transform_type(const string &arg) { + if (strcmp(arg.c_str(), "all") == 0) { + return TT_all; + } else if (strcmp(arg.c_str(), "model") == 0) { + return TT_model; + } else if (strcmp(arg.c_str(), "dcs") == 0) { + return TT_dcs; + } else if (strcmp(arg.c_str(), "none") == 0) { + return TT_none; + } else { + return TT_invalid; + } +} + +Modifier* MaxToEggConverter::FindSkinModifier (INode* node, const Class_ID &type) +{ + // Get object from node. Abort if no object. + Object* pObj = node->GetObjectRef(); + if (!pObj) return NULL; + + // Is derived object ? + while (pObj->SuperClassID() == GEN_DERIVOB_CLASS_ID) + { + // Yes -> Cast. + IDerivedObject* pDerObj = static_cast(pObj); + + // Iterate over all entries of the modifier stack. + for (int stackId = 0; stackId < pDerObj->NumModifiers(); ++stackId) + { + // Get current modifier. + Modifier* mod = pDerObj->GetModifier(stackId); + + // Is this what we are looking for? + if (mod->ClassID() == type ) + return mod; + } + + // continue with next derived object + pObj = pDerObj->GetObjRef(); + } + + // Not found. + return NULL; +} diff --git a/pandatool/src/maxegg/max_post_include.h b/pandatool/src/maxegg/max_post_include.h index ab377a7c3a..2ba4db5976 100755 --- a/pandatool/src/maxegg/max_post_include.h +++ b/pandatool/src/maxegg/max_post_include.h @@ -3,3 +3,18 @@ // //////////////////////////////////////////////////////////////////// // +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +// This header file works in conjunction with pre_maya_include.h; it +// cleans up some of the definitions that it left open. + +// Remove the symbols defined from pre_maya_include.h. +#undef ostream +#undef istream diff --git a/pandatool/src/maxegg/max_pre_include.h b/pandatool/src/maxegg/max_pre_include.h index 603ad177fe..7464bd4fd1 100755 --- a/pandatool/src/maxegg/max_pre_include.h +++ b/pandatool/src/maxegg/max_pre_include.h @@ -3,3 +3,29 @@ // //////////////////////////////////////////////////////////////////// // +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +// This header file defines a few things that are necessary to define +// before including any Maya headers, just to work around some of +// Max's assumptions about the compiler. It must not try to protect +// itself from multiple inclusion with #ifdef .. #endif, since it must +// be used each time it is included. + +// Max will try to typedef bool unless this symbol is defined. +#ifndef _BOOL +#define _BOOL 1 +#endif + +// Max tries to make a forward declaration for class ostream, but +// this is not necessarily a class! Curses. We can't use any of the +// built-in Max stream operators, and we have to protect ourselves +// from them. +#define ostream max_ostream +#define istream max_istream