1 // -*-c++-*-
2 
3 /*
4  * $Id$
5  *
6  * DirectX file converter for OpenSceneGraph.
7  * Copyright (c)2002 Ulrich Hertlein <u.hertlein@sandbox.de>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 #include "directx.h"
25 
26 #include <osg/TexEnv>
27 #include <osg/CullFace>
28 
29 #include <osg/Geode>
30 #include <osg/Geometry>
31 #include <osg/Material>
32 #include <osg/Image>
33 #include <osg/Texture2D>
34 
35 #include <osg/Notify>
36 #include <osgDB/Registry>
37 #include <osgDB/ReadFile>
38 #include <osgDB/FileNameUtils>
39 #include <osgDB/FileUtils>
40 
41 #include <map>
42 #include <iostream>
43 
44 
45 /**
46  * OpenSceneGraph plugin wrapper/converter.
47  */
48 class ReaderWriterDirectX : public osgDB::ReaderWriter
49 {
50 public:
ReaderWriterDirectX()51     ReaderWriterDirectX()
52     {
53         supportsExtension("x","DirectX scene format");
54         supportsOption("flipTexture", "flip texture upside-down");
55         // made hand switching an option - .x models from XSI's export are right-handed already
56         supportsOption("rightHanded", "prevents reader from switching handedness for right handed files");
57         supportsOption("leftHanded", "reader switches handedness for left handed files");
58     }
59 
className() const60     virtual const char* className() const {
61         return "DirectX Reader";
62     }
63 
64     virtual ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options* options) const;
65     virtual ReadResult readNode(std::istream& fin, const osgDB::ReaderWriter::Options* options) const;
66 
67 private:
68     osg::Group * convertFromDX(DX::Object & obj, bool switchToLeftHanded, bool flipTexture, float creaseAngle,
69             const osgDB::ReaderWriter::Options * options) const;
70 
71     osg::Geode * convertFromDX(DX::Mesh & mesh, bool switchToLeftHanded, bool flipTexture, float creaseAngle,
72             const osgDB::ReaderWriter::Options * options) const;
73 };
74 
75 // Register with Registry to instantiate the above reader/writer.
REGISTER_OSGPLUGIN(x,ReaderWriterDirectX)76 REGISTER_OSGPLUGIN(x, ReaderWriterDirectX)
77 
78 
79 // Read node
80 osgDB::ReaderWriter::ReadResult ReaderWriterDirectX::readNode(const std::string& file, const osgDB::ReaderWriter::Options* options) const
81 {
82     std::string ext = osgDB::getLowerCaseFileExtension(file);
83     if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
84 
85     std::string fileName = osgDB::findDataFile( file, options );
86     if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;
87 
88     OSG_INFO << "ReaderWriterDirectX::readNode(" << fileName << ")\n";
89 
90     osgDB::ifstream fin(fileName.c_str());
91     if (fin.bad()) {
92         OSG_WARN << "ReaderWriterDirectX failed to read '" << fileName.c_str() << "'\n";
93         return ReadResult::ERROR_IN_READING_FILE;
94     }
95 
96     // code for setting up the database path so that internally referenced file are searched for on relative paths.
97     osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
98     local_opt->setDatabasePath(osgDB::getFilePath(fileName));
99 
100     return readNode(fin, local_opt.get());
101 }
102 
readNode(std::istream & fin,const osgDB::ReaderWriter::Options * options) const103 osgDB::ReaderWriter::ReadResult ReaderWriterDirectX::readNode(std::istream& fin, const osgDB::ReaderWriter::Options* options) const
104 {
105     DX::Object obj;
106     if (obj.load(fin) == false) {
107         OSG_WARN << "ReaderWriterDirectX failed to read stream" << std::endl;
108         return ReadResult::ERROR_IN_READING_FILE;
109     }
110 
111     // Options?
112     bool flipTexture = true;
113     bool switchToLeftHanded = true; // when true: swap y and z for incoming files
114     float creaseAngle = 80.0f;
115     if (options) {
116         const std::string option = options->getOptionString();
117         if (option.find("rightHanded") != std::string::npos) {
118             switchToLeftHanded = false;
119         }
120         if (option.find("leftHanded") != std::string::npos) {
121             switchToLeftHanded = true;
122         }
123         if (option.find("flipTexture") != std::string::npos) {
124             flipTexture = false;
125         }
126         if (option.find("creaseAngle") != std::string::npos) {
127             // TODO
128         }
129     }
130 
131     // Convert to osg::Group
132     osg::Group* group = convertFromDX(obj, switchToLeftHanded, flipTexture, creaseAngle, options);
133     if (!group) {
134         OSG_WARN << "ReaderWriterDirectX failed to convert\n";
135         return ReadResult::ERROR_IN_READING_FILE;
136     }
137 
138     return group;
139 }
140 
141 
142 // Convert DirectX object
convertFromDX(DX::Object & obj,bool switchToLeftHanded,bool flipTexture,float creaseAngle,const osgDB::ReaderWriter::Options * options) const143 osg::Group * ReaderWriterDirectX::convertFromDX(DX::Object & obj, bool switchToLeftHanded,
144                                                 bool flipTexture, float creaseAngle,
145                                                 const osgDB::ReaderWriter::Options * options) const
146 {
147     osg::ref_ptr<osg::Group> group = new osg::Group;
148 
149     for (unsigned int i = 0; i < obj.getNumMeshes(); ++i) {
150         //std::cerr << "converting mesh " << i << std::endl;
151         DX::Mesh & mesh = *obj.getMesh(i);
152         osg::Geode * geode = convertFromDX(mesh, switchToLeftHanded, flipTexture, creaseAngle, options);
153         if (!geode) {
154             return 0;
155         }
156         group->addChild(geode);
157     }
158 
159     return group.release();
160 }
161 
162 // Convert DirectX mesh to osg::Geode
convertFromDX(DX::Mesh & mesh,bool switchToLeftHanded,bool flipTexture,float creaseAngle,const osgDB::ReaderWriter::Options * options) const163 osg::Geode* ReaderWriterDirectX::convertFromDX(DX::Mesh & mesh, bool switchToLeftHanded,
164                                                bool flipTexture, float creaseAngle,
165                                                const osgDB::ReaderWriter::Options * options) const
166 {
167     const DX::MeshMaterialList* meshMaterial = mesh.getMeshMaterialList();
168     if (!meshMaterial)
169         return NULL;
170 
171     const DX::MeshNormals* meshNormals = mesh.getMeshNormals();
172     if (!meshNormals) {
173         mesh.generateNormals(creaseAngle);
174         meshNormals = mesh.getMeshNormals();
175     }
176     //std::cerr << "normals=" << meshNormals << std::endl;
177     if (!meshNormals)
178         return NULL;
179 
180     const DX::MeshTextureCoords* meshTexCoords = mesh.getMeshTextureCoords();
181     //std::cerr << "texcoord=" << meshTexCoords << std::endl;
182 
183     /*
184      * - MeshMaterialList contains a list of Material and a per-face
185      *   information with Material is to be applied to which face.
186      * - Mesh contains a list of Vertices and a per-face information
187      *   which vertices (three or four) belong to this face.
188      * - MeshNormals contains a list of Normals and a per-face information
189      *   which normal is used by which vertex.
190      * - MeshTextureCoords contains a list of per-vertex texture coordinates.
191      *
192      * - Uses left-hand CS with Y-up, Z-into
193      *   obj_x -> osg_x
194      *   obj_y -> osg_z
195      *   obj_z -> osg_y
196      *
197      * - aa: Changed always change left to right hand to an option that allows
198      *   us to read right-handed models as-is.  Our modeler is using XSI, which
199      *   exports to right-handed system.
200      *
201      * - Polys are CW oriented
202      */
203     std::vector<osg::Geometry*> geomList;
204 
205     // Texture-for-Image map
206     std::map<std::string, osg::Texture2D*> texForImage;
207 
208     unsigned int i;
209     for (i = 0; i < meshMaterial->material.size(); i++) {
210 
211         //std::cerr << "material " << i << std::endl;
212 
213         const DX::Material& mtl = meshMaterial->material[i];
214         osg::StateSet* state = new osg::StateSet;
215 
216         // Material
217         osg::Material* material = new osg::Material;
218         state->setAttributeAndModes(material);
219 
220         float alpha = mtl.faceColor.alpha;
221         osg::Vec4 ambient(mtl.faceColor.red,
222                           mtl.faceColor.green,
223                           mtl.faceColor.blue,
224                           alpha);
225         material->setAmbient(osg::Material::FRONT, ambient);
226         material->setDiffuse(osg::Material::FRONT, ambient);
227 
228         material->setShininess(osg::Material::FRONT, mtl.power);
229 
230         osg::Vec4 specular(mtl.specularColor.red,
231                            mtl.specularColor.green,
232                            mtl.specularColor.blue, alpha);
233         material->setSpecular(osg::Material::FRONT, specular);
234 
235         osg::Vec4 emissive(mtl.emissiveColor.red,
236                            mtl.emissiveColor.green,
237                            mtl.emissiveColor.blue, alpha);
238         material->setEmission(osg::Material::FRONT, emissive);
239 
240         // Transparency? Set render hint & blending
241         if (alpha < 1.0f) {
242             state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
243             state->setMode(GL_BLEND, osg::StateAttribute::ON);
244         }
245         else
246             state->setMode(GL_BLEND, osg::StateAttribute::OFF);
247 
248         unsigned int textureCount = mtl.texture.size();
249         for (unsigned int j = 0; j < textureCount; j++) {
250 
251             //std::cerr << "texture " << j << std::endl;
252 
253             // Share image/texture pairs
254             osg::Texture2D* texture = texForImage[mtl.texture[j]];
255             if (!texture) {
256                 osg::ref_ptr<osg::Image> image = osgDB::readRefImageFile(mtl.texture[j],options);
257                 if (!image)
258                     continue;
259 
260                 // Texture
261                 texture = new osg::Texture2D;
262                 texForImage[mtl.texture[j]] = texture;
263 
264                 texture->setImage(image.get());
265                 texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT);
266                 texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT);
267             }
268             state->setTextureAttributeAndModes(j, texture);
269         }
270 
271         // Geometry
272         osg::Geometry* geom = new osg::Geometry;
273         geomList.push_back(geom);
274 
275         geom->setStateSet(state);
276 
277         // Arrays to hold vertices, normals, and texcoords.
278         geom->setVertexArray(new osg::Vec3Array);
279         geom->setNormalArray(new osg::Vec3Array, osg::Array::BIND_PER_VERTEX);
280         if (textureCount) {
281             // All texture units share the same array
282             osg::Vec2Array* texCoords = new osg::Vec2Array;
283             for (unsigned int j = 0; j < textureCount; j++)
284                 geom->setTexCoordArray(j, texCoords);
285         }
286 
287         geom->addPrimitiveSet(new osg::DrawArrayLengths(osg::PrimitiveSet::POLYGON));
288     }
289 
290     const std::vector<DX::MeshFace> & faces = mesh.getFaces();
291     if (faces.size() != meshMaterial->faceIndices.size())
292     {
293         OSG_FATAL<<"Error: internal error in DirectX .x loader,"<<std::endl;
294         OSG_FATAL<<"       mesh->faces.size() == meshMaterial->faceIndices.size()"<<std::endl;
295         return NULL;
296     }
297 
298     // Add faces to Geometry
299     for (i = 0; i < meshMaterial->faceIndices.size(); i++) {
300 
301         // Geometry for Material
302         unsigned int mi = meshMaterial->faceIndices[i];
303         osg::Geometry* geom = geomList[mi];
304 
305         // #pts of this face
306         unsigned int np = faces[i].size();
307         ((osg::DrawArrayLengths*) geom->getPrimitiveSet(0))->push_back(np);
308 
309         if (np != meshNormals->faceNormals[i].size())
310         {
311             OSG_WARN<<"DirectX loader: Error, error in normal list."<<std::endl;
312         }
313 
314         osg::Vec3Array* vertexArray = (osg::Vec3Array*) geom->getVertexArray();
315         osg::Vec3Array* normalArray = (osg::Vec3Array*) geom->getNormalArray();
316         osg::Vec2Array* texCoordArray=NULL; // only make them if the original has them
317         if(meshTexCoords) texCoordArray = (osg::Vec2Array*) geom->getTexCoordArray(0);
318 
319         // Add vertices, normals, texcoords
320         for (unsigned int j = 0; j < np; j++) {
321 
322             // Convert CW to CCW order
323             unsigned int jj = (j > 0 ? np - j : j);
324             if(!switchToLeftHanded) jj=j;
325 
326             // Vertices
327             unsigned int vi = faces[i][jj];
328             if (vertexArray) {
329                 const DX::Vector & v = mesh.getVertices()[vi];
330                 if(switchToLeftHanded)// Transform Xleft/Yup/Zinto to Xleft/Yinto/Zup
331                     vertexArray->push_back(osg::Vec3(v.x,v.z,v.y));
332                 else
333                     vertexArray->push_back(osg::Vec3(v.x,v.y,v.z));
334             }
335 
336             // Normals
337             unsigned int ni = meshNormals->faceNormals[i][jj];
338             if (normalArray) {
339                 const DX::Vector& n = meshNormals->normals[ni];
340                 if(switchToLeftHanded)// Transform Xleft/Yup/Zinto to Xleft/Yinto/Zup
341                     normalArray->push_back(osg::Vec3(n.x,n.z,n.y));
342                 else
343                     normalArray->push_back(osg::Vec3(n.x,n.y,n.z));
344             }
345 
346             // TexCoords
347             if (texCoordArray) {
348                 const DX::Coords2d& tc = (*meshTexCoords)[vi];
349                 osg::Vec2 uv;
350                 if (flipTexture){
351                     if(switchToLeftHanded)
352                         uv.set(tc.u, 1.0f - tc.v); // Image is upside down
353                     else
354                         uv.set(1.0f - tc.u, 1.0f - tc.v); // Image is 180 degrees
355                 }
356                 else
357                     uv.set(tc.u, tc.v);
358                 texCoordArray->push_back(uv);
359             }
360         }
361     }
362 
363     // Add non-empty nodes to Geode
364     osg::Geode* geode = new osg::Geode;
365     for (i = 0; i < geomList.size(); i++) {
366         osg::Geometry* geom = geomList[i];
367         if (((osg::Vec3Array*) geom->getVertexArray())->size())
368             geode->addDrawable(geom);
369     }
370 
371     // Back-face culling
372     osg::StateSet* state = new osg::StateSet;
373     geode->setStateSet(state);
374 
375     osg::CullFace* cullFace = new osg::CullFace;
376     cullFace->setMode(osg::CullFace::BACK);
377     state->setAttributeAndModes(cullFace);
378 
379     return geode;
380 }
381