void NifImporter::SetNormals(Mesh& mesh, const vector<Niflib::Triangle>& tris, const vector<Niflib::Vector3>& n) { mesh.checkNormals(TRUE); if (n.size() > 0) { bool needNormals = false; for (unsigned int i=0; i<n.size(); i++){ Vector3 v = n[i]; Point3 norm(v.x, v.y, v.z); if (norm != mesh.getNormal(i)) { needNormals = true; break; } } if (needNormals) { #if VERSION_3DSMAX > ((5000<<16)+(15<<8)+0) // Version 5 mesh.SpecifyNormals(); MeshNormalSpec *specNorms = mesh.GetSpecifiedNormals (); if (NULL != specNorms) { specNorms->ClearAndFree(); specNorms->SetNumFaces(tris.size()); specNorms->SetNumNormals(n.size()); Point3* norms = specNorms->GetNormalArray(); for (unsigned int i=0; i<n.size(); i++){ Vector3 v = n[i]; norms[i] = Point3(v.x, v.y, v.z); } MeshNormalFace* pFaces = specNorms->GetFaceArray(); for (unsigned int i=0; i<tris.size(); i++){ const Triangle& tri = tris[i]; pFaces[i].SpecifyNormalID(0, tri.v1); pFaces[i].SpecifyNormalID(1, tri.v2); pFaces[i].SpecifyNormalID(2, tri.v3); } #if VERSION_3DSMAX > ((7000<<16)+(15<<8)+0) // Version 7+ specNorms->SetAllExplicit(true); #else for (int i=0; i<specNorms->GetNumNormals(); ++i) { specNorms->SetNormalExplicit(i, true); } #endif specNorms->CheckNormals(); } #endif } } }
bool Exporter::splitMesh(INode *node, Mesh& mesh, FaceGroups &grps, TimeValue t, vector<Color4>& vertColors, bool noSplit) { Mtl* nodeMtl = node->GetMtl(); Matrix3 tm = node->GetObjTMAfterWSM(t); // Order of the vertices. Get 'em counter clockwise if the objects is // negatively scaled. int vi[3]; if (TMNegParity(tm)) { vi[0] = 2; vi[1] = 1; vi[2] = 0; } else { vi[0] = 0; vi[1] = 1; vi[2] = 2; } Matrix3 flip; flip.IdentityMatrix(); flip.Scale(Point3(1, -1, 1)); int nv = mesh.getNumVerts(); int nf = mesh.getNumFaces(); if (noSplit) { int nv = mesh.getNumVerts(); int nf = mesh.getNumFaces(); // Dont split the mesh at all. For debugging purposes. FaceGroup& grp = grps[0]; grp.vidx.resize(nv, -1); grp.verts.resize(nv); grp.faces.resize(nf); grp.uvs.resize(nv); grp.vnorms.resize(nv); grp.fidx.resize(nf); Matrix3 texm; getTextureMatrix(texm, getMaterial(node, 0)); texm *= flip; for (int face=0; face<nf; ++face) { grp.fidx[face] = face; for (int vi=0; vi<3; ++vi) { int idx = mesh.faces[face].getVert(vi); grp.faces[face][vi] = idx; // Calculate normal Point3 norm; #if VERSION_3DSMAX <= ((5000<<16)+(15<<8)+0) // Version 5 norm = getVertexNormal(&mesh, face, mesh.getRVertPtr(idx)); #else MeshNormalSpec *specNorms = mesh.GetSpecifiedNormals (); if (NULL != specNorms && specNorms->GetNumNormals() != 0) norm = specNorms->GetNormal(face, vi); else norm = getVertexNormal(&mesh, face, mesh.getRVertPtr(idx)); #endif Point3 uv; if (mesh.tVerts && mesh.tvFace) { uv = mesh.tVerts[ mesh.tvFace[ face ].t[ vi ]] * texm; uv.y += 1.0f; } if (grp.vidx[idx] == idx){ ASSERT(grp.verts[idx] == TOVECTOR3(mesh.getVert(idx))); //ASSERT(vg.norm == norm); //Point3 uv = mesh.getTVert(idx); //if (mesh.getNumTVerts() > 0) //{ // ASSERT(grp.uvs[idx].u == uv.x && grp.uvs[idx].v == uv.y); //} } else { grp.vidx[idx] = idx; grp.verts[idx] = TOVECTOR3(mesh.getVert(idx)); //grp.uvs[idx].u = uv.x; //grp.uvs[idx].v = uv.y; grp.vnorms[idx] = TOVECTOR3(norm); } } } for (int i=0; i<nv; ++i) { ASSERT(grp.vidx[i] != -1); } } else { int face, numSubMtls = nodeMtl?nodeMtl->NumSubMtls():0; for (face=0; face<mesh.getNumFaces(); face++) { int mtlID = (numSubMtls!=0) ? (mesh.faces[face].getMatID() % numSubMtls) : 0; Mtl *mtl = getMaterial(node, mtlID); Matrix3 texm; getTextureMatrix(texm, mtl); texm *= flip; FaceGroup& grp = grps[mtlID]; if (grp.uvMapping.size() == 0) // Only needs to be done once per face group { int nmaps = 0; int nmapsStart = max(1, mesh.getNumMaps() - (mesh.mapSupport(0) ? 1 : 0)); // Omit vertex color map. for (int ii = 1; ii <= nmapsStart; ii++) // Winnow out the unsupported maps. { if (!mesh.mapSupport(ii)) continue; grp.uvMapping[ii] = nmaps++; } grp.uvs.resize(nmaps == 0 ? 1 : nmaps); } if (nv > int(grp.verts.capacity())) { grp.vgrp.reserve(nv); grp.verts.reserve(nv); grp.vnorms.reserve(nv); for (int i=0; i<grp.uvs.size(); ++i) grp.uvs[i].reserve(nv); grp.vcolors.reserve(nv); grp.vidx.reserve(nv); } if (nf > int(grp.faces.capacity())) { grp.faces.reserve(nf); grp.fidx.reserve(nf); } Triangle tri; for (int i=0; i<3; i++) tri[i] = addVertex(grp, face, vi[i], &mesh, texm, vertColors); grp.faces.push_back(tri); if (grp.fidx.size() < nf) grp.fidx.resize(nf,-1); grp.fidx[face] = grp.faces.size() - 1; } } return true; }
int Exporter::addVertex(FaceGroup &grp, int face, int vi, Mesh *mesh, const Matrix3 &texm, vector<Color4>& vertColors) { VertexGroup vg; int vidx; vidx = vg.idx = mesh->faces[ face ].v[ vi ]; vg.pt = mesh->verts[ vidx ]; #if VERSION_3DSMAX <= ((5000<<16)+(15<<8)+0) // Version 5 vg.norm = getVertexNormal(mesh, face, mesh->getRVertPtr(vidx)); #else MeshNormalSpec *specNorms = mesh->GetSpecifiedNormals (); if (NULL != specNorms && specNorms->GetNumNormals() != 0) vg.norm = specNorms->GetNormal(face, vi); else vg.norm = getVertexNormal(mesh, face, mesh->getRVertPtr(vidx)); #endif int nmaps = grp.uvMapping.size(); map<int,int>::iterator UVSetIter; vg.uvs.resize(nmaps > 0 ? nmaps : 1); if (nmaps > 0) { for (UVSetIter=grp.uvMapping.begin() ; UVSetIter != grp.uvMapping.end(); UVSetIter++ ) { int maxUVIdx = (*UVSetIter).first; int nifUVIdx = (*UVSetIter).second; TexCoord& uvs = vg.uvs[nifUVIdx]; UVVert *uv = mesh->mapVerts(maxUVIdx); TVFace *tv = mesh->mapFaces(maxUVIdx); if (uv && tv) { Point3 uvw = uv[ tv[ face ].t[ vi ]] * texm; uvs.u = uvw[0]; uvs.v = uvw[1] + 1.0f; } } } else { if (mesh->tVerts && mesh->tvFace) { Point3 uvw = mesh->tVerts[ mesh->tvFace[ face ].t[ vi ]] * texm; vg.uvs[0].u = uvw[0]; vg.uvs[0].v = uvw[1] + 1.0f; } } vg.color = Color4(1.0f, 1.0f, 1.0f); if (mVertexColors && !vertColors.empty()){ TVFace *vcFace = mesh->vcFaceData ? mesh->vcFaceData : mesh->vcFace; if (vcFace) { int vidx = vcFace[ face ].t[ vi ]; vg.color = vertColors[vidx]; } } VertexCompare vc(grp, Exporter::mWeldThresh, Exporter::mNormThresh, Exporter::mUVWThresh); int n = grp.verts.size(); #if 0 IntRange range = std::equal_range(grp.vmap.begin(), grp.vmap.end(), vg, vc); if (range.first != range.second) { return (*range.first); } grp.vmap.insert(range.first, n); #else for (int i=0; i<grp.vgrp.size(); i++) { if ( vc.compare(vg, i) == 0 ) return i; } grp.vmap.push_back(n); #endif grp.vidx.push_back(vidx); grp.vgrp.push_back(vg); grp.verts.push_back(TOVECTOR3(vg.pt)); grp.vnorms.push_back(TOVECTOR3(vg.norm)); for (int i=0; i< grp.uvs.size(); ++i) { TexCoords& uvs = grp.uvs[i]; uvs.push_back(vg.uvs[i]); } grp.vcolors.push_back(vg.color); return n; }
Exporter::Result Exporter::exportMesh(NiNodeRef &ninode, INode *node, TimeValue t) { ObjectState os = node->EvalWorldState(t); bool local = !mFlattenHierarchy; TriObject *tri = (TriObject *)os.obj->ConvertToType(t, Class_ID(TRIOBJ_CLASS_ID, 0)); if (!tri) return Skip; Mesh *copymesh = NULL; Mesh *mesh = &tri->GetMesh(); Matrix3 mtx(true), rtx(true); if (Exporter::mCollapseTransforms) { mtx = GetNodeLocalTM(node, t); mtx.NoTrans(); Quat q(mtx); q.MakeMatrix(rtx); mesh = copymesh = new Mesh(*mesh); { int n = mesh->getNumVerts(); for ( unsigned int i = 0; i < n; ++i ) { Point3& vert = mesh->getVert(i); vert = mtx * vert; } mesh->checkNormals(TRUE); #if VERSION_3DSMAX > ((5000<<16)+(15<<8)+0) // Version 6+ MeshNormalSpec *specNorms = mesh->GetSpecifiedNormals (); if (NULL != specNorms) { specNorms->CheckNormals(); for ( unsigned int i = 0; i < specNorms->GetNumNormals(); ++i ) { Point3& norm = specNorms->Normal(i); norm = (rtx * norm).Normalize(); } } #endif } } // Note that calling setVCDisplayData will clear things like normals so we set this up first vector<Color4> vertColors; if (mVertexColors) { bool hasvc = false; if (mesh->mapSupport(MAP_ALPHA)) { mesh->setVCDisplayData(MAP_ALPHA); int n = mesh->getNumVertCol(); if (n > vertColors.size()) vertColors.assign(n, Color4(1.0f, 1.0f, 1.0f, 1.0f)); VertColor *vertCol = mesh->vertColArray; if (vertCol) { for (int i=0; i<n; ++i) { VertColor c = vertCol[ i ]; float a = (c.x + c.y + c.z) / 3.0f; vertColors[i].a = a; hasvc |= (a != 1.0f); } } } if (mesh->mapSupport(0)) { mesh->setVCDisplayData(0); VertColor *vertCol = mesh->vertColArray; int n = mesh->getNumVertCol(); if (n > vertColors.size()) vertColors.assign(n, Color4(1.0f, 1.0f, 1.0f, 1.0f)); if (vertCol) { for (int i=0; i<n; ++i) { VertColor col = vertCol[ i ]; vertColors[i] = Color4(col.x, col.y, col.z, vertColors[i].a); hasvc |= (col.x != 1.0f || col.y != 1.0f || col.z != 1.0f); } } } if (!hasvc) vertColors.clear(); } #if VERSION_3DSMAX <= ((5000<<16)+(15<<8)+0) // Version 5 mesh->checkNormals(TRUE); #else MeshNormalSpec *specNorms = mesh->GetSpecifiedNormals (); if (NULL != specNorms) { specNorms->CheckNormals(); if (specNorms->GetNumNormals() == 0) mesh->checkNormals(TRUE); } else { mesh->checkNormals(TRUE); } #endif Result result = Ok; Modifier* geomMorpherMod = GetMorpherModifier(node); bool noSplit = FALSE; // bool noSplit = (NULL != geomMorpherMod); while (1) { FaceGroups grps; if (!splitMesh(node, *mesh, grps, t, vertColors, noSplit)) { result = Error; break; } bool exportStrips = mTriStrips && (Exporter::mNifVersionInt > VER_4_2_2_0); Matrix44 tm = Matrix44::IDENTITY; if ( mExportExtraNodes || (mExportType != NIF_WO_ANIM && isNodeKeyed(node) ) ) { tm = TOMATRIX4(getObjectTransform(node, t, false) * Inverse(getNodeTransform(node, t, false))); } else { Matrix33 rot; Vector3 trans; objectTransform(rot, trans, node, t, local); tm = Matrix44(trans, rot, 1.0f); } tm = TOMATRIX4(Inverse(mtx)) * tm; TSTR basename = node->NodeName(); TSTR format = (!basename.isNull() && grps.size() > 1) ? "%s:%d" : "%s"; int i=1; FaceGroups::iterator grp; for (grp=grps.begin(); grp!=grps.end(); ++grp, ++i) { string name = FormatString(format, basename.data(), i); NiTriBasedGeomRef shape = makeMesh(ninode, getMaterial(node, grp->first), grp->second, exportStrips); if (shape == NULL) { result = Error; break; } if (node->IsHidden()) shape->SetVisibility(false); shape->SetName(name); shape->SetLocalTransform(tm); if (Exporter::mZeroTransforms) { shape->ApplyTransforms(); } makeSkin(shape, node, grp->second, t); if (geomMorpherMod) { vector<Vector3> verts = shape->GetData()->GetVertices(); exportGeomMorpherControl(geomMorpherMod, verts, shape->GetData()->GetVertexIndices(), shape); shape->GetData()->SetConsistencyFlags(CT_VOLATILE); } } break; } if (tri != os.obj) tri->DeleteMe(); if (copymesh) delete copymesh; return result; }
void RenderMesh::ConvertFaces(Mesh *Mesh, int MatIndex, Tab<Vert3> &Verts, Tab<Face3> &Faces, bool NegScale) { Face3 TmpFace; Vert3 TmpVert; BitArray Written; int i,j,k,NumFace; int NumUV,UVCount,Index; int NumVert,Count,VIndex; Face *aFace; Tab<BasisVert> FNormals; Tab<VNormal> Normals; UVVert *UVVert; TVFace *UVFace; Point3 S,T,SxT; unsigned long Sg; bool useMeshNorms = false; if(NegScale) { gVIndex[0] = 2; gVIndex[1] = 1; gVIndex[2] = 0; } else { gVIndex[0] = 0; gVIndex[1] = 1; gVIndex[2] = 2; } // Do we have an EditNormal modifier present - if so we use those normals instead. // We only use this if they have been applied on a face with smoothing groups, otherwise // it messes up the tangent space calculation. Probably not the most obtmized route, but it // works... MeshNormalSpec * meshNorm = Mesh->GetSpecifiedNormals(); if(meshNorm && meshNorm->GetNumNormals()) useMeshNorms = true; NumFace = 0; for(i=0; i < Mesh->getNumFaces(); i++) { if(!Mesh->faces[i].Hidden()) { Index = Mesh->getFaceMtlIndex(i) + 1; if(Index == MatIndex || MatIndex == 0) { NumFace++; } } } NumVert = Mesh->getNumVerts(); Verts.SetCount(NumVert); Faces.SetCount(NumFace); if(NumVert == 0 || NumFace == 0) { return; } ComputeVertexNormals(Mesh,FNormals,Normals,NegScale); Written.SetSize(Mesh->getNumVerts()); Written.ClearAll(); NumUV = Mesh->getNumMaps(); if(NumUV) { Count = 0; if(NumUV > MAX_TMUS + 1) { NumUV = MAX_TMUS + 1; } for(i=0; i < Mesh->getNumFaces(); i++) { aFace = &Mesh->faces[i]; TmpFace.m_Num[0] = aFace->v[gVIndex[0]]; TmpFace.m_Num[1] = aFace->v[gVIndex[1]]; TmpFace.m_Num[2] = aFace->v[gVIndex[2]]; Sg = aFace->smGroup; for(j=0; j < 3; j++) { VIndex = aFace->v[gVIndex[j]]; TmpVert.m_Pos = Mesh->verts[VIndex]; if(Sg) { if(useMeshNorms) { int normID = meshNorm->Face(i).GetNormalID(gVIndex[j]); TmpVert.m_Normal = meshNorm->Normal(normID).Normalize(); Normals[VIndex].GetNormal(Sg,S,T,SxT); } else TmpVert.m_Normal = Normals[VIndex].GetNormal(Sg,S,T,SxT); TmpVert.m_S = S; TmpVert.m_T = T; TmpVert.m_SxT = SxT; } else { TmpVert.m_Normal = FNormals[i].m_Normal; TmpVert.m_S = FNormals[i].m_S; TmpVert.m_T = FNormals[i].m_T; TmpVert.m_SxT = FNormals[i].m_SxT; } UVCount = 0; TmpVert.m_Sg = Sg; for(k=0;k<m_MapChannels.Count();k++) { int index = m_MapChannels[k]; if(Mesh->getNumMapVerts(index)) { UVVert = Mesh->mapVerts(index); UVFace = Mesh->mapFaces(index); TmpVert.m_UV[k].x = UVVert[UVFace[i].t[gVIndex[j]]].x; TmpVert.m_UV[k].y = UVVert[UVFace[i].t[gVIndex[j]]].y; } else { TmpVert.m_UV[k].x = 0.0f; TmpVert.m_UV[k].y = 0.0f; } } if(Written[VIndex]) { if((Sg == 0) || (Verts[VIndex].m_Sg != TmpVert.m_Sg) || (!UVVertEqual(Verts[VIndex].m_UV[0],TmpVert.m_UV[0]))) { TmpFace.m_Num[j] = Verts.Count(); Verts.Append(1,&TmpVert,10); } } else { Verts[VIndex] = TmpVert; Written.Set(VIndex); } } if(!Mesh->faces[i].Hidden()) { Index = Mesh->getFaceMtlIndex(i) + 1; if(Index == MatIndex || MatIndex == 0) { Faces[Count++] = TmpFace; } } } } else { for(i=0; i < Mesh->getNumFaces(); i++) { aFace = &Mesh->faces[i]; Faces[i].m_Num[0] = aFace->v[gVIndex[0]]; Faces[i].m_Num[1] = aFace->v[gVIndex[1]]; Faces[i].m_Num[2] = aFace->v[gVIndex[2]]; for(j=0; j < 3; j++) { VIndex = aFace->v[gVIndex[j]]; Verts[VIndex].m_Pos = Mesh->verts[VIndex]; Verts[VIndex].m_Normal = Normals[VIndex].GetNormal(aFace->smGroup,S,T,SxT); Verts[VIndex].m_S = Point3(0.0f,0.0f,0.0f); Verts[VIndex].m_T = Point3(0.0f,0.0f,0.0f); Verts[VIndex].m_SxT = Point3(0.0f,0.0f,0.0f); for(k=0; k < MAX_TMUS; k++) { Verts[VIndex].m_UV[k].x = 0.0f; Verts[VIndex].m_UV[k].y = 0.0f; } } } } Verts.Shrink(); }
void SolidifyPW::ModifyObject(TimeValue t, ModContext &mc, ObjectState * os, INode *node) { //TODO: Add the code for actually modifying the object meshInfo.Free(); if (os->obj->IsSubClassOf(triObjectClassID)) { TriObject *tobj = (TriObject*)os->obj; Mesh &mesh = tobj->GetMesh(); Interval iv = FOREVER; float a,oa; pblock->GetValue(pb_amount,t,a,iv); pblock->GetValue(pb_oamount,t,oa,iv); if (a == oa) oa += 0.00001f; BOOL overrideMatID; int matid; pblock->GetValue(pb_overridematid,t,overrideMatID,iv); pblock->GetValue(pb_matid,t,matid,iv); matid--; if (!overrideMatID) matid = -1; BOOL overridesg; int sg; pblock->GetValue(pb_overridesg,t,overridesg,iv); pblock->GetValue(pb_sg,t,sg,iv); if (!overridesg) sg = -1; int edgeMap; pblock->GetValue(pb_edgemap,t,edgeMap,iv); float tvOffset; pblock->GetValue(pb_tvoffset,t,tvOffset,iv); BOOL ioverrideMatID; int imatid; pblock->GetValue(pb_overrideinnermatid,t,ioverrideMatID,iv); pblock->GetValue(pb_innermatid,t,imatid,iv); imatid--; if (!ioverrideMatID) imatid = -1; BOOL ooverrideMatID; int omatid; pblock->GetValue(pb_overrideoutermatid,t,ooverrideMatID,iv); pblock->GetValue(pb_outermatid,t,omatid,iv); omatid--; if (!ooverrideMatID) omatid = -1; BOOL selEdges, selInner,selOuter; static BOOL selEdgesPrev = FALSE; static BOOL selInnerPrev = FALSE; static BOOL selOuterPrev = FALSE; BOOL updateUI = FALSE; pblock->GetValue(pb_seledges,t,selEdges,iv); pblock->GetValue(pb_selinner,t,selInner,iv); pblock->GetValue(pb_selouter,t,selOuter,iv); if (selEdges && (!selEdgesPrev)) updateUI = TRUE; if (selInner && (!selInnerPrev)) updateUI = TRUE; if (selOuter && (!selOuterPrev)) updateUI = TRUE; selEdgesPrev = selEdges; selInnerPrev = selInner; selOuterPrev = selOuter; if (selEdges || selInner|| selOuter) { mesh.dispFlags = DISP_SELFACES; mesh.selLevel = MESH_FACE; } int segments = 1; pblock->GetValue(pb_segments,t,segments,iv); if (segments < 1) segments = 1; BOOL fixupCorners; pblock->GetValue(pb_fixupcorners,t,fixupCorners,iv); BOOL autoSmooth; float smoothAngle; pblock->GetValue(pb_autosmooth,t,autoSmooth,iv); pblock->GetValue(pb_autosmoothangle,t,smoothAngle,iv); BOOL bevel; INode *node; pblock->GetValue(pb_bevel,t,bevel,iv); pblock->GetValue(pb_bevelshape,t,node,iv); PolyShape shape; if ((bevel) && node) { ObjectState nos = node->EvalWorldState(t); if (nos.obj->IsShapeObject()) { ShapeObject *pathOb = (ShapeObject*)nos.obj; if (!pathOb->NumberOfCurves()) { bevel = FALSE; } else { pathOb->MakePolyShape(t, shape,PSHAPE_BUILTIN_STEPS,TRUE); if (shape.lines[0].IsClosed()) bevel = FALSE; ; } } } DWORD selLevel = mesh.selLevel; mesh.faceSel.ClearAll(); if (bevel) meshInfo.MakeSolid(&mesh,segments, a,oa,matid, sg,edgeMap,tvOffset,imatid,omatid,selEdges,selInner,selOuter,fixupCorners,autoSmooth,smoothAngle,&shape.lines[0]); else meshInfo.MakeSolid(&mesh,segments, a,oa,matid, sg,edgeMap,tvOffset,imatid,omatid,selEdges,selInner,selOuter,fixupCorners,autoSmooth,smoothAngle,NULL); mesh.selLevel = selLevel; mesh.InvalidateTopologyCache (); for (int i = 0; i < mesh.numFaces; i++) { for (int j = 0; j < 3; j++) { int index = mesh.faces[j].v[j]; if ((index < 0) || (index >= mesh.numVerts)) DebugPrint(_T("Invalid face %d(%d) %d\n"),i,j,index); } } int numMaps = mesh.getNumMaps(); for (int mp = -NUM_HIDDENMAPS; mp < numMaps; mp++) { if (!mesh.mapSupport(mp)) continue; Point3 *uvw = mesh.mapVerts(mp); TVFace *uvwFace = mesh.mapFaces(mp); if ((uvw) && (uvwFace)) { int numberTVVerts = mesh.getNumMapVerts(mp); for (int i = 0; i < mesh.numFaces; i++) { for (int j = 0; j < 3; j++) { int index = uvwFace[i].t[j]; if ((index < 0) || (index >= numberTVVerts)) DebugPrint(_T("Invalid Map %d tvface %d(%d) %d\n"),mp,i,j,index); } } } } os->obj->UpdateValidity(GEOM_CHAN_NUM,iv); os->obj->UpdateValidity(TOPO_CHAN_NUM,iv); MeshNormalSpec *pNormSpec = (MeshNormalSpec *) mesh.GetInterface (MESH_NORMAL_SPEC_INTERFACE); if (pNormSpec && pNormSpec->GetNumFaces() > 0) { pNormSpec->SetParent(&mesh); pNormSpec->BuildNormals(); pNormSpec->ComputeNormals(); } if ((updateUI) && (ip)) { ip->PipeSelLevelChanged(); } } EnableUIControls(); }
bool NifImporter::ImportMultipleGeometry(NiNodeRef parent, vector<NiTriBasedGeomRef>& glist) { bool ok = true; if (glist.empty()) return false; ImpNode *node = i->CreateNode(); if(!node) return false; INode *inode = node->GetINode(); TriObject *triObject = CreateNewTriObject(); node->Reference(triObject); string name = parent->GetName(); node->SetName(wide(name).c_str()); // Texture Mesh& mesh = triObject->GetMesh(); vector< pair<int, int> > vert_range, tri_range; vector<Triangle> tris; vector<Vector3> verts; int submats = glist.size(); // Build list of vertices and triangles. Optional components like normals will be handled later. for (vector<NiTriBasedGeomRef>::iterator itr = glist.begin(), end = glist.end(); itr != end; ++itr) { NiTriBasedGeomDataRef triGeomData = StaticCast<NiTriBasedGeomData>((*itr)->GetData()); // Get verts and collapse local transform into them int nVertices = triGeomData->GetVertexCount(); vector<Vector3> subverts = triGeomData->GetVertices(); Matrix44 transform = (*itr)->GetLocalTransform(); //Apply the transformations if (transform != Matrix44::IDENTITY) { for ( unsigned int i = 0; i < subverts.size(); ++i ) subverts[i] = transform * subverts[i]; } vert_range.push_back( pair<int,int>( verts.size(), verts.size() + subverts.size()) ); verts.insert(verts.end(), subverts.begin(), subverts.end()); vector<Triangle> subtris = triGeomData->GetTriangles(); for (vector<Triangle>::iterator itr = subtris.begin(), end = subtris.end(); itr != end; ++itr) { (*itr).v1 += nVertices, (*itr).v2 += nVertices, (*itr).v3 += nVertices; } tri_range.push_back( pair<int,int>( tris.size(), tris.size() + subtris.size()) ); tris.insert(tris.end(), subtris.begin(), subtris.end()); } // Transform up-to-parent Matrix44 baseTM = (importBones) ? Matrix44::IDENTITY : parent->GetWorldTransform(); node->SetTransform(0,TOMATRIX3(baseTM)); // Set vertices and triangles mesh.setNumVerts(verts.size()); mesh.setNumTVerts(verts.size(), TRUE); for (int i=0, n=verts.size(); i < n; ++i){ Vector3 &v = verts[i]; mesh.verts[i].Set(v.x, v.y, v.z); } mesh.setNumFaces(tris.size()); mesh.setNumTVFaces(tris.size()); for (int submat=0; submat<submats; ++submat) { int t_start = tri_range[submat].first, t_end = tri_range[submat].second; for (int i=t_start; i<t_end; ++i) { Triangle& t = tris[i]; Face& f = mesh.faces[i]; f.setVerts(t.v1, t.v2, t.v3); f.Show(); f.setEdgeVisFlags(EDGE_VIS, EDGE_VIS, EDGE_VIS); f.setMatID(-1); TVFace& tf = mesh.tvFace[i]; tf.setTVerts(t.v1, t.v2, t.v3); } } mesh.buildNormals(); bool bSpecNorms = false; MultiMtl *mtl = NULL; int igeom = 0; for (vector<NiTriBasedGeomRef>::iterator itr = glist.begin(), end = glist.end(); itr != end; ++itr, ++igeom) { NiTriBasedGeomDataRef triGeomData = StaticCast<NiTriBasedGeomData>((*itr)->GetData()); int v_start = vert_range[igeom].first, v_end = vert_range[igeom].second; int t_start = tri_range[igeom].first, t_end = tri_range[igeom].second; // Normals vector<Vector3> subnorms = triGeomData->GetNormals(); Matrix44 rotation = (*itr)->GetLocalTransform().GetRotation(); if (rotation != Matrix44::IDENTITY) { for ( unsigned int i = 0; i < subnorms.size(); ++i ) subnorms[i] = rotation * subnorms[i]; } if (!subnorms.empty()) { #if VERSION_3DSMAX > ((5000<<16)+(15<<8)+0) // Version 5 // Initialize normals if necessary if (!bSpecNorms) { bSpecNorms = true; mesh.SpecifyNormals(); MeshNormalSpec *specNorms = mesh.GetSpecifiedNormals(); if (NULL != specNorms) { specNorms->BuildNormals(); //specNorms->ClearAndFree(); //specNorms->SetNumFaces(tris.size()); //specNorms->SetNumNormals(n.size()); } } MeshNormalSpec *specNorms = mesh.GetSpecifiedNormals(); if (NULL != specNorms) { Point3* norms = specNorms->GetNormalArray(); for (int i=0, n=subnorms.size(); i<n; i++){ Vector3& v = subnorms[i]; norms[i+v_start] = Point3(v.x, v.y, v.z); } //MeshNormalFace* pFaces = specNorms->GetFaceArray(); //for (int i=0; i<tris.size(); i++){ // Triangle& tri = tris[i]; // MeshNormalFace& face = pFaces[i+t_start]; // face.SpecifyNormalID(0, tri.v1); // face.SpecifyNormalID(1, tri.v2); // face.SpecifyNormalID(2, tri.v3); //} #if VERSION_3DSMAX > ((7000<<16)+(15<<8)+0) // Version 7+ specNorms->SetAllExplicit(true); #endif specNorms->CheckNormals(); } #endif } // uv texture info if (triGeomData->GetUVSetCount() > 0) { vector<TexCoord> texCoords = triGeomData->GetUVSet(0); for (int i=0, n = texCoords.size(); i<n; ++i) { TexCoord& texCoord = texCoords[i]; mesh.tVerts[i+v_start].Set(texCoord.u, (flipUVTextures) ? 1.0f-texCoord.v : texCoord.v, 0); } } vector<Color4> cv = triGeomData->GetColors(); ImportVertexColor(inode, triObject, tris, cv, v_start); if ( StdMat2* submtl = ImportMaterialAndTextures(node, (*itr)) ) { if (mtl == NULL) { mtl = NewDefaultMultiMtl(); gi->GetMaterialLibrary().Add(mtl); inode->SetMtl(mtl); } // SubMatIDs do not have to be contiguous so we just use the offset mtl->SetSubMtlAndName(igeom, submtl, submtl->GetName()); for (int i=t_start; i<t_end; ++i) mesh.faces[i].setMatID(igeom); } if (enableSkinSupport) ImportSkin(node, (*itr)); } this->i->AddNodeToScene(node); inode = node->GetINode(); inode->EvalWorldState(0); for (vector<NiTriBasedGeomRef>::iterator itr = glist.begin(), end = glist.end(); itr != end; ++itr) { // attach child if (INode *parent = GetNode((*itr)->GetParent())) parent->AttachChild(inode, 1); inode->Hide((*itr)->GetVisibility() ? FALSE : TRUE); } if (removeDegenerateFaces) mesh.RemoveDegenerateFaces(); if (removeIllegalFaces) mesh.RemoveIllegalFaces(); if (weldVertices) WeldVertices(mesh); if (enableAutoSmooth) mesh.AutoSmooth(TORAD(autoSmoothAngle), FALSE, FALSE); return ok; }
void MaxAWDExporter::ExportTriGeom(AWDTriGeom *awdGeom, Object *obj, INode *node, ISkin *skin, IGameMesh * igame_mesh) { if (awdGeom != NULL) { if (awdGeom->get_is_created()) return; else { awdGeom->set_is_created(true); // Extract skinning information (returns number of joints per vertex), // and writes the weights and joints array according to the jpv int jpv=0; awd_float64 *weights=NULL; awd_uint32 *joints=NULL; jpv = ExportSkin(node, skin, &weights, &joints); // Calculate offset matrix from the object TM (which includes geometry offset) // this will be used to transform all vertices into node space. int time=maxInterface->GetTime(); // get the neutral pose time from the vertex (using a dedcated cache for this) bool hasVertexAnim=vetexAnimNeutralPosecache->hasKey(node); if(hasVertexAnim) time=vetexAnimNeutralPosecache->Get(node)*GetTicksPerFrame(); else{ // get the neutral pose time from the skeleton, if any is used if (opts->ExportSkin() && skin && jpv>0) { SkeletonCacheItem *skel = skeletonCache->GetFromBone(skin->GetBone(0)); time=skel->awdSkel->get_neutralPose(); } } Matrix3 offsMtx = node->GetObjectTM(time) * Inverse(node->GetNodeTM(time)); bool isInSkinPoseMode=false; double *mtxData = (double *)malloc(12*sizeof(double)); SerializeMatrix3(offsMtx, mtxData); /*if (skin && jpv>0) { // if the mesh is not in "Skin Pose Mode", // we add the bind-matrix to the offset matrix (so that all points are moved accoridingly) // and later we set the transform matrix of the mesh to the identity-matrix (no transform) ISkinPose * skinPose; skinPose=skinPose->GetISkinPose(*node); //isInSkinPoseMode=skinPose->SkinPoseMode(); Matrix3 mtx; mtx.IdentityMatrix(); if (!isInSkinPoseMode){ Matrix3 bm; bm.IdentityMatrix(); //skin->GetSkinInitTM(node, bm, true); //offsMtx *= bm; //SerializeMatrix3(mtx, mtxData); } skinPose=NULL; }*/ ObjectState os; // Flatten entire modifier stack os = node->EvalWorldState(time); obj = os.obj; // its allready been taken care that the correct obj is submitted to this function, so we can directly convert to TriObject (?) TriObject *triObject = (TriObject*)obj->ConvertToType(time, Class_ID(TRIOBJ_CLASS_ID, 0)); Mesh mesh = triObject->mesh; int numTris = mesh.getNumFaces(); int numVerts = mesh.getNumVerts(); awdGeom->set_originalPointCnt(numVerts); // This could happen for example with splines (No!, this should never happen, because we check for this earlier (?)) if (numTris==0) return; /* //TODO: optional reorder exported faces, so that quads are stored first //we than can store one uint32 as offset into the triangle-list, to reconstruct quads on import int * quadDic=(int *)malloc(sizeof(int) * numTris); int * isExportedDic=(int *)malloc(sizeof(int) * numTris); int cnt1=0; for(cnt1=0;cnt1<numTris;cnt1++){ quadDic[cnt1]=0; isExportedDic[cnt1]=0; } PolyObject *polyObject = (PolyObject*)obj->ConvertToType(time, Class_ID(POLYOBJ_CLASS_ID, 0)); if (polyObject!=NULL){ MNMesh mnMesh = polyObject->GetMesh(); int numFaces = mnMesh.FNum(); int numtri = mnMesh.TriNum(); int idxCnt=0; for(idxCnt=0;idxCnt<numFaces;idxCnt++){ MNFace thisface=mnMesh.f[idxCnt]; if(thisface.TriNum()==2){ Tab<int> intList; thisface.GetTriangles(intList); //quadDic[intList[0]]=intList[1]; //quadDic[intList[1]]=intList[0]; } } //if (polyObject!=obj) // polyObject->DeleteMe(); } */ bool force_split=opts->SplitByMatID();// if true, the GeomUtils will create a SubGeo for each MaterialID used by a face, no matter if they share materials or not bool useUV=opts->ExportUVs();// TODO: check if uvs exists bool useSecUVs=useUV; // TODO: check if second UVs exists (and are requested) bool useNormals=opts->ExportNormals(); // ATTENTION: // we have collected all meshintsances that are using this geometry. // but some material-settings ( UV / SecondUV / explode) can force us to create multiple geometries... // the IGAmeMesh gives acces to some handy functions for exporting meshes // we still need to use the mesh from the standart api, to have access to the correct UV (?) MeshNormalSpec *specificNormals = NULL; if(igame_mesh!=NULL){ int numTrisGameMesh = igame_mesh->GetNumberOfFaces(); if (numTrisGameMesh!=numTris){ return; //ERROR: faceCount of game-mesh is not facecount of api-mesh - should not happen } AWDBlockList * meshInstanceList = awdGeom->get_mesh_instance_list(); if (meshInstanceList==NULL){ return; //ERROR: faceCount of game-mesh is not facecount of api-mesh - should not happen } int numMeshInstances=meshInstanceList->get_num_blocks(); if ((meshInstanceList!=NULL)&&(numMeshInstances==0)){ return; //ERROR: faceCount of game-mesh is not facecount of api-mesh - should not happen } // check if the first UVChannel is available for this mesh ( numTVFaces must be equal to numTris) if (useUV) { try{ if (mesh.mapSupport(1)){ MeshMap * mesh_map; mesh_map = &(mesh.Map(1)); int numTrisMap = mesh_map->getNumFaces(); if (numTrisMap!=numTris){ useUV=false; useSecUVs=false; } } else{ useUV=false; useSecUVs=false; } } catch(...){ useUV=false; useSecUVs=false; } } // check if any normals are available for the mesh if (useNormals) { mesh.SpecifyNormals(); specificNormals = mesh.GetSpecifiedNormals(); int specificNormalCount = specificNormals->GetNumNormals();// for me, this is allways been 0 if (specificNormalCount==0){ specificNormals=NULL; int numNorms = igame_mesh->GetNumberOfNormals(); if (numNorms==0){ useNormals=false; } } } AWD_field_type precision_geo=AWD_FIELD_FLOAT32; if (opts->StorageGeometry()==1) precision_geo=AWD_FIELD_FLOAT64; AWDGeomUtil * geomUtil=new AWDGeomUtil(awdGeom->get_split_faces(), force_split, useUV, useSecUVs, useNormals, 0.0, jpv, precision_geo); // create a list of GUGeom for each Mesh instance. // before collecting the actual geom-data, // we will reduce the number of GUGeoms to the minimum needed to display all mesh-instances correctly geomUtil->createPreGeometries(meshInstanceList); Tab<int> MatIDList=igame_mesh->GetActiveMatIDs(); int matIDCnt; Tab<FaceEx *> facelist; // for each submesh do: for (matIDCnt=0; matIDCnt< MatIDList.Count(); matIDCnt++){ facelist=igame_mesh->GetFacesFromMatID(MatIDList[matIDCnt]); int idx; int faceCnt=facelist.Count(); if (faceCnt>0){ // if the submesh will be used (if the matID is used by any face): AWDBlockList * subMaterialList = new AWDBlockList(); int meshInstCnt=0; // for each mesh instance, apply the material for (meshInstCnt=0;meshInstCnt<numMeshInstances; meshInstCnt++){ AWDMeshInst * awdMesh=(AWDMeshInst *)meshInstanceList->getByIndex(meshInstCnt); if (awdMesh==NULL){ return; } else{ AWDBlockList * preMaterials = awdMesh->get_pre_materials(); if (preMaterials==NULL){ return; } else{ bool createDefault = false; AWDMaterial * thisMatBlock = (AWDMaterial *)preMaterials->getByIndex(MatIDList[matIDCnt]); if (thisMatBlock==NULL){ thisMatBlock=(AWDMaterial *)awdMesh->get_defaultMat(); } if (useUV){ if (thisMatBlock->get_mappingChannel()>0) thisMatBlock->set_mappingChannel(checkIfUVMapExists(mesh, numTris, thisMatBlock->get_mappingChannel())); } if (useSecUVs){ if (thisMatBlock->get_secondMappingChannel()>0) thisMatBlock->set_secondMappingChannel(checkIfUVMapExists(mesh, numTris, thisMatBlock->get_secondMappingChannel())); } subMaterialList->force_append(thisMatBlock); } } } geomUtil->add_new_sub_geo_to_preGUgeoms(subMaterialList, MatIDList[matIDCnt]); delete subMaterialList; } } geomUtil->createGeometries(); for (matIDCnt=0; matIDCnt<MatIDList.Count(); matIDCnt++){ facelist=igame_mesh->GetFacesFromMatID(MatIDList[matIDCnt]); int idx; int faceCnt=facelist.Count(); if (faceCnt>0){ int numGeoms=geomUtil->get_geoList()->get_num_blocks(); int geoCnt; for(geoCnt=0; geoCnt<numGeoms; geoCnt++){ GUGeo * thisGUGeo = geomUtil->get_geoList()->get_by_idx(geoCnt); // get the uvs using the index stored in the geomUtil // get the secondUV using the index stored in the geomUtil // get the explode (normals) using the index stored in the geomUtil // ATTENTION: its not a Subgeo but a SubgeoGroup, so it contain several Subgeos if the face or vert lists are to big for one subgeo int thisSubGeoIdx=geomUtil->getSubGeoIdxForMatIdx(MatIDList[matIDCnt]); GUSubGeoGroup *thisSubGeoGroup = thisGUGeo->get_subGeomList()->get_by_idx(thisSubGeoIdx); if (thisSubGeoGroup==NULL){ return; } AWDMaterial * thisAWDMat=(AWDMaterial *)thisSubGeoGroup->materials->getByIndex(0); bool explode=thisAWDMat->get_is_faceted(); MeshMap * mainUVMeshMap=NULL; MeshMap * secondUVMeshMap=NULL; if (thisSubGeoGroup->include_uv){ if (thisAWDMat->get_mappingChannel()>0) mainUVMeshMap = &(mesh.Map(thisAWDMat->get_mappingChannel())); } if (thisSubGeoGroup->include_suv){ if (thisAWDMat->get_secondMappingChannel()>0) secondUVMeshMap = &(mesh.Map(thisAWDMat->get_secondMappingChannel())); } // for each face in the list do: bool hasMultipleUV=false; for (idx=0;idx<faceCnt;idx++){ /*if(isExportedDic[idx]==0){ isExportedDic[idx]=1; if(quadDic[idx]>0){ int yes=0; }*/ // this will create a new SubGeo inside the SubgeoGroup, if the limits are reached thisSubGeoGroup->check_limits(); // create a new vert FaceEx * f=facelist[idx]; int apiFaceIdx=f->meshFaceIndex; TVFace tvface; TVFace tvFaceSecond; Face face = mesh.faces[apiFaceIdx]; DWORD *inds = face.getAllVerts(); if (thisSubGeoGroup->include_uv){ if (mainUVMeshMap!=NULL) tvface = mainUVMeshMap->tf[apiFaceIdx]; else tvface = mesh.tvFace[apiFaceIdx]; } if (thisSubGeoGroup->include_suv){ if (secondUVMeshMap!=NULL) tvFaceSecond = secondUVMeshMap->tf[apiFaceIdx]; else tvFaceSecond = mesh.tvFace[apiFaceIdx]; } Point3 faceNormal; if (geomUtil->include_normals) { // if we want to export normals, but no normals was read, than we need to calculate ourself, // using the face-normal for the angle-calulation if ((igame_mesh==NULL && specificNormals==NULL)||(explode)){ // faceNormal = mesh.getFaceNormal(t); // this crashes 3dsmax (why?), so calulate the facenormal manually: Point3 v0, v1, v2; Tab<Point3> fnorms; v0 = mesh.getVert(face.getVert(0)); v1 = mesh.getVert(face.getVert(1)); v2 = mesh.getVert(face.getVert(2)); faceNormal = (v1-v0)^(v2-v1); faceNormal = Normalize(faceNormal); } } int v; for (v=2; v>=0; v--) { int vIdx = face.getVert(v); Point3 vtx = offsMtx * mesh.getVert(vIdx); vdata *vd = (vdata *)malloc(sizeof(vdata)); vd->orig_idx = vIdx; vd->x = vtx.x; vd->y = vtx.z; vd->z = vtx.y; // Might not have UV coords if (geomUtil->include_uv) { int tvIdx; Point3 tvtx; Point3 stvtx; if (mainUVMeshMap!=NULL) tvtx=mainUVMeshMap->tv[tvface.t[v]]; else{ tvIdx = tvface.getTVert(v); tvtx = mesh.getTVert(tvIdx); } if (secondUVMeshMap!=NULL) stvtx=secondUVMeshMap->tv[tvFaceSecond.t[v]]; else{ tvIdx = tvface.getTVert(v); stvtx = mesh.getTVert(tvIdx); } vd->u = tvtx.x; vd->v = 1.0-tvtx.y; vd->su = stvtx.x; vd->sv = 1.0-stvtx.y; } if (geomUtil->include_normals) { Point3 normal; // if specific vertex-normals was found, we use it, if the subgeo is not set to explode if ((specificNormals!=NULL) && (!explode)){ normal = specificNormals->GetNormal(apiFaceIdx, v); } // else if a (not specific) vertex-normals was found (on the igame-mesh), we use it, if the subgeo is not set to explode else if ((igame_mesh!=NULL) && (!explode)){ igame_mesh->GetNormal(f->norm[v], normal, true); } // else: since we still want normals exported, we use the face-normal (using facenormal with threshold of 0 will explode the mesh else{ // i dont think this should really get executed anymore (since the igame-object allways should give access to the normals) normal=faceNormal; } // if the object is skinned, we get the global normals if (jpv>0){ if (normal) normal=offsMtx*normal; } vd->nx = normal.x; vd->ny = normal.z; vd->nz = normal.y; } // If there is skinning information, copy it from the weight // and joint index arrays returned by ExportSkin() above. vd->num_bindings = jpv; if (jpv > 0) { vd->weights = (awd_float64*)malloc(jpv*sizeof(awd_float64)); vd->joints = (awd_uint32*)malloc(jpv*sizeof(awd_uint32)); int memoffs = jpv*vIdx; memcpy(vd->weights, weights+memoffs, jpv*sizeof(awd_float64)); memcpy(vd->joints, joints+memoffs, jpv*sizeof(awd_uint32)); } vd->force_hard = false; // add the new vertex to the subgeo thisSubGeoGroup->append_vdata(vd); } } } } } AWDBlockList * returned_geoms = geomUtil->build_geom(awdGeom); AWDBlockIterator * it=NULL; AWDMeshInst * block; int maxCount=0; int geomCnt=0; for (geomCnt=0; geomCnt<returned_geoms->get_num_blocks();geomCnt++){ AWDTriGeom * thisAWDGeom=(AWDTriGeom *)returned_geoms->getByIndex(geomCnt); it = new AWDBlockIterator(thisAWDGeom->get_mesh_instance_list()); AWDSubGeom *sub; sub = thisAWDGeom->get_first_sub(); while (sub) { AWDBlockList *subGeoGroupMatList=sub->get_materials(); int thisIdx=0; it->reset(); while ((block = (AWDMeshInst*)it->next()) != NULL) { block->set_geom(thisAWDGeom); //if (!isInSkinPoseMode){ // block->set_transform(mtxData); //} AWDMaterial * thisMat=(AWDMaterial *)subGeoGroupMatList->getByIndex(thisIdx); if (thisMat==NULL){ int test=0; //ERROR - this should never happen } else{ AWDLightPicker * lightPicker=(AWDLightPicker *)block->get_lightPicker(); AWDBlock * thisAnimator=(AWDBlock *)block->get_animator(); if ((lightPicker!=NULL)||(thisAnimator!=NULL)){ thisMat=thisMat->get_unique_material(lightPicker, thisAnimator, NULL); if(lightPicker!=NULL){ if(opts->SetMultiPass()){ // multipass using the number of lights, that the lightpicker uses if (lightPicker->get_lights()->get_num_blocks()>4){ thisMat->set_multiPass(true); } else{ thisMat->set_multiPass(false); } } if ((lightPicker->get_lights()->get_num_blocks()>4)&&(!thisMat->get_multiPass())){ AWDMessageBlock * newWarning = new AWDMessageBlock(thisMat->get_name(), "AWDMaterial has more than 4 lights assigned, but is set to singlepass. this will cause problems on render."); awd->get_message_blocks()->append(newWarning); } if(opts->IncludeShadows()){ if(thisMat->get_shadowMethod()!=NULL){ bool shadowOK=lightPicker->check_shadowMethod((AWDShadowMethod *)(thisMat->get_shadowMethod())); if(!shadowOK){ AWDMessageBlock * newWarning = new AWDMessageBlock(thisMat->get_name(), "Could not find the ShadowMethod thats applied to the material on one of the lights that it assigned to the material."); awd->get_message_blocks()->append(newWarning); thisMat->set_shadowMethod(NULL); } } if(thisMat->get_shadowMethod()==NULL){ thisMat->set_shadowMethod(lightPicker->get_shadowMethod()); } } } } block->add_material((AWDMaterial*)thisMat); } thisIdx++; } sub = sub->next; } delete it; } delete returned_geoms; // If conversion created a new object, dispose it if (triObject != obj) triObject->DeleteMe(); delete geomUtil; } else{ } //free(quadDic); //free(isExportedDic); free(mtxData); if (weights!=NULL) free(weights); if (joints!=NULL) free(joints); } } return; }
int ExportQuake3Model(const TCHAR *filename, ExpInterface *ei, Interface *gi, int start_time, std::list<ExportNode> lTags, std::list<ExportNode> lMeshes) { FILE *file; int i, j, totalTags, totalMeshes, current_time = 0; long pos_current, totalTris = 0, totalVerts = 0; std::list<FrameRange>::iterator range_i; std::vector<Point3> lFrameBBoxMin; std::vector<Point3> lFrameBBoxMax; long pos_tagstart; long pos_tagend; long pos_filesize; long pos_framestart; int lazynamesfixed = 0; const Point3 x_axis(1, 0, 0); const Point3 z_axis(0, 0, 1); SceneEnumProc checkScene(ei->theScene, start_time, gi); totalTags = (int)lTags.size(); if (g_tag_for_pivot) totalTags++; totalMeshes = (int)lMeshes.size(); // open file file = _tfopen(filename, _T("wb")); if (!file) { ExportError("Cannot open file '%s'.", filename); return FALSE; } ExportDebug("%s:", filename); // sync pattern and version putChars("IDP3", 4, file); put32(15, file); putChars("Darkplaces MD3 Exporter", 64, file); put32(0, file); // flags // MD3 header ExportState("Writing MD3 header"); put32(g_total_frames, file); // how many frames put32(totalTags, file); // tagsnum put32(totalMeshes, file); // meshnum put32(1, file); // maxskinnum put32(108, file); // headersize pos_tagstart = ftell(file); put32(0, file); // tagstart pos_tagend = ftell(file); put32(256, file); // tagend pos_filesize = ftell(file); put32(512, file); // filesize ExportDebug(" %i frames, %i tags, %i meshes", g_total_frames, totalTags, totalMeshes); // frame info // bbox arrays get filled while exported mesh and written back then ExportState("Writing frame info"); pos_framestart = ftell(file); lFrameBBoxMin.resize(g_total_frames); lFrameBBoxMax.resize(g_total_frames); for (i = 0; i < g_total_frames; i++) { // init frame data lFrameBBoxMin[i].Set(0, 0, 0); lFrameBBoxMax[i].Set(0, 0, 0); // put data putFloat(-1.0f, file); // bbox min vector putFloat(-1.0f, file); putFloat(-1.0f, file); putFloat( 1.0f, file); // bbox max vector putFloat(1.0f, file); putFloat(1.0f, file); putFloat(0.0f, file); // local origin (usually 0 0 0) putFloat(0.0f, file); putFloat(0.0f, file); putFloat(1.0f, file); // radius of bounding sphere putChars("", 16, file); } // tags pos_current = ftell(file); fseek(file, pos_tagstart, SEEK_SET); put32(pos_current, file); fseek(file, pos_current, SEEK_SET); // for each frame range cycle all frames and write out each tag long pos_tags = pos_current; if (totalTags) { long current_frame = 0; ExportState("Writing %i tags", totalTags); for (range_i = g_frame_ranges.begin(); range_i != g_frame_ranges.end(); range_i++) { for (i = (*range_i).first; i <= (int)(*range_i).last; i++, current_frame++) { SceneEnumProc current_scene(ei->theScene, i * g_ticks_per_frame, gi); current_time = current_scene.time; // write out tags if (lTags.size()) { for (std::list<ExportNode>::iterator tag_i = lTags.begin(); tag_i != lTags.end(); tag_i++) { INode *node = current_scene[tag_i->i]->node; Matrix3 tm = node->GetObjTMAfterWSM(current_time); ExportState("Writing '%s' frame %i of %i", tag_i->name, i, g_total_frames); // tagname putChars(tag_i->name, 64, file); // origin, rotation matrix Point3 row = tm.GetRow(3); putFloat(row.x, file); putFloat(row.y, file); putFloat(row.z, file); row = tm.GetRow(0); putFloat(row.x, file); putFloat(row.y, file); putFloat(row.z, file); row = tm.GetRow(1); putFloat(row.x, file); putFloat(row.y, file); putFloat(row.z, file); row = tm.GetRow(2); putFloat(row.x, file); putFloat(row.y, file); putFloat(row.z, file); } } // write the center of mass tag_pivot which is avg of all objects's pivots if (g_tag_for_pivot) { ExportState("Writing 'tag_pivot' frame %i of %i", i, g_total_frames); // write the null data as tag_pivot need to be written after actual geometry // (it needs information on frame bound boxes to get proper blendings) putChars("tag_pivot", 64, file); putFloat(0, file); putFloat(0, file); putFloat(0, file); putFloat(1, file); putFloat(0, file); putFloat(0, file); putFloat(0, file); putFloat(1, file); putFloat(0, file); putFloat(0, file); putFloat(0, file); putFloat(1, file); } } } } // write the tag object offsets pos_current = ftell(file); fseek(file, pos_tagend, SEEK_SET); put32(pos_current, file); fseek(file, pos_current, SEEK_SET); // allocate the structs used to calculate tag_pivot std::vector<Point3> tag_pivot_origin; std::vector<double> tag_pivot_volume; if (g_tag_for_pivot) { tag_pivot_origin.resize(g_total_frames); tag_pivot_volume.resize(g_total_frames); } // mesh objects // for each mesh object write uv and frames SceneEnumProc scratch(ei->theScene, start_time, gi); ExportState("Writing %i meshes", (int)lMeshes.size()); for (std::list<ExportNode>::iterator mesh_i = lMeshes.begin(); mesh_i != lMeshes.end(); mesh_i++) { bool needsDel; ExportState("Start mesh #%i", mesh_i); INode *node = checkScene[mesh_i->i]->node; Matrix3 tm = node->GetObjTMAfterWSM(start_time); TriObject *tri = GetTriObjectFromNode(node, start_time, needsDel); if (!tri) continue; // get mesh, compute normals Mesh &mesh = tri->GetMesh(); MeshNormalSpec *meshNormalSpec = mesh.GetSpecifiedNormals(); if (meshNormalSpec) { if (!meshNormalSpec->GetNumFaces()) meshNormalSpec = NULL; else { meshNormalSpec->SetParent(&mesh); meshNormalSpec->CheckNormals(); } } mesh.checkNormals(TRUE); // fix lazy object names ExportState("Attempt to fix mesh name '%s'", mesh_i->name); char meshname[64]; size_t meshnamelen = min(63, strlen(mesh_i->name)); memset(meshname, 0, 64); strncpy(meshname, mesh_i->name, meshnamelen); meshname[meshnamelen] = 0; if (!strncmp("Box", meshname, 3) || !strncmp("Sphere", meshname, 6) || !strncmp("Cylinder", meshname, 8) || !strncmp("Torus", meshname, 5) || !strncmp("Cone", meshname, 4) || !strncmp("GeoSphere", meshname, 9) || !strncmp("Tube", meshname, 4) || !strncmp("Pyramid", meshname, 7) || !strncmp("Plane", meshname, 5) || !strncmp("Teapot", meshname, 6) || !strncmp("Object", meshname, 6)) { name_conflict: lazynamesfixed++; if (lazynamesfixed == 1) strcpy(meshname, "base"); else sprintf(meshname, "base%i", lazynamesfixed); // check if it's not used by another mesh for (std::list<ExportNode>::iterator m_i = lMeshes.begin(); m_i != lMeshes.end(); m_i++) if (!strncmp(m_i->name, meshname, strlen(meshname))) goto name_conflict; // approve name ExportWarning("Lazy object name '%s' (mesh renamed to '%s').", node->GetName(), meshname); } // special mesh check bool shadow_or_collision = false; if (g_mesh_special) if (!strncmp("collision", meshname, 9) || !strncmp("shadow", meshname, 6)) shadow_or_collision = true; // get material const char *shadername = NULL; Texmap *tex = 0; Mtl *mtl = 0; if (!shadow_or_collision) { mtl = node->GetMtl(); if (mtl) { // check for multi-material if (mtl->IsMultiMtl()) { // check if it's truly multi material // we do support multi-material with only one texture (some importers set it) bool multi_material = false; MtlID matId = mesh.faces[0].getMatID(); for (i = 1; i < mesh.getNumFaces(); i++) if (mesh.faces[i].getMatID() != matId) multi_material = true; if (multi_material) if (g_mesh_multimaterials == MULTIMATERIALS_NONE) ExportWarning("Object '%s' is multimaterial and using multiple materials on its faces, that case is not yet supported (truncating to first submaterial).", node->GetName()); // switch to submaterial mtl = mtl->GetSubMtl(matId); } // get shader from material if supplied char *materialname = GetChar(mtl->GetName()); if (g_mesh_materialasshader && (strstr(materialname, "/") != NULL || strstr(materialname, "\\") != NULL)) shadername = GetChar(mtl->GetName()); else { // get texture tex = mtl->GetSubTexmap(ID_DI); if (tex) { if (tex->ClassID() == Class_ID(BMTEX_CLASS_ID, 0x00)) { shadername = GetChar(((BitmapTex *)tex)->GetMapName()); if (shadername == NULL || !shadername[0]) ExportWarning("Object '%s' material '%s' has no bitmap.", tex->GetName(), node->GetName()); } else { tex = NULL; ExportWarning("Object '%s' has material with wrong texture type (only Bitmap are supported).", node->GetName()); } } else ExportWarning("Object '%s' has material but no texture.", node->GetName()); } } else ExportWarning("Object '%s' has no material.", node->GetName()); } long pos_meshstart = ftell(file); // surface object ExportState("Writing mesh '%s' header", meshname); putChars("IDP3", 4, file); putChars(meshname, 64, file); put32(0, file); // flags put32(g_total_frames, file); // framecount put32(1, file); // skincount long pos_vertexnum = ftell(file); put32(0, file); // vertexcount put32(mesh.getNumFaces(), file); // trianglecount long pos_trianglestart = ftell(file); put32(0, file); // start triangles put32(108, file); // header size long pos_texvecstart = ftell(file); put32(0, file); // texvecstart long pos_vertexstart = ftell(file); put32(16, file); // vertexstart long pos_meshsize = ftell(file); put32(32, file); // meshsize // write out a single 'skin' ExportState("Writing mesh %s texture", meshname); if (shadow_or_collision) putChars(meshname, 64, file); else if (shadername) putMaterial(shadername, mtl, tex, file); else putChars("noshader", 64, file); put32(0, file); // flags // build geometry ExportState("Building vertexes/triangles"); std::vector<ExportVertex>vVertexes; std::vector<ExportTriangle>vTriangles; vVertexes.resize(mesh.getNumVerts()); int vExtraVerts = mesh.getNumVerts(); for (i = 0; i < mesh.getNumVerts(); i++) { vVertexes[i].vert = i; vVertexes[i].normalfilled = false; // todo: check for coincident verts } int vNumExtraVerts = 0; // check normals if (!mesh.normalsBuilt && !shadow_or_collision) ExportWarning("Object '%s' does not have normals contructed.", node->GetName()); // get info for triangles const float normal_epsilon = 0.01f; vTriangles.resize(mesh.getNumFaces()); for (i = 0; i < mesh.getNumFaces(); i++) { DWORD smGroup = mesh.faces[i].getSmGroup(); ExportState("Mesh %s: checking normals for face %i of %i", meshname, i, mesh.getNumFaces()); for (j = 0; j < 3; j++) { int vert = mesh.faces[i].getVert(j); vTriangles[i].e[j] = vert; // find a right normal for this vertex and save its 'address' int vni; Point3 vn; if (!mesh.normalsBuilt || shadow_or_collision) { vn.Set(0, 0, 0); vni = 0; } else { int numNormals; RVertex *rv = mesh.getRVertPtr(vert); if (meshNormalSpec) { ExportState("face %i vert %i have normal specified", i, j); // mesh have explicit normals (i.e. Edit Normals modifier) vn = meshNormalSpec->GetNormal(i, j); vni = meshNormalSpec->GetNormalIndex(i, j); } else if (rv && rv->rFlags & SPECIFIED_NORMAL) { ExportState("face %i vert %i have SPECIFIED_NORMAL flag", i, j); // SPECIFIED_NORMAL flag vn = rv->rn.getNormal(); vni = 0; } else if (rv && (numNormals = rv->rFlags & NORCT_MASK) && smGroup) { // If there is only one vertex is found in the rn member. if (numNormals == 1) { ExportState("face %i vert %i have solid smooth group", i, j); vn = rv->rn.getNormal(); vni = 0; } else { ExportState("face %i vert %i have mixed smoothing groups", i, j); // 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 k = 0; k < numNormals; k++) { if (rv->ern[k].getSmGroup() & smGroup) { vn = rv->ern[k].getNormal(); vni = 1 + k; } } } } else { ExportState("face %i vert %i flat shaded", i, j); // Get the normal from the Face if no smoothing groups are there vn = mesh.getFaceNormal(i); vni = 0 - (i + 1); } } // subdivide to get all normals right if (!vVertexes[vert].normalfilled) { vVertexes[vert].normal = vn; vVertexes[vert].normalindex = vni; vVertexes[vert].normalfilled = true; } else if ((vVertexes[vert].normal - vn).Length() >= normal_epsilon) { // current vertex not matching normal - it was already filled by different smoothing group // find a vert in extra verts in case it was already created bool vert_found = false; for (int ev = vExtraVerts; ev < (int)vVertexes.size(); ev++) { if (vVertexes[ev].vert == vert && (vVertexes[ev].normal - vn).Length() < normal_epsilon) { vert_found = true; vTriangles[i].e[j] = ev; break; } } // we havent found a vertex, create new if (!vert_found) { ExportVertex NewVert; NewVert.vert = vVertexes[vert].vert; NewVert.normal = vn; NewVert.normalindex = vni; NewVert.normalfilled = true; vTriangles[i].e[j] = (int)vVertexes.size(); vVertexes.push_back(NewVert); vNumExtraVerts++; } } } } int vNumExtraVertsForSmoothGroups = vNumExtraVerts; // generate UV map // VorteX: use direct maps reading since getNumTVerts()/getTVert is deprecated // max sets two default mesh maps: 0 - vertex color, 1 : UVW, 2 & up are custom ones ExportState("Building UV map"); std::vector<ExportUV>vUVMap; vUVMap.resize(vVertexes.size()); int meshMap = 1; if (!mesh.mapSupport(meshMap) || !mesh.getNumMapVerts(meshMap) || shadow_or_collision) { for (i = 0; i < mesh.getNumVerts(); i++) { vUVMap[i].u = 0.5; vUVMap[i].v = 0.5; } if (!shadow_or_collision) ExportWarning("No UV mapping was found on object '%s'.", node->GetName()); } else { UVVert *meshUV = mesh.mapVerts(meshMap); for (i = 0; i < (int)vTriangles.size(); i++) { ExportState("Mesh %s: converting tvert for face %i of %i", meshname, i, (int)vTriangles.size()); // for 3 face vertexes for (j = 0; j < 3; j++) { int vert = vTriangles[i].e[j]; int tv = mesh.tvFace[i].t[j]; UVVert &UV = meshUV[tv]; if (!vUVMap[vert].filled) { // fill uvMap vertex vUVMap[vert].u = UV.x; vUVMap[vert].v = UV.y; vUVMap[vert].filled = true; vUVMap[vert].tvert = tv; } else if (tv != vUVMap[vert].tvert) { // uvMap slot for this vertex has been filled // we should arrange triangle to other vertex, which not filled and having same shading and uv // check if any of the extra vertices can fit bool vert_found = false; for (int ev = vExtraVerts; ev < (int)vVertexes.size(); ev++) { if (vVertexes[ev].vert == vert && vUVMap[vert].u == UV.x &&vUVMap[vert].v == UV.y && (vVertexes[ev].normal - vVertexes[vert].normal).Length() < normal_epsilon) { vert_found = true; vTriangles[i].e[j] = vVertexes[ev].vert; break; } } if (!vert_found) { // create new vert ExportVertex NewVert; NewVert.vert = vVertexes[vert].vert; NewVert.normal = vVertexes[vert].normal; NewVert.normalindex = vVertexes[vert].normalindex; NewVert.normalfilled = vVertexes[vert].normalfilled; vTriangles[i].e[j] = (int)vVertexes.size(); vVertexes.push_back(NewVert); vNumExtraVerts++; // create new TVert ExportUV newUV; newUV.filled = true; newUV.u = UV.x; newUV.v = UV.y; newUV.tvert = tv; vUVMap.push_back(newUV); } } } } } int vNumExtraVertsForUV = (vNumExtraVerts - vNumExtraVertsForSmoothGroups); // print some debug stats ExportDebug(" mesh %s: %i vertexes +%i %s +%i UV, %i triangles", meshname, ((int)vVertexes.size() - vNumExtraVerts), vNumExtraVertsForSmoothGroups, meshNormalSpec ? "EditNormals" : "SmoothGroups", vNumExtraVertsForUV, (int)vTriangles.size()); // fill in triangle start pos_current = ftell(file); fseek(file, pos_trianglestart, SEEK_SET); put32(pos_current - pos_meshstart, file); fseek(file, pos_current, SEEK_SET); // detect if object have negative scale (mirrored) // in this canse we should rearrange triangles counterclockwise // so stuff will not be inverted ExportState("Mesh %s: writing %i triangles", meshname, (int)vTriangles.size()); if (DotProd(CrossProd(tm.GetRow(0), tm.GetRow(1)), tm.GetRow(2)) < 0.0) { ExportWarning("Object '%s' is mirrored (having negative scale on it's transformation)", node->GetName()); for (i = 0; i < (int)vTriangles.size(); i++) { put32(vTriangles[i].b, file); // vertex index put32(vTriangles[i].c, file); // for 3 vertices put32(vTriangles[i].a, file); // of triangle } } else { for (i = 0; i < (int)vTriangles.size(); i++) { put32(vTriangles[i].a, file); // vertex index put32(vTriangles[i].c, file); // for 3 vertices put32(vTriangles[i].b, file); // of triangle } } // fill in texvecstart // write out UV mapping coords. ExportState("Mesh %s: writing %i UV vertexes", meshname, (int)vUVMap.size()); pos_current = ftell(file); fseek(file, pos_texvecstart, SEEK_SET); put32(pos_current - pos_meshstart, file); fseek(file, pos_current, SEEK_SET); for (i = 0; i < (int)vUVMap.size(); i++) { putFloat(vUVMap[i].u, file); // texture coord u,v putFloat(1.0f - vUVMap[i].v, file); // for vertex } vUVMap.clear(); // fill in vertexstart pos_current = ftell(file); fseek(file, pos_vertexstart, SEEK_SET); put32(pos_current - pos_meshstart, file); fseek(file, pos_current, SEEK_SET); // fill in vertexnum pos_current = ftell(file); fseek(file, pos_vertexnum, SEEK_SET); put32((int)vVertexes.size(), file); fseek(file, pos_current, SEEK_SET); // write out for each frame the position of each vertex long current_frame = 0; ExportState("Mesh %s: writing %i frames", meshname, g_total_frames); for (range_i = g_frame_ranges.begin(); range_i != g_frame_ranges.end(); range_i++) { for (i = (*range_i).first; i <= (int)(*range_i).last; i++, current_frame++) { bool _needsDel; // get triobject for current frame SceneEnumProc current_scene(ei->theScene, i * g_ticks_per_frame, gi); current_time = current_scene.time; INode *_node = current_scene[mesh_i->i]->node; TriObject *_tri = GetTriObjectFromNode(_node, current_time, _needsDel); if (!_tri) continue; // get mesh, compute normals Mesh &_mesh = _tri->GetMesh(); MeshNormalSpec *_meshNormalSpec = _mesh.GetSpecifiedNormals(); if (_meshNormalSpec) { if (!_meshNormalSpec->GetNumFaces()) _meshNormalSpec = NULL; else { _meshNormalSpec->SetParent(&_mesh); _meshNormalSpec->CheckNormals(); } } _mesh.checkNormals(TRUE); // get transformations for current frame Matrix3 _tm = _node->GetObjTMAfterWSM(current_time); ExportState("Mesh %s: writing frame %i of %i", meshname, current_frame, g_total_frames); Point3 BoxMin(0, 0, 0); Point3 BoxMax(0, 0, 0); for (j = 0; j < (int)vVertexes.size(); j++) // number of vertices { ExportState("Mesh %s: transform vertex %i of %i", meshname, j, (int)vVertexes.size()); int vert = vVertexes[j].vert; Point3 &v = _tm.PointTransform(_mesh.getVert(vert)); // populate bbox data if (!shadow_or_collision) { BoxMin.x = min(BoxMin.x, v.x); BoxMin.y = min(BoxMin.y, v.y); BoxMin.z = min(BoxMin.z, v.z); BoxMax.x = max(BoxMax.x, v.x); BoxMax.y = max(BoxMax.y, v.y); BoxMax.z = max(BoxMax.z, v.z); } // write vertex double f; f = v.x * 64.0f; if (f < -32768.0) f = -32768.0; if (f > 32767.0) f = 32767.0; put16((short)f, file); f = v.y * 64.0f; if (f < -32768.0) f = -32768.0; if (f > 32767.0) f = 32767.0; put16((short)f, file); f = v.z * 64.0f; if (f < -32768.0) f = -32768.0; if (f > 32767.0) f = 32767.0; put16((short)f, file); // get normal ExportState("Mesh %s: transform vertex normal %i of %i", meshname, j, (int)vVertexes.size()); Point3 n; if (_meshNormalSpec) // mesh have explicit normals (i.e. Edit Normals modifier) n = _meshNormalSpec->Normal(vVertexes[j].normalindex); else if (!vVertexes[j].normalfilled || !_mesh.normalsBuilt) n = _mesh.getNormal(vert); else { RVertex *rv = _mesh.getRVertPtr(vert); if (vVertexes[j].normalindex < 0) n = _mesh.getFaceNormal((0 - vVertexes[j].normalindex) - 1); else if (vVertexes[j].normalindex == 0) n = rv->rn.getNormal(); else n = rv->ern[vVertexes[j].normalindex - 1].getNormal(); } // transform normal Point3 &nt = _tm.VectorTransform(n).Normalize(); // encode a normal vector into a 16-bit latitude-longitude value double lng = acos(nt.z) * 255 / (2 * pi); double lat = atan2(nt.y, nt.x) * 255 / (2 * pi); put16((((int)lat & 0xFF) << 8) | ((int)lng & 0xFF), file); } // blend the pivot positions for tag_pivot using mesh's volumes for blending power if (g_tag_for_pivot && !shadow_or_collision) { ExportState("Mesh %s: writing tag_pivot", meshname); Point3 Size = BoxMax - BoxMin; double BoxVolume = pow(Size.x * Size.y * Size.z, 0.333f); // blend matrices float blend = (float)(BoxVolume / (BoxVolume + tag_pivot_volume[current_frame])); float iblend = 1 - blend; tag_pivot_volume[current_frame] = tag_pivot_volume[current_frame] + BoxVolume; Point3 row = _tm.GetRow(3) - _node->GetObjOffsetPos(); tag_pivot_origin[current_frame].x = tag_pivot_origin[current_frame].x * iblend + row.x * blend; tag_pivot_origin[current_frame].y = tag_pivot_origin[current_frame].y * iblend + row.y * blend; tag_pivot_origin[current_frame].z = tag_pivot_origin[current_frame].z * iblend + row.z * blend; } // populate bbox data for frames lFrameBBoxMin[current_frame].x = min(lFrameBBoxMin[current_frame].x, BoxMin.x); lFrameBBoxMin[current_frame].y = min(lFrameBBoxMin[current_frame].y, BoxMin.y); lFrameBBoxMin[current_frame].z = min(lFrameBBoxMin[current_frame].z, BoxMin.z); lFrameBBoxMax[current_frame].x = max(lFrameBBoxMax[current_frame].x, BoxMax.x); lFrameBBoxMax[current_frame].y = max(lFrameBBoxMax[current_frame].y, BoxMax.y); lFrameBBoxMax[current_frame].z = max(lFrameBBoxMax[current_frame].z, BoxMax.z); // delete the working object, if necessary. if (_needsDel) delete _tri; } } // delete if necessary if (needsDel) delete tri; // fill in meshsize pos_current = ftell(file); fseek(file, pos_meshsize, SEEK_SET); put32(pos_current - pos_meshstart, file); fseek(file, pos_current, SEEK_SET); // reset back to first frame SceneEnumProc scratch(ei->theScene, start_time, gi); totalTris += (long)vTriangles.size(); totalVerts += (long)vVertexes.size(); vTriangles.clear(); vVertexes.clear(); } // write tag_pivot ExportState("Writing tag_pivot positions"); if (g_tag_for_pivot) { pos_current = ftell(file); long current_frame = 0; for (range_i = g_frame_ranges.begin(); range_i != g_frame_ranges.end(); range_i++) { for (i = (*range_i).first; i <= (int)(*range_i).last; i++, current_frame++) { fseek(file, pos_tags + totalTags*112*current_frame + (int)lTags.size()*112 + 64, SEEK_SET); // origin putFloat(tag_pivot_origin[current_frame].x, file); putFloat(tag_pivot_origin[current_frame].y, file); putFloat(tag_pivot_origin[current_frame].z, file); } } fseek(file, pos_current, SEEK_SET); } tag_pivot_volume.clear(); tag_pivot_origin.clear(); // write frame data ExportState("Writing culling info"); long current_frame = 0; pos_current = ftell(file); for (range_i = g_frame_ranges.begin(); range_i != g_frame_ranges.end(); range_i++) { for (i = (*range_i).first; i <= (int)(*range_i).last; i++, current_frame++) { fseek(file, pos_framestart + current_frame*56, SEEK_SET); putFloat(lFrameBBoxMin[current_frame].x, file); // bbox min vector putFloat(lFrameBBoxMin[current_frame].y, file); putFloat(lFrameBBoxMin[current_frame].z, file); putFloat(lFrameBBoxMax[current_frame].x, file); // bbox max vector putFloat(lFrameBBoxMax[current_frame].y, file); putFloat(lFrameBBoxMax[current_frame].z, file); putFloat(0, file); // local origin (usually 0 0 0) putFloat(0, file); putFloat(0, file); putFloat(max(lFrameBBoxMin[current_frame].Length(), lFrameBBoxMax[current_frame].Length()) , file); // radius of bounding sphere } } fseek(file, pos_current, SEEK_SET); lFrameBBoxMin.clear(); lFrameBBoxMax.clear(); // fill in filesize pos_current = ftell(file); fseek(file, pos_filesize, SEEK_SET); put32(pos_current, file); fseek(file, pos_current, SEEK_SET); fclose(file); ExportDebug(" total: %i vertexes, %i triangles", totalVerts, totalTris); return TRUE; }
void WMOGroupImpl::buildMaxData() { // Group Header Node INode* groupHeadNode = createGroupHeaderNode(); groupHeadNode->SetGroupHead(TRUE); groupHeadNode->SetGroupMember(FALSE); // Geoset // 一个Render Batch构造一个Node, 并且加入到组中 for (int i = 0; i < m_batchCount; ++i) { WMORenderBatch& renderBatch = m_batchData[i]; m_wmoImporter->m_logStream << "Model Geoset " << i << " Vertex Info: " << renderBatch.vertexStart << " -- " << renderBatch.vertexEnd << endl; m_wmoImporter->m_logStream << "Model Geoset " << i << " Index Info: " << renderBatch.indexStart << " -- " << renderBatch.indexCount << endl; // Triangle Mesh Object // 基本的三角形模型对象 TriObject* triObject = CreateNewTriObject(); // 创建Node, 并且设为Group Header Node的子节点 ImpNode* tmpImpNode = m_wmoImporter->m_impInterface->CreateNode(); tmpImpNode->Reference(triObject); m_wmoImporter->m_impInterface->AddNodeToScene(tmpImpNode); INode* realINode = tmpImpNode->GetINode(); realINode->SetGroupHead(FALSE); realINode->SetGroupMember(TRUE); groupHeadNode->AttachChild(realINode); m_geosetNodeList.push_back(realINode); TCHAR nodeName[256]; sprintf(nodeName, "%s_part_%03d", m_groupName.c_str(), i); realINode->SetName(nodeName); // mesh unsigned short vertexCount = renderBatch.vertexEnd - renderBatch.vertexStart + 1; unsigned int trigangleCount = renderBatch.indexCount / 3; Mesh& mesh = triObject->GetMesh(); mesh.setNumVerts(vertexCount); mesh.setNumTVerts(vertexCount, TRUE); mesh.setNumFaces(trigangleCount); mesh.setNumTVFaces(trigangleCount); // 顶点坐标和UV for (int i = 0; i < vertexCount; ++i) { mesh.verts[i] = *(Point3*)(&(m_vertexData[renderBatch.vertexStart + i])); // UV坐标反转 mesh.tVerts[i].x = m_textureCoords[renderBatch.vertexStart + i].x; mesh.tVerts[i].y = 1.0f - m_textureCoords[renderBatch.vertexStart + i].y; } // 三角形 for (unsigned int i = 0; i < trigangleCount; ++i) { Face& face = mesh.faces[i]; int index1 = m_indexData[renderBatch.indexStart + i*3] - renderBatch.vertexStart; int index2 = m_indexData[renderBatch.indexStart + i*3+1] - renderBatch.vertexStart; int index3 = m_indexData[renderBatch.indexStart + i*3+2] - renderBatch.vertexStart; face.setVerts(index1, index2, index3); face.Show(); face.setEdgeVisFlags(EDGE_VIS, EDGE_VIS, EDGE_VIS); TVFace& tface = mesh.tvFace[i]; tface.setTVerts(index1, index2, index3); } // 法线 mesh.SpecifyNormals(); MeshNormalSpec *specNorms = mesh.GetSpecifiedNormals(); if (specNorms) { specNorms->ClearAndFree(); specNorms->SetNumFaces(trigangleCount); specNorms->SetNumNormals(vertexCount); Point3* norms = specNorms->GetNormalArray(); for (int i = 0; i < vertexCount; ++i) { norms[i] = *(Point3*)(&m_normalData[renderBatch.vertexStart + i]); } MeshNormalFace* pFaces = specNorms->GetFaceArray(); for (unsigned int i = 0; i < trigangleCount; ++i) { int index1 = m_indexData[renderBatch.indexStart + i*3] - renderBatch.vertexStart; int index2 = m_indexData[renderBatch.indexStart + i*3+1] - renderBatch.vertexStart; int index3 = m_indexData[renderBatch.indexStart + i*3+2] - renderBatch.vertexStart; pFaces[i].SpecifyNormalID(0, index1); pFaces[i].SpecifyNormalID(1, index2); pFaces[i].SpecifyNormalID(2, index3); } specNorms->SetAllExplicit(true); specNorms->CheckNormals(); } // 删除重复的和无效的面 mesh.RemoveDegenerateFaces(); mesh.RemoveIllegalFaces(); realINode->SetMtl(m_wmoImporter->m_materialList[renderBatch.texture]); //realINode->BackCull(FALSE); // 取消背面裁减 不是所有的Node都要取消背面裁减 realINode->EvalWorldState(0); } }
void SymmetryMod::ModifyTriObject (TimeValue t, ModContext &mc, TriObject *tobj, INode *inode) { Mesh &mesh = tobj->GetMesh(); Interval iv = FOREVER; int axis, slice, weld, flip; float threshold; mp_pblock->GetValue (kSymAxis, t, axis, iv); mp_pblock->GetValue (kSymFlip, t, flip, iv); mp_pblock->GetValue (kSymSlice, t, slice, iv); mp_pblock->GetValue (kSymWeld, t, weld, iv); mp_pblock->GetValue (kSymThreshold, t, threshold, iv); if (threshold<0) threshold=0; // Get transform from mirror controller: Matrix3 tm = CompMatrix (t, NULL, &mc, &iv); Matrix3 itm = Inverse (tm); // Get DotProd(N,x)=offset plane definition from transform Point3 Axis(0,0,0); Axis[axis] = flip ? -1.0f : 1.0f; Point3 origin = tm.GetTrans(); Point3 N = Normalize(tm*Axis - origin); float offset = DotProd (N, origin); // Slice operation does not handle NormalSpecs, but it handles mapping channels. // move our mesh normal data to a map channel MeshNormalSpec *pNormals = mesh.GetSpecifiedNormals (); int normalMapChannel = INVALID_NORMALMAPCHANNEL; if (pNormals && pNormals->GetNumFaces()) { pNormals->SetParent(&mesh); //find an empty map channel for (int mp = 0; mp < mesh.getNumMaps(); mp++) { if (!mesh.mapSupport(mp)) { normalMapChannel = mp; mesh.setMapSupport(normalMapChannel,TRUE); MeshMap& map = mesh.Map(normalMapChannel); for (int i = 0; i < map.fnum; i++) { for (int j = 0; j < 3; j++) { unsigned int newID = pNormals->Face(i).GetNormalID(j); map.tf[i].t[j] = newID; } } map.setNumVerts(pNormals->GetNumNormals()); for (int i = 0; i < map.vnum; i++) { map.tv[i] = pNormals->Normal(i); } // make sure nothing is done with MeshNormalSpec (until data is copied back) pNormals->Clear(); break; } } } // Slice off everything below the plane. if (slice) SliceTriObject (mesh, N, offset); MirrorTriObject (mesh, axis, tm, itm,normalMapChannel); if (weld) WeldTriObject (mesh, N, offset, threshold); //now move the normals back if (pNormals && normalMapChannel != -1) { MeshMap& map = mesh.Map(normalMapChannel); pNormals->SetNumFaces(map.fnum); pNormals->SetNumNormals(map.vnum); pNormals->SetAllExplicit(true); BitArray temp; temp.SetSize(map.vnum); temp.SetAll(); pNormals->SpecifyNormals(TRUE,&temp); for (int i = 0; i < map.vnum; i++) { pNormals->GetNormalArray()[i] = map.tv[i]; pNormals->SetNormalExplicit(i,true); } for (int i = 0; i < map.fnum; i++) { for (int j = 0; j < 3; j++) { pNormals->SetNormalIndex(i,j,map.tf[i].t[j]); MeshNormalFace& face = pNormals->Face(i); face.SpecifyAll(true); } } pNormals->SetFlag(MESH_NORMAL_MODIFIER_SUPPORT); for (int i = 0; i < pNormals->GetNumFaces(); i++) { for (int j = 0; j < 3; j++) { int id = pNormals->GetNormalIndex(i,j); } } pNormals->CheckNormals(); pNormals->SetParent(NULL); // Free the map channel mesh.setMapSupport(normalMapChannel,FALSE); } tobj->UpdateValidity (GEOM_CHAN_NUM, iv); tobj->UpdateValidity (TOPO_CHAN_NUM, iv); tobj->UpdateValidity (VERT_COLOR_CHAN_NUM, iv); tobj->UpdateValidity (TEXMAP_CHAN_NUM, iv); tobj->UpdateValidity (SELECT_CHAN_NUM, iv); }
// 1. 加载模型顶点数据 void M2Importer::importGeomObject() { // Group Header Node INode* groupHeadNode = createGroupHeaderNode(); groupHeadNode->SetGroupHead(TRUE); groupHeadNode->SetGroupMember(FALSE); if (m_modelHeader->nameLength > 1) { TCHAR* modelName = (TCHAR*)(m_m2FileData + m_modelHeader->nameOfs); groupHeadNode->SetName(modelName); m_logStream << "ModelName: " << modelName << endl; } else groupHeadNode->SetName("GeomGroup"); // Geoset // 一个Geoset构造一个Node, 并且加入到组中 unsigned short* verDataIndex = (unsigned short*)(m_m2FileData + m_modelView->ofsIndex); unsigned short* triData = (unsigned short*)(m_m2FileData + m_modelView->ofsTris); m_geosetNodeList.reserve(m_modelView->nSub); m_materialList.reserve(m_modelView->nSub); for (unsigned int i = 0; i < m_modelView->nSub; ++i) m_materialList.push_back(0); for (unsigned int i = 0; i < m_modelView->nSub; ++i) { ModelGeoset& geosetData = m_modelGeoset[i]; // Triangle Mesh Object // 基本的三角形模型对象 TriObject* triObject = CreateNewTriObject(); // 创建Node, 并且设为Group Header Node的子节点 ImpNode* tmpImpNode = m_impInterface->CreateNode(); tmpImpNode->Reference(triObject); //tmpImpNode->SetPivot(*(Point3*)&(geosetData.v)); m_impInterface->AddNodeToScene(tmpImpNode); INode* realINode = tmpImpNode->GetINode(); realINode->SetGroupHead(FALSE); realINode->SetGroupMember(TRUE); groupHeadNode->AttachChild(realINode); m_geosetNodeList.push_back(realINode); TCHAR nodeName[256]; sprintf(nodeName, "GeosetPart_%d", i); realINode->SetName(nodeName); // mesh Mesh& mesh = triObject->GetMesh(); mesh.setNumVerts(geosetData.vcount); mesh.setNumTVerts(geosetData.vcount, TRUE); unsigned int triangeCount = geosetData.icount / 3; mesh.setNumFaces(triangeCount); mesh.setNumTVFaces(triangeCount); m_logStream << "Model Geoset " << i << " Vertex Count: " << geosetData.vcount << endl; m_logStream << "Model Geoset " << i << " Index Count: " << triangeCount << endl; // 顶点坐标和UV for (unsigned int i = 0; i < geosetData.vcount; ++i) { ModelVertex& vertexData = m_globalVertices[ verDataIndex[geosetData.vstart + i] ]; mesh.verts[i] = *(Point3*)(&vertexData.pos); // UV坐标反转 mesh.tVerts[i].x = vertexData.texcoords.x; mesh.tVerts[i].y = 1.0f - vertexData.texcoords.y; } // 三角形 for (unsigned int i = 0; i < triangeCount; ++i) { Face& face = mesh.faces[i]; face.setVerts(triData[geosetData.istart + i*3] - m_indexCount, triData[geosetData.istart + i*3+1] - m_indexCount, triData[geosetData.istart + i*3+2] - m_indexCount); face.Show(); face.setEdgeVisFlags(EDGE_VIS, EDGE_VIS, EDGE_VIS); TVFace& tface = mesh.tvFace[i]; tface.setTVerts(triData[geosetData.istart + i*3] - m_indexCount, triData[geosetData.istart + i*3+1] - m_indexCount, triData[geosetData.istart + i*3+2] - m_indexCount); } // 法线 mesh.SpecifyNormals(); MeshNormalSpec *specNorms = mesh.GetSpecifiedNormals(); if (specNorms) { specNorms->ClearAndFree(); specNorms->SetNumFaces(triangeCount); specNorms->SetNumNormals(geosetData.vcount); Point3* norms = specNorms->GetNormalArray(); for (unsigned int i = 0; i < geosetData.vcount; ++i) { ModelVertex& vertexData = m_globalVertices[ verDataIndex[geosetData.vstart + i] ]; norms[i] = *(Point3*)(&vertexData.normal); } MeshNormalFace* pFaces = specNorms->GetFaceArray(); for (unsigned int i = 0; i < triangeCount; ++i) { pFaces[i].SpecifyNormalID(0, triData[geosetData.istart + i*3] - m_indexCount); pFaces[i].SpecifyNormalID(1, triData[geosetData.istart + i*3+1] - m_indexCount); pFaces[i].SpecifyNormalID(2, triData[geosetData.istart + i*3+2] - m_indexCount); } specNorms->SetAllExplicit(true); specNorms->CheckNormals(); } // 删除重复的和无效的面 mesh.RemoveDegenerateFaces(); mesh.RemoveIllegalFaces(); //realINode->BackCull(FALSE); // 取消背面裁减 双面绘制与取消背面裁减一起设置 realINode->EvalWorldState(0); // 索引值修正 m_indexCount += geosetData.vcount; } // 加载材质 unsigned short* texLookupData = (unsigned short*)(m_m2FileData + m_modelHeader->ofsTexLookup); ModelTextureDef* texUnitDefData = (ModelTextureDef*)(m_m2FileData + m_modelHeader->ofsTextures); ModelTexUnit* texUnitData = (ModelTexUnit*)(m_m2FileData + m_modelView->ofsTex); for (unsigned int i = 0; i < m_modelView->nTex; ++i) { ModelTexUnit& texUnit = texUnitData[i]; unsigned short textureID = texLookupData[texUnit.textureid]; ModelTextureDef& texDef = texUnitDefData[textureID]; string textureName; if (texDef.type == 0) textureName = (LPCSTR)(m_m2FileData + texDef.nameOfs); else textureName = getReplacableTexture(texDef.type); StdMat2* material = m_materialList[texUnit.op]; if (!material) material = createMaterial(); // 根据混合属性决定加在第几层 material->SetSubTexmap(ID_DI, createTexture(textureName.c_str())); material->EnableMap(ID_DI, TRUE); //material->SetTwoSided(TRUE); // 双面 设置了此标志的才打开 m_maxInterface->GetMaterialLibrary().Add(material); m_geosetNodeList[texUnit.op]->SetMtl(material); } m_maxInterface->RedrawViews(m_maxInterface->GetTime()); }
/* * Get vertex normal using smooth group with normal method.(using RVertex information) * FaceVertexIdx is between 0 and 2 local to a triangle */ Point3 SGP_MaxInterface::GetVertexNormal( Mesh* pMesh, int faceId, int vertexId, int FaceVertexIdx ) { ////////////////////////////////////////////////////////////////////////// // Below is the way if someone has used "edit normals modifier" to change vertex normal MeshNormalSpec *pNormalSpec = pMesh->GetSpecifiedNormals(); if( pNormalSpec ) { const int NumFaces = pNormalSpec->GetNumFaces(); const int NumNormals = pNormalSpec->GetNumNormals(); if( NumFaces != 0 && NumNormals != 0 ) { const int NormalID = pNormalSpec->Face(faceId).GetNormalID(FaceVertexIdx); return pNormalSpec->Normal(NormalID).Normalize(); } } ////////////////////////////////////////////////////////////////////////// RVertex *pRVertex; pRVertex = pMesh->getRVertPtr(vertexId); // get the face Face *pFace; pFace = &pMesh->faces[faceId]; // get the smoothing group of the face DWORD smGroup; smGroup = pFace->smGroup; // get the number of normals int normalCount; normalCount = pRVertex->rFlags & NORCT_MASK; // check if the normal is specified ... if(pRVertex->rFlags & SPECIFIED_NORMAL) { return pRVertex->rn.getNormal(); } // ... otherwise, check for a smoothing group else if((normalCount > 0) && (smGroup != 0)) { // If there is only one vertex is found in the rn member. if(normalCount == 1) { return pRVertex->rn.getNormal(); } else { int normalId; Point3 n(0,0,0); for(normalId = 0; normalId < normalCount; normalId++) { if(pRVertex->ern[normalId].getSmGroup() & smGroup) { n = n+pRVertex->ern[normalId].getNormal(); } } n = n.Normalize(); return n; } } // if all fails, return the face normal return pMesh->getFaceNormal(faceId); }
BOOL GetVertexNormalUsingSmoothGroup(Point3& VN, Mesh& mesh, int faceId, int globalvertexId, int _FaceVertexIdx) { //_FaceVertexIdx is between 0 and 2 local to a triangle //THIS IS WHAT YOU NEED IF SOMEONE HAS USED THE EDIT NORMAL MODIFIER MeshNormalSpec * normalspec = mesh.GetSpecifiedNormals(); if (normalspec) { const int NumFaces = normalspec->GetNumFaces (); const int NumNormals = normalspec->GetNumNormals (); if (NumFaces && NumNormals) { const int normID = normalspec->Face(faceId).GetNormalID(_FaceVertexIdx); VN = normalspec->Normal(normID).Normalize(); return TRUE; } } // get the "rendered" vertex RVertex *pRVertex = mesh.getRVertPtr(globalvertexId); if(! pRVertex)return FALSE; // get the face const Face& Face = mesh.faces[faceId]; // get the smoothing group of the face const DWORD smGroup = Face.smGroup; // get the number of normals const int normalCount = pRVertex->rFlags & NORCT_MASK; // check if the normal is specified ... if(pRVertex->rFlags & SPECIFIED_NORMAL) { VN = pRVertex->rn.getNormal(); return TRUE; } // ... otherwise, check for a smoothing group else if((normalCount > 0) && (smGroup != 0)) { // If there is only one vertex is found in the rn member. if(normalCount == 1) { VN = pRVertex->rn.getNormal(); return TRUE; } else { for(int normalId = 0; normalId < normalCount; normalId++) { if(pRVertex->ern[normalId].getSmGroup() & smGroup) { VN = pRVertex->ern[normalId].getNormal(); return TRUE; } } } } // if all failed, return the face normal VN = mesh.getFaceNormal(faceId); return TRUE; }