panda3d/pandatool/src/maxegg/maxToEggConverter.cxx
2008-11-04 18:25:21 +00:00

1326 lines
51 KiB
C++
Executable File

// Filename: maxToEggConverter.cxx
// Created by Corey Revilla and Ken Strickland (6/22/03)
// from mayaToEggConverter.cxx created by drose (10Nov99)
//
////////////////////////////////////////////////////////////////////
//
// 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 "maxEgg.h"
////////////////////////////////////////////////////////////////////
// Function: MaxToEggConverter::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
MaxToEggConverter::
MaxToEggConverter()
{
reset();
}
////////////////////////////////////////////////////////////////////
// Function: MaxToEggConverter::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
MaxToEggConverter::
~MaxToEggConverter()
{
}
////////////////////////////////////////////////////////////////////
// Function: MaxToEggConverter::reset
////////////////////////////////////////////////////////////////////
void MaxToEggConverter::reset() {
_cur_tref = 0;
_current_frame = 0;
_textures.clear();
_egg_data = NULL;
}
////////////////////////////////////////////////////////////////////
// Function: MaxToEggConverter::convert
// 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(MaxEggOptions *options) {
_options = options;
Filename fn = Filename::from_os_specific(_options->_file_name);
_options->_path_replace->_path_directory = fn.get_dirname();
_egg_data = new EggData;
if (_egg_data->get_coordinate_system() == CS_default) {
_egg_data->set_coordinate_system(CS_zup_right);
}
// Figure out the animation parameters.
// Get the start and end frames and the animation frame rate from Max
Interval anim_range = _options->_max_interface->GetAnimRange();
int start_frame = anim_range.Start()/GetTicksPerFrame();
int end_frame = anim_range.End()/GetTicksPerFrame();
if (!_options->_export_all_frames) {
if (_options->_start_frame < start_frame) _options->_start_frame = start_frame;
if (_options->_start_frame > end_frame) _options->_start_frame = end_frame;
if (_options->_end_frame < start_frame) _options->_end_frame = start_frame;
if (_options->_end_frame > end_frame) _options->_end_frame = end_frame;
if (_options->_end_frame < _options->_start_frame) _options->_end_frame = _options->_start_frame;
start_frame = _options->_start_frame;
end_frame = _options->_end_frame;
}
int frame_inc = 1;
int output_frame_rate = GetFrameRate();
bool all_ok = true;
if (_options->_export_whole_scene) {
all_ok = _tree.build_complete_hierarchy(_options->_max_interface->GetRootNode(), NULL, 0);
} else {
all_ok = _tree.build_complete_hierarchy(_options->_max_interface->GetRootNode(), &_options->_node_list.front(), _options->_node_list.size());
}
if (all_ok) {
switch (_options->_anim_type) {
case MaxEggOptions::AT_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() );
// Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Converting static model." );
_current_frame = start_frame;
all_ok = convert_hierarchy(_egg_data);
break;
case MaxEggOptions::AT_model:
// model: get out an animatable model with joints and vertex
// membership.
all_ok = convert_char_model();
break;
case MaxEggOptions::AT_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 MaxEggOptions::AT_both:
// both: Put a model and its animation into the same egg file.
_options->_anim_type = MaxEggOptions::AT_model;
if (!convert_char_model()) {
all_ok = false;
}
_options->_anim_type = MaxEggOptions::AT_chan;
if (!convert_char_chan(start_frame, end_frame, frame_inc,
output_frame_rate)) {
all_ok = false;
}
// Set the type back to AT_both
_options->_anim_type = MaxEggOptions::AT_both;
break;
default:
all_ok = false;
};
reparent_decals(_egg_data);
}
if (all_ok) {
_egg_data->recompute_tangent_binormal_auto();
_egg_data->remove_unused_vertices(true);
}
_options->_successful = all_ok;
if (all_ok) {
Filename fn = Filename::from_os_specific(_options->_file_name);
return _egg_data->write_egg(fn);
} else {
return false;
}
}
////////////////////////////////////////////////////////////////////
// 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() {
std::string character_name = "character";
_current_frame = _options->_start_frame;
EggGroup *char_node = new EggGroup(character_name);
_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) {
std::string character_name = "character";
EggTable *root_table_node = new EggTable();
_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("<skeleton>");
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(_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;
TimeValue frame = start_frame;
TimeValue frame_stop = end_frame;
while (frame <= frame_stop) {
_current_frame = frame;
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(_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 (_options->_anim_type == MaxEggOptions::AT_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() ){
case GEOMOBJECT_CLASS_ID:
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))) {
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();
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) {
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:
break;
case LIGHT_CLASS_ID:
break;
case HELPER_CLASS_ID:
break;
}
}
}
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 (_options->_anim_type == MaxEggOptions::AT_model) {
// When we're getting an animated model, we only get transforms
// for joints.
return;
}
if ( !egg_group ) {
return;
}
// Gets the TM for this node, a matrix which encapsulates all transformations
// it takes to get to the current node, including parent transformations.
Matrix3 pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame());
//This is the Panda-flava-flav-style matrix we'll be exporting to.
Point3 row0 = pivot.GetRow(0);
Point3 row1 = pivot.GetRow(1);
Point3 row2 = pivot.GetRow(2);
Point3 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);
}
}
////////////////////////////////////////////////////////////////////
// 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) {
// Gets the TM for this node, a matrix which encapsulates all transformations
// it takes to get to the current node, including parent transformations.
Matrix3 pivot = max_node->GetObjectTM(_current_frame * GetTicksPerFrame());
Point3 row0 = pivot.GetRow(0);
Point3 row1 = pivot.GetRow(1);
Point3 row2 = pivot.GetRow(2);
Point3 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 );
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) {
if ( !egg_group ) {
return;
}
// Gets the TM for this node, a matrix which encapsulates all transformations
// it takes to get to the current node, including parent transformations.
Matrix3 pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame());
Point3 row0 = pivot.GetRow(0);
Point3 row1 = pivot.GetRow(1);
Point3 row2 = pivot.GetRow(2);
Point3 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);
}
}
////////////////////////////////////////////////////////////////////
// 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) {
if ( !egg_group ) {
return;
}
// Gets the TM for this node, a matrix which encapsulates all transformations
// it takes to get to the current node, including parent transformations.
Matrix3 pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame());
Point3 row0 = pivot.GetRow(0);
Point3 row1 = pivot.GetRow(1);
Point3 row2 = pivot.GetRow(2);
Point3 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) {
Matrix3 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);
}
}
////////////////////////////////////////////////////////////////////
// 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)
{
int degree = curve->GetOrder();
int cvs = curve->GetNumCVs();
int knots = curve->GetNumKnots();
int i;
if (knots != cvs + degree) {
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);
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) {
mesh->buildNormals();
if (mesh->getNumFaces() == 0) {
return;
}
// 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.
string vpool_name = string(max_node->GetName()) + ".verts";
EggVertexPool *vpool = new EggVertexPool(vpool_name);
egg_group->add_child(vpool);
// 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.
LMatrix4d vertex_frame = get_object_transform(max_node) *
egg_group->get_vertex_frame_inv();
for ( int iFace=0; iFace < mesh->getNumFaces(); iFace++ ) {
EggPolygon *egg_poly = new EggPolygon;
egg_group->add_child(egg_poly);
egg_poly->set_bface_flag(_options->_double_sided);
Face face = mesh->faces[iFace];
const PandaMaterial &pmat = get_panda_material(max_node->GetMtl(), face.getMatID());
// Get the vertices for the polygon.
for ( int iVertex=0; iVertex < 3; iVertex++ ) {
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);
// 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);
// Get the UVs for this vertex
for (int iChan=0; iChan<pmat._map_channels.size(); iChan++) {
int channel = pmat._map_channels[iChan];
ostringstream uvname;
uvname << "m" << channel;
UVVert uvw = get_max_vertex_texcoord(mesh, iFace, iVertex, channel);
vert.set_uv( uvname.str(), TexCoordd(uvw.x, uvw.y));
}
vert.set_external_index(face.v[iVertex]);
egg_poly->add_vertex(vpool->create_unique_vertex(vert));
}
//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]);
}
for (int i=0; i<pmat._texture_list.size(); i++) {
egg_poly->add_texture(pmat._texture_list[i]);
}
egg_poly->set_color(pmat._color);
}
// 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 (_options->_anim_type == MaxEggOptions::AT_model) {
get_vertex_weights(max_node, vpool);
}
}
UVVert MaxToEggConverter::get_max_vertex_texcoord(Mesh *mesh, int faceNo, int vertNo, int channel) {
// extract the texture coordinate
UVVert uvVert(0,0,0);
if(mesh->mapSupport(channel)) {
TVFace *pTVFace = mesh->mapFaces(channel);
UVVert *pUVVert = mesh->mapVerts(channel);
uvVert = pUVVert[pTVFace[faceNo].t[vertNo]];
} else if(mesh->numTVerts > 0) {
uvVert = mesh->tVerts[mesh->tvFace[faceNo].t[vertNo]];
}
return uvVert;
}
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 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);
}
}
}
}
//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);
}
}
}
}
}
}
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: MaxToEggConverter::get_material_textures
// Access: Private
// Description: Converts a Max material into a set of Panda textures
// and a primitive color.
////////////////////////////////////////////////////////////////////
const MaxToEggConverter::PandaMaterial &MaxToEggConverter::
get_panda_material(Mtl *mtl, MtlID matID) {
MaterialMap::iterator it = _material_map.find(mtl);
if (it != _material_map.end()) {
return (*it).second;
}
PandaMaterial &pandaMat = _material_map[mtl];
pandaMat._color = Colorf(1,1,1,1);
pandaMat._any_diffuse = false;
pandaMat._any_opacity = false;
pandaMat._any_gloss = false;
pandaMat._any_normal = false;
// If it's a multi-material, dig down.
while (( mtl != 0) && (mtl->ClassID() == Class_ID(MULTI_CLASS_ID, 0 ))) {
if (matID < mtl->NumSubMtls()) {
mtl = mtl->GetSubMtl(matID);
} else {
mtl = 0;
}
}
// If it's a standard material, we're good.
if ((mtl != 0) && (mtl->ClassID() == Class_ID(DMTL_CLASS_ID, 0 ))) {
StdMat *maxMaterial = (StdMat*)mtl;
analyze_diffuse_maps(pandaMat, maxMaterial->GetSubTexmap(ID_DI));
analyze_opacity_maps(pandaMat, maxMaterial->GetSubTexmap(ID_OP));
analyze_gloss_maps(pandaMat, maxMaterial->GetSubTexmap(ID_SP));
if (!pandaMat._any_gloss)
analyze_gloss_maps(pandaMat, maxMaterial->GetSubTexmap(ID_SS));
if (!pandaMat._any_gloss)
analyze_gloss_maps(pandaMat, maxMaterial->GetSubTexmap(ID_SH));
analyze_glow_maps(pandaMat, maxMaterial->GetSubTexmap(ID_SI));
analyze_normal_maps(pandaMat, maxMaterial->GetSubTexmap(ID_BU));
for (int i=0; i<pandaMat._texture_list.size(); i++) {
EggTexture *src = pandaMat._texture_list[i];
pandaMat._texture_list[i] =
_textures.create_unique_texture(*src, ~EggTexture::E_tref_name);
}
// The existence of a texture on either color channel completely
// replaces the corresponding flat color.
if (!pandaMat._any_diffuse) {
// Get the default diffuse color of the material without the texture map
Point3 diffuseColor = Point3(maxMaterial->GetDiffuse(0));
pandaMat._color[0] = diffuseColor.x;
pandaMat._color[1] = diffuseColor.y;
pandaMat._color[2] = diffuseColor.z;
}
if (!pandaMat._any_opacity) {
// *** Figure out how to actually get the opacity here
pandaMat._color[3] = maxMaterial->GetOpacity(_current_frame * GetTicksPerFrame());
}
return pandaMat;
}
// Otherwise, it's unrecognizable. Leave result blank.
return pandaMat;
}
////////////////////////////////////////////////////////////////////
// Function: MayaShader::analyze_diffuse_maps
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
void MaxToEggConverter::analyze_diffuse_maps(PandaMaterial &pandaMat, Texmap *mat) {
if (mat == 0) return;
if (mat->ClassID() == Class_ID(RGBMULT_CLASS_ID, 0)) {
for (int i=0; i<mat->NumSubTexmaps(); i++) {
analyze_diffuse_maps(pandaMat, mat->GetSubTexmap(i));
}
return;
}
if (mat->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
pandaMat._any_diffuse = true;
PT(EggTexture) tex = new EggTexture(generate_tex_name(), "");
BitmapTex *diffuseTex = (BitmapTex *)mat;
Filename fullpath, outpath;
Filename filename = Filename::from_os_specific(diffuseTex->GetMapName());
_options->_path_replace->full_convert_path(filename, get_model_path(),
fullpath, outpath);
tex->set_filename(outpath);
tex->set_fullpath(fullpath);
apply_texture_properties(*tex, diffuseTex->GetMapChannel());
add_map_channel(pandaMat, diffuseTex->GetMapChannel());
Bitmap *diffuseBitmap = diffuseTex->GetBitmap(0);
if ( diffuseBitmap && diffuseBitmap->HasAlpha()) {
tex->set_format(EggTexture::F_rgba);
} else {
tex->set_format(EggTexture::F_rgb);
}
tex->set_env_type(EggTexture::ET_modulate);
pandaMat._texture_list.push_back(tex);
}
}
////////////////////////////////////////////////////////////////////
// Function: MayaShader::analyze_opacity_maps
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
void MaxToEggConverter::analyze_opacity_maps(PandaMaterial &pandaMat, Texmap *mat) {
if (mat == 0) return;
if (mat->ClassID() == Class_ID(RGBMULT_CLASS_ID, 0)) {
for (int i=0; i<mat->NumSubTexmaps(); i++) {
analyze_opacity_maps(pandaMat, mat->GetSubTexmap(i));
}
return;
}
if (mat->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
pandaMat._any_opacity = true;
BitmapTex *transTex = (BitmapTex *)mat;
Filename fullpath, outpath;
Filename filename = Filename::from_os_specific(transTex->GetMapName());
_options->_path_replace->full_convert_path(filename, get_model_path(),
fullpath, outpath);
// See if this opacity map already showed up.
for (int i=0; i<pandaMat._texture_list.size(); i++) {
EggTexture *tex = pandaMat._texture_list[i];
if ((tex->get_env_type()==EggTexture::ET_modulate)&&(tex->get_fullpath() == fullpath)) {
tex->set_format(EggTexture::F_rgba);
return;
}
}
// Try to find a diffuse map to pair this with as an alpha-texture.
std::string uvname = get_uv_name(transTex->GetMapChannel());
for (int i=0; i<pandaMat._texture_list.size(); i++) {
EggTexture *tex = pandaMat._texture_list[i];
if ((tex->get_env_type()==EggTexture::ET_modulate)&&
(tex->get_format() == EggTexture::F_rgb)&&
(tex->get_uv_name() == uvname)) {
tex->set_format(EggTexture::F_rgba);
tex->set_alpha_filename(outpath);
tex->set_alpha_fullpath(fullpath);
return;
}
}
// Otherwise, just create it as an alpha-texture.
PT(EggTexture) tex = new EggTexture(generate_tex_name(), "");
tex->set_filename(outpath);
tex->set_fullpath(fullpath);
apply_texture_properties(*tex, transTex->GetMapChannel());
add_map_channel(pandaMat, transTex->GetMapChannel());
tex->set_format(EggTexture::F_alpha);
pandaMat._texture_list.push_back(tex);
}
}
////////////////////////////////////////////////////////////////////
// Function: MayaShader::analyze_glow_maps
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
void MaxToEggConverter::analyze_glow_maps(PandaMaterial &pandaMat, Texmap *mat) {
if (mat == 0) return;
if (mat->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
BitmapTex *gtex = (BitmapTex *)mat;
Filename fullpath, outpath;
Filename filename = Filename::from_os_specific(gtex->GetMapName());
_options->_path_replace->full_convert_path(filename, get_model_path(),
fullpath, outpath);
// Try to find a diffuse map to pair this with as an alpha-texture.
std::string uvname = get_uv_name(gtex->GetMapChannel());
for (int i=0; i<pandaMat._texture_list.size(); i++) {
EggTexture *tex = pandaMat._texture_list[i];
if ((tex->get_env_type()==EggTexture::ET_modulate)&&
(tex->get_format() == EggTexture::F_rgb)&&
(tex->get_uv_name() == uvname)) {
tex->set_env_type(EggTexture::ET_modulate_glow);
tex->set_format(EggTexture::F_rgba);
tex->set_alpha_filename(outpath);
tex->set_alpha_fullpath(fullpath);
return;
}
}
// Otherwise, just create it as a separate glow-texture.
PT(EggTexture) tex = new EggTexture(generate_tex_name(), "");
tex->set_env_type(EggTexture::ET_glow);
tex->set_filename(outpath);
tex->set_fullpath(fullpath);
apply_texture_properties(*tex, gtex->GetMapChannel());
add_map_channel(pandaMat, gtex->GetMapChannel());
tex->set_format(EggTexture::F_alpha);
pandaMat._texture_list.push_back(tex);
}
}
////////////////////////////////////////////////////////////////////
// Function: MayaShader::analyze_gloss_maps
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
void MaxToEggConverter::analyze_gloss_maps(PandaMaterial &pandaMat, Texmap *mat) {
if (mat == 0) return;
if (mat->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
pandaMat._any_gloss = true;
BitmapTex *gtex = (BitmapTex *)mat;
Filename fullpath, outpath;
Filename filename = Filename::from_os_specific(gtex->GetMapName());
_options->_path_replace->full_convert_path(filename, get_model_path(),
fullpath, outpath);
// Try to find a diffuse map to pair this with as an alpha-texture.
std::string uvname = get_uv_name(gtex->GetMapChannel());
for (int i=0; i<pandaMat._texture_list.size(); i++) {
EggTexture *tex = pandaMat._texture_list[i];
if ((tex->get_env_type()==EggTexture::ET_modulate)&&
(tex->get_format() == EggTexture::F_rgb)&&
(tex->get_uv_name() == uvname)) {
tex->set_env_type(EggTexture::ET_modulate_gloss);
tex->set_format(EggTexture::F_rgba);
tex->set_alpha_filename(outpath);
tex->set_alpha_fullpath(fullpath);
return;
}
}
// Otherwise, just create it as a separate gloss-texture.
PT(EggTexture) tex = new EggTexture(generate_tex_name(), "");
tex->set_env_type(EggTexture::ET_gloss);
tex->set_filename(outpath);
tex->set_fullpath(fullpath);
apply_texture_properties(*tex, gtex->GetMapChannel());
add_map_channel(pandaMat, gtex->GetMapChannel());
tex->set_format(EggTexture::F_alpha);
pandaMat._texture_list.push_back(tex);
}
}
////////////////////////////////////////////////////////////////////
// Function: MayaShader::analyze_normal_maps
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
void MaxToEggConverter::analyze_normal_maps(PandaMaterial &pandaMat, Texmap *mat) {
if (mat == 0) return;
if (mat->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
pandaMat._any_normal = true;
BitmapTex *ntex = (BitmapTex *)mat;
Filename fullpath, outpath;
Filename filename = Filename::from_os_specific(ntex->GetMapName());
_options->_path_replace->full_convert_path(filename, get_model_path(),
fullpath, outpath);
PT(EggTexture) tex = new EggTexture(generate_tex_name(), "");
tex->set_env_type(EggTexture::ET_normal);
tex->set_filename(outpath);
tex->set_fullpath(fullpath);
apply_texture_properties(*tex, ntex->GetMapChannel());
add_map_channel(pandaMat, ntex->GetMapChannel());
tex->set_format(EggTexture::F_rgb);
pandaMat._texture_list.push_back(tex);
}
}
////////////////////////////////////////////////////////////////////
// Function: MayaShader::add_map_channel
// Access: Private
// Description: Adds the specified map channel to the map channel
// list, if it's not already there.
////////////////////////////////////////////////////////////////////
void MaxToEggConverter::add_map_channel(PandaMaterial &pandaMat, int chan) {
for (int i=0; i<pandaMat._map_channels.size(); i++) {
if (pandaMat._map_channels[i] == chan) {
return;
}
}
pandaMat._map_channels.push_back(chan);
}
////////////////////////////////////////////////////////////////////
// Function: MayaShader::generate_tex_name
// Access: Private
// Description: Generates an arbitrary unused texture name.
////////////////////////////////////////////////////////////////////
std::string MaxToEggConverter::generate_tex_name() {
ostringstream name_strm;
name_strm << "Tex" << ++_cur_tref;
return name_strm.str();
}
////////////////////////////////////////////////////////////////////
// Function: MayaShader::get_uv_name
// Access: Private
// Description: Returns the UV-name of the nth map-channel.
////////////////////////////////////////////////////////////////////
std::string MaxToEggConverter::get_uv_name(int channel) {
ostringstream uvname;
uvname << "m" << channel;
return uvname.str();
}
////////////////////////////////////////////////////////////////////
// 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, int channel) {
tex.set_uv_name(get_uv_name(channel));
tex.set_minfilter(EggTexture::FT_linear_mipmap_linear);
tex.set_magfilter(EggTexture::FT_linear);
EggTexture::WrapMode wrap_u = EggTexture::WM_repeat;
EggTexture::WrapMode wrap_v = EggTexture::WM_repeat;
tex.set_wrap_u(wrap_u);
tex.set_wrap_v(wrap_v);
}
////////////////////////////////////////////////////////////////////
// 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<EggGroup *> 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<EggGroup *>::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;
}
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<IDerivedObject*>(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;
}