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