// Copyright (C) 2002-2012 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h // TODO: second UV-coordinates currently ignored in textures #include "IrrCompileConfig.h" #ifdef _IRR_COMPILE_WITH_COLLADA_WRITER_ #include "CColladaMeshWriter.h" #include "os.h" #include "IFileSystem.h" #include "IWriteFile.h" #include "IXMLWriter.h" #include "IMesh.h" #include "IAttributes.h" #include "IAnimatedMeshSceneNode.h" #include "IMeshSceneNode.h" #include "ITerrainSceneNode.h" #include "ILightSceneNode.h" #include "ICameraSceneNode.h" #include "ISceneManager.h" namespace irr { namespace scene { //! Which lighting model should be used in the technique (FX) section when exporting effects (materials) E_COLLADA_TECHNIQUE_FX CColladaMeshWriterProperties::getTechniqueFx(const video::SMaterial& material) const { return ECTF_BLINN; } //! Which texture index should be used when writing the texture of the given sampler color. s32 CColladaMeshWriterProperties::getTextureIdx(const video::SMaterial & material, E_COLLADA_COLOR_SAMPLER cs) const { // So far we just export in a way which is similar to how we import colladas. // There might be better ways to do this, but I suppose it depends a lot for which target // application we export, so in most cases it will have to be done in user-code anyway. switch ( cs ) { case ECCS_DIFFUSE: return 2; case ECCS_AMBIENT: return 1; case ECCS_EMISSIVE: return 0; case ECCS_SPECULAR: return 3; case ECCS_TRANSPARENT: return -1; case ECCS_REFLECTIVE: return -1; }; return -1; } E_COLLADA_IRR_COLOR CColladaMeshWriterProperties::getColorMapping(const video::SMaterial & material, E_COLLADA_COLOR_SAMPLER cs) const { switch ( cs ) { case ECCS_DIFFUSE: return ECIC_DIFFUSE; case ECCS_AMBIENT: return ECIC_AMBIENT; case ECCS_EMISSIVE: return ECIC_EMISSIVE; case ECCS_SPECULAR: return ECIC_SPECULAR; case ECCS_TRANSPARENT: return ECIC_NONE; case ECCS_REFLECTIVE: return ECIC_CUSTOM; }; return ECIC_NONE; } //! Return custom colors for certain color types requested by collada. video::SColor CColladaMeshWriterProperties::getCustomColor(const video::SMaterial & material, E_COLLADA_COLOR_SAMPLER cs) const { return video::SColor(255, 0, 0, 0); } //! Return the settings for transparence E_COLLADA_TRANSPARENT_FX CColladaMeshWriterProperties::getTransparentFx(const video::SMaterial& material) const { // TODO: figure out best default mapping return ECOF_A_ONE; } //! Transparency value for the material. f32 CColladaMeshWriterProperties::getTransparency(const video::SMaterial& material) const { // TODO: figure out best default mapping return -1.f; } //! Reflectivity value for that material f32 CColladaMeshWriterProperties::getReflectivity(const video::SMaterial& material) const { // TODO: figure out best default mapping return 0.f; } //! Return index of refraction for that material f32 CColladaMeshWriterProperties::getIndexOfRefraction(const video::SMaterial& material) const { return -1.f; } bool CColladaMeshWriterProperties::isExportable(const irr::scene::ISceneNode * node) const { return node && node->isVisible(); } IMesh* CColladaMeshWriterProperties::getMesh(irr::scene::ISceneNode * node) { if ( !node ) return 0; if ( node->getType() == ESNT_ANIMATED_MESH ) return static_cast(node)->getMesh()->getMesh(0); // TODO: we need some ISceneNode::hasType() function to get rid of those checks if ( node->getType() == ESNT_MESH || node->getType() == ESNT_CUBE || node->getType() == ESNT_SPHERE || node->getType() == ESNT_WATER_SURFACE || node->getType() == ESNT_Q3SHADER_SCENE_NODE ) return static_cast(node)->getMesh(); if ( node->getType() == ESNT_TERRAIN ) return static_cast(node)->getMesh(); return 0; } // Check if the node has it's own material overwriting the mesh-materials bool CColladaMeshWriterProperties::useNodeMaterial(const scene::ISceneNode* node) const { if ( !node ) return false; // TODO: we need some ISceneNode::hasType() function to get rid of those checks bool useMeshMaterial = ( (node->getType() == ESNT_MESH || node->getType() == ESNT_CUBE || node->getType() == ESNT_SPHERE || node->getType() == ESNT_WATER_SURFACE || node->getType() == ESNT_Q3SHADER_SCENE_NODE) && static_cast(node)->isReadOnlyMaterials()) || (node->getType() == ESNT_ANIMATED_MESH && static_cast(node)->isReadOnlyMaterials() ); return !useMeshMaterial; } CColladaMeshWriterNames::CColladaMeshWriterNames(IColladaMeshWriter * writer) : ColladaMeshWriter(writer) { } irr::core::stringw CColladaMeshWriterNames::nameForMesh(const scene::IMesh* mesh, int instance) { irr::core::stringw name(L"mesh"); name += nameForPtr(mesh); if ( instance > 0 ) { name += L"i"; name += irr::core::stringw(instance); } return ColladaMeshWriter->toNCName(name); } irr::core::stringw CColladaMeshWriterNames::nameForNode(const scene::ISceneNode* node) { irr::core::stringw name; // Prefix, because xs::ID can't start with a number, also nicer name if ( node && node->getType() == ESNT_LIGHT ) name = L"light"; else name = L"node"; name += nameForPtr(node); if ( node ) { name += irr::core::stringw(node->getName()); } return ColladaMeshWriter->toNCName(name); } irr::core::stringw CColladaMeshWriterNames::nameForMaterial(const video::SMaterial & material, int materialId, const scene::IMesh* mesh, const scene::ISceneNode* node) { core::stringw strMat(L"mat"); bool nodeMaterial = ColladaMeshWriter->getProperties()->useNodeMaterial(node); if ( nodeMaterial ) { strMat += L"node"; strMat += nameForPtr(node); strMat += irr::core::stringw(node->getName()); } strMat += L"mesh"; strMat += nameForPtr(mesh); strMat += materialId; return ColladaMeshWriter->toNCName(strMat); } irr::core::stringw CColladaMeshWriterNames::nameForPtr(const void* ptr) const { wchar_t buf[32]; swprintf_irr(buf, 32, L"%p", ptr); return irr::core::stringw(buf); } CColladaMeshWriter::CColladaMeshWriter( ISceneManager * smgr, video::IVideoDriver* driver, io::IFileSystem* fs) : FileSystem(fs), VideoDriver(driver), Writer(0) { #ifdef _DEBUG setDebugName("CColladaMeshWriter"); #endif if (VideoDriver) VideoDriver->grab(); if (FileSystem) FileSystem->grab(); if ( smgr ) setAmbientLight( smgr->getAmbientLight() ); CColladaMeshWriterProperties * p = new CColladaMeshWriterProperties(); setDefaultProperties(p); setProperties(p); p->drop(); CColladaMeshWriterNames * nameGenerator = new CColladaMeshWriterNames(this); setDefaultNameGenerator(nameGenerator); setNameGenerator(nameGenerator); nameGenerator->drop(); } CColladaMeshWriter::~CColladaMeshWriter() { if (VideoDriver) VideoDriver->drop(); if (FileSystem) FileSystem->drop(); } void CColladaMeshWriter::reset() { LibraryImages.clear(); Meshes.clear(); LightNodes.clear(); CameraNodes.clear(); MaterialsWritten.clear(); EffectsWritten.clear(); MaterialNameCache.clear(); } //! Returns the type of the mesh writer EMESH_WRITER_TYPE CColladaMeshWriter::getType() const { return EMWT_COLLADA; } //! writes a scene starting with the given node bool CColladaMeshWriter::writeScene(io::IWriteFile* file, scene::ISceneNode* root) { if (!file || !root) return false; reset(); Writer = FileSystem->createXMLWriter(file); if (!Writer) { os::Printer::log("Could not write file", file->getFileName()); return false; } Directory = FileSystem->getFileDir(FileSystem->getAbsolutePath( file->getFileName() )); // make names for all nodes with exportable meshes makeMeshNames(root); os::Printer::log("Writing scene", file->getFileName()); // write COLLADA header Writer->writeXMLHeader(); Writer->writeElement(L"COLLADA", false, L"xmlns", L"http://www.collada.org/2005/11/COLLADASchema", L"version", L"1.4.1"); Writer->writeLineBreak(); // write asset data writeAsset(); // write all materials Writer->writeElement(L"library_materials", false); Writer->writeLineBreak(); writeNodeMaterials(root); Writer->writeClosingTag(L"library_materials"); Writer->writeLineBreak(); Writer->writeElement(L"library_effects", false); Writer->writeLineBreak(); writeNodeEffects(root); Writer->writeClosingTag(L"library_effects"); Writer->writeLineBreak(); // images writeLibraryImages(); // lights Writer->writeElement(L"library_lights", false); Writer->writeLineBreak(); writeAmbientLightElement( getAmbientLight() ); writeNodeLights(root); Writer->writeClosingTag(L"library_lights"); Writer->writeLineBreak(); // cameras Writer->writeElement(L"library_cameras", false); Writer->writeLineBreak(); writeNodeCameras(root); Writer->writeClosingTag(L"library_cameras"); Writer->writeLineBreak(); // write meshes Writer->writeElement(L"library_geometries", false); Writer->writeLineBreak(); writeAllMeshGeometries(); Writer->writeClosingTag(L"library_geometries"); Writer->writeLineBreak(); // write scene Writer->writeElement(L"library_visual_scenes", false); Writer->writeLineBreak(); Writer->writeElement(L"visual_scene", false, L"id", L"default_scene"); Writer->writeLineBreak(); // ambient light (instance_light also needs a node as parent so we have to create one) Writer->writeElement(L"node", false); Writer->writeLineBreak(); Writer->writeElement(L"instance_light", true, L"url", L"#ambientlight"); Writer->writeLineBreak(); Writer->writeClosingTag(L"node"); Writer->writeLineBreak(); // Write the scenegraph. if ( root->getType() != ESNT_SCENE_MANAGER ) { // TODO: Not certain if we should really write the root or if we should just always only write the children. // For now writing root to keep backward compatibility for this case, but if anyone needs to _not_ write // that root-node we can add a parameter for this later on in writeScene. writeSceneNode(root); } else { // The visual_scene element is identical to our scenemanager and acts as root, // so we do not write the root itself if it points to the scenemanager. const core::list& rootChildren = root->getChildren(); for ( core::list::ConstIterator it = rootChildren.begin(); it != rootChildren.end(); ++ it ) { writeSceneNode(*it); } } Writer->writeClosingTag(L"visual_scene"); Writer->writeLineBreak(); Writer->writeClosingTag(L"library_visual_scenes"); Writer->writeLineBreak(); // instance scene Writer->writeElement(L"scene", false); Writer->writeLineBreak(); Writer->writeElement(L"instance_visual_scene", true, L"url", L"#default_scene"); Writer->writeLineBreak(); Writer->writeClosingTag(L"scene"); Writer->writeLineBreak(); // close everything Writer->writeClosingTag(L"COLLADA"); Writer->drop(); return true; } void CColladaMeshWriter::makeMeshNames(irr::scene::ISceneNode * node) { if ( !node || !getProperties() || !getProperties()->isExportable(node) || !getNameGenerator()) return; IMesh* mesh = getProperties()->getMesh(node); if ( mesh ) { if ( !Meshes.find(mesh) ) { SColladaMesh cm; cm.Name = nameForMesh(mesh, 0); Meshes.insert(mesh, cm); } } const core::list& children = node->getChildren(); for ( core::list::ConstIterator it = children.begin(); it != children.end(); ++it ) { makeMeshNames(*it); } } void CColladaMeshWriter::writeNodeMaterials(irr::scene::ISceneNode * node) { if ( !node || !getProperties() || !getProperties()->isExportable(node) ) return; core::array materialNames; IMesh* mesh = getProperties()->getMesh(node); if ( mesh ) { MeshNode * n = Meshes.find(mesh); if ( !getProperties()->useNodeMaterial(node) ) { // no material overrides - write mesh materials if ( n && !n->getValue().MaterialsWritten ) { writeMeshMaterials(mesh, getGeometryWriting() == ECGI_PER_MESH_AND_MATERIAL ? &materialNames : NULL); n->getValue().MaterialsWritten = true; } } else { // write node materials for (u32 i=0; igetMaterialCount(); ++i) { video::SMaterial & material = node->getMaterial(i); core::stringw strMat(nameForMaterial(material, i, mesh, node)); writeMaterial(strMat); if ( getGeometryWriting() == ECGI_PER_MESH_AND_MATERIAL ) materialNames.push_back(strMat); } } // When we write another mesh-geometry for each new material-list we have // to figure out here if we need another geometry copy and create a new name here. if ( n && getGeometryWriting() == ECGI_PER_MESH_AND_MATERIAL ) { SGeometryMeshMaterials * geomMat = n->getValue().findGeometryMeshMaterials(materialNames); if ( geomMat ) geomMat->MaterialOwners.push_back(node); else { SGeometryMeshMaterials gmm; if ( n->getValue().GeometryMeshMaterials.empty() ) gmm.GeometryName = n->getValue().Name; // first one can use the original name else gmm.GeometryName = nameForMesh(mesh, n->getValue().GeometryMeshMaterials.size()); gmm.MaterialNames = materialNames; gmm.MaterialOwners.push_back(node); n->getValue().GeometryMeshMaterials.push_back(gmm); } } } const core::list& children = node->getChildren(); for ( core::list::ConstIterator it = children.begin(); it != children.end(); ++it ) { writeNodeMaterials( *it ); } } void CColladaMeshWriter::writeMaterial(const irr::core::stringw& materialname) { if ( MaterialsWritten.find(materialname) ) return; MaterialsWritten.insert(materialname, true); Writer->writeElement(L"material", false, L"id", materialname.c_str(), L"name", materialname.c_str()); Writer->writeLineBreak(); // We don't make a difference between material and effect on export. // Every material is just using an instance of an effect. core::stringw strFx(materialname); strFx += L"-fx"; Writer->writeElement(L"instance_effect", true, L"url", (core::stringw(L"#") + strFx).c_str()); Writer->writeLineBreak(); Writer->writeClosingTag(L"material"); Writer->writeLineBreak(); } void CColladaMeshWriter::writeNodeEffects(irr::scene::ISceneNode * node) { if ( !node || !getProperties() || !getProperties()->isExportable(node) || !getNameGenerator() ) return; IMesh* mesh = getProperties()->getMesh(node); if ( mesh ) { if ( !getProperties()->useNodeMaterial(node) ) { // no material overrides - write mesh materials MeshNode * n = Meshes.find(mesh); if ( n && !n->getValue().EffectsWritten ) { writeMeshEffects(mesh); n->getValue().EffectsWritten = true; } } else { // write node materials for (u32 i=0; igetMaterialCount(); ++i) { video::SMaterial & material = node->getMaterial(i); irr::core::stringw materialfxname(nameForMaterial(material, i, mesh, node)); materialfxname += L"-fx"; writeMaterialEffect(materialfxname, material); } } } const core::list& children = node->getChildren(); for ( core::list::ConstIterator it = children.begin(); it != children.end(); ++it ) { writeNodeEffects( *it ); } } void CColladaMeshWriter::writeNodeLights(irr::scene::ISceneNode * node) { if ( !node || !getProperties() || !getProperties()->isExportable(node)) return; if ( node->getType() == ESNT_LIGHT ) { ILightSceneNode * lightNode = static_cast(node); const video::SLight& lightData = lightNode->getLightData(); SColladaLight cLight; cLight.Name = nameForNode(node); LightNodes.insert(node, cLight); Writer->writeElement(L"light", false, L"id", cLight.Name.c_str()); Writer->writeLineBreak(); Writer->writeElement(L"technique_common", false); Writer->writeLineBreak(); switch ( lightNode->getLightType() ) { case video::ELT_POINT: Writer->writeElement(L"point", false); Writer->writeLineBreak(); writeColorElement(lightData.DiffuseColor, false); writeNode(L"constant_attenuation ", core::stringw(lightData.Attenuation.X).c_str()); writeNode(L"linear_attenuation ", core::stringw(lightData.Attenuation.Y).c_str()); writeNode(L"quadratic_attenuation", core::stringw(lightData.Attenuation.Z).c_str()); Writer->writeClosingTag(L"point"); Writer->writeLineBreak(); break; case video::ELT_SPOT: Writer->writeElement(L"spot", false); Writer->writeLineBreak(); writeColorElement(lightData.DiffuseColor, false); writeNode(L"constant_attenuation ", core::stringw(lightData.Attenuation.X).c_str()); writeNode(L"linear_attenuation ", core::stringw(lightData.Attenuation.Y).c_str()); writeNode(L"quadratic_attenuation", core::stringw(lightData.Attenuation.Z).c_str()); writeNode(L"falloff_angle", core::stringw(lightData.OuterCone * core::RADTODEG).c_str()); writeNode(L"falloff_exponent", core::stringw(lightData.Falloff).c_str()); Writer->writeClosingTag(L"spot"); Writer->writeLineBreak(); break; case video::ELT_DIRECTIONAL: Writer->writeElement(L"directional", false); Writer->writeLineBreak(); writeColorElement(lightData.DiffuseColor, false); Writer->writeClosingTag(L"directional"); Writer->writeLineBreak(); break; default: break; } Writer->writeClosingTag(L"technique_common"); Writer->writeLineBreak(); Writer->writeClosingTag(L"light"); Writer->writeLineBreak(); } const core::list& children = node->getChildren(); for ( core::list::ConstIterator it = children.begin(); it != children.end(); ++it ) { writeNodeLights( *it ); } } void CColladaMeshWriter::writeNodeCameras(irr::scene::ISceneNode * node) { if ( !node || !getProperties() || !getProperties()->isExportable(node) ) return; if ( isCamera(node) ) { ICameraSceneNode * cameraNode = static_cast(node); irr::core::stringw name = nameForNode(node); CameraNodes.insert(cameraNode, name); Writer->writeElement(L"camera", false, L"id", name.c_str()); Writer->writeLineBreak(); Writer->writeElement(L"optics", false); Writer->writeLineBreak(); Writer->writeElement(L"technique_common", false); Writer->writeLineBreak(); if ( cameraNode->isOrthogonal() ) { Writer->writeElement(L"orthographic", false); Writer->writeLineBreak(); irr::core::matrix4 projMat( cameraNode->getProjectionMatrix() ); irr::f32 xmag = 2.f/projMat[0]; irr::f32 ymag = 2.f/projMat[5]; // Note that Irrlicht camera does not update near/far when setting the projection matrix, // so we have to calculate that here (at least currently - maybe camera code will be updated at some time). irr::f32 nearMinusFar = -1.f/projMat[10]; irr::f32 zNear = projMat[14]*nearMinusFar; irr::f32 zFar = 1.f/projMat[10] + zNear; writeNode(L"xmag", core::stringw(xmag).c_str()); writeNode(L"ymag", core::stringw(ymag).c_str()); writeNode(L"znear", core::stringw(zNear).c_str()); writeNode(L"zfar", core::stringw(zFar).c_str()); Writer->writeClosingTag(L"orthographic"); Writer->writeLineBreak(); } else { Writer->writeElement(L"perspective", false); Writer->writeLineBreak(); writeNode(L"yfov", core::stringw(cameraNode->getFOV()*core::RADTODEG).c_str()); writeNode(L"aspect_ratio", core::stringw(cameraNode->getAspectRatio()).c_str()); writeNode(L"znear", core::stringw(cameraNode->getNearValue()).c_str()); writeNode(L"zfar", core::stringw(cameraNode->getFarValue()).c_str()); Writer->writeClosingTag(L"perspective"); Writer->writeLineBreak(); } Writer->writeClosingTag(L"technique_common"); Writer->writeLineBreak(); Writer->writeClosingTag(L"optics"); Writer->writeLineBreak(); Writer->writeClosingTag(L"camera"); Writer->writeLineBreak(); } const core::list& children = node->getChildren(); for ( core::list::ConstIterator it = children.begin(); it != children.end(); ++it ) { writeNodeCameras( *it ); } } void CColladaMeshWriter::writeAllMeshGeometries() { core::map::ConstIterator it = Meshes.getConstIterator(); for(; !it.atEnd(); it++ ) { IMesh* mesh = it->getKey(); const SColladaMesh& colladaMesh = it->getValue(); if ( getGeometryWriting() == ECGI_PER_MESH_AND_MATERIAL && colladaMesh.GeometryMeshMaterials.size() > 1 ) { for ( u32 i=0; iisExportable(node) ) return; // Collada doesn't require to set the id, but some other tools have problems if none exists, so we just add it. irr::core::stringw nameId(nameForNode(node)); Writer->writeElement(L"node", false, L"id", nameId.c_str()); Writer->writeLineBreak(); // DummyTransformationSceneNode don't have rotation, position, scale information // But also don't always export the transformation matrix as that forces us creating // new DummyTransformationSceneNode's on import. if ( node->getType() == ESNT_DUMMY_TRANSFORMATION ) { writeMatrixElement(node->getRelativeTransformation()); } else if ( isCamera(node) ) { // TODO: We do not handle the case when ICameraSceneNode::getTargetAndRotationBinding() is false. Probably we would have to create a second // node to do that. // Note: We can't use rotations for the camera as Irrlicht does not regard the up-vector in rotations so far. // We could maybe use projection matrices, but avoiding them might allow us to get rid of some DummyTransformationSceneNodes on // import in the future. So that's why we use the lookat element instead. ICameraSceneNode * camNode = static_cast(node); writeLookAtElement(camNode->getPosition(), camNode->getTarget(), camNode->getUpVector()); } else { writeTranslateElement( node->getPosition() ); irr::core::vector3df rot(node->getRotation()); core::quaternion quat(rot*core::DEGTORAD); f32 angle; core::vector3df axis; quat.toAngleAxis(angle, axis); writeRotateElement( axis, angle*core::RADTODEG ); writeScaleElement( node->getScale() ); } // instance geometry IMesh* mesh = getProperties()->getMesh(node); if ( mesh ) { MeshNode * n = Meshes.find(mesh); if ( n ) { const SColladaMesh& colladaMesh = n->getValue(); writeMeshInstanceGeometry(colladaMesh.findGeometryNameForNode(node), mesh, node); } } // instance light if ( node->getType() == ESNT_LIGHT ) { LightNode * n = LightNodes.find(node); if ( n ) writeLightInstance(n->getValue().Name); } // instance camera if ( isCamera(node) ) { CameraNode * camNode = CameraNodes.find(node); if ( camNode ) writeCameraInstance(camNode->getValue()); } const core::list& children = node->getChildren(); for ( core::list::ConstIterator it = children.begin(); it != children.end(); ++it ) { writeSceneNode( *it ); } Writer->writeClosingTag(L"node"); Writer->writeLineBreak(); } //! writes a mesh bool CColladaMeshWriter::writeMesh(io::IWriteFile* file, scene::IMesh* mesh, s32 flags) { if (!file) return false; reset(); Writer = FileSystem->createXMLWriter(file); if (!Writer) { os::Printer::log("Could not write file", file->getFileName()); return false; } Directory = FileSystem->getFileDir(FileSystem->getAbsolutePath( file->getFileName() )); os::Printer::log("Writing mesh", file->getFileName()); // write COLLADA header Writer->writeXMLHeader(); Writer->writeElement(L"COLLADA", false, L"xmlns", L"http://www.collada.org/2005/11/COLLADASchema", L"version", L"1.4.1"); Writer->writeLineBreak(); // write asset data writeAsset(); // write all materials Writer->writeElement(L"library_materials", false); Writer->writeLineBreak(); writeMeshMaterials(mesh); Writer->writeClosingTag(L"library_materials"); Writer->writeLineBreak(); Writer->writeElement(L"library_effects", false); Writer->writeLineBreak(); writeMeshEffects(mesh); Writer->writeClosingTag(L"library_effects"); Writer->writeLineBreak(); // images writeLibraryImages(); // write mesh Writer->writeElement(L"library_geometries", false); Writer->writeLineBreak(); irr::core::stringw meshname(nameForMesh(mesh, 0)); writeMeshGeometry(meshname, mesh); Writer->writeClosingTag(L"library_geometries"); Writer->writeLineBreak(); // write scene_library if ( getWriteDefaultScene() ) { Writer->writeElement(L"library_visual_scenes", false); Writer->writeLineBreak(); Writer->writeElement(L"visual_scene", false, L"id", L"default_scene"); Writer->writeLineBreak(); Writer->writeElement(L"node", false); Writer->writeLineBreak(); writeMeshInstanceGeometry(meshname, mesh); Writer->writeClosingTag(L"node"); Writer->writeLineBreak(); Writer->writeClosingTag(L"visual_scene"); Writer->writeLineBreak(); Writer->writeClosingTag(L"library_visual_scenes"); Writer->writeLineBreak(); // write scene Writer->writeElement(L"scene", false); Writer->writeLineBreak(); Writer->writeElement(L"instance_visual_scene", true, L"url", L"#default_scene"); Writer->writeLineBreak(); Writer->writeClosingTag(L"scene"); Writer->writeLineBreak(); } // close everything Writer->writeClosingTag(L"COLLADA"); Writer->drop(); return true; } void CColladaMeshWriter::writeMeshInstanceGeometry(const irr::core::stringw& meshname, scene::IMesh* mesh, scene::ISceneNode* node) { // Writer->writeElement(L"instance_geometry", false, L"url", toRef(meshname).c_str()); Writer->writeLineBreak(); Writer->writeElement(L"bind_material", false); Writer->writeLineBreak(); Writer->writeElement(L"technique_common", false); Writer->writeLineBreak(); // instance materials // bool useNodeMaterials = node && node->getMaterialCount() == mesh->getMeshBufferCount(); for (u32 i=0; igetMeshBufferCount(); ++i) { irr::core::stringw strMatSymbol(nameForMaterialSymbol(mesh, i)); core::stringw strMatTarget = "#"; video::SMaterial & material = useNodeMaterials ? node->getMaterial(i) : mesh->getMeshBuffer(i)->getMaterial(); strMatTarget += nameForMaterial(material, i, mesh, node); Writer->writeElement(L"instance_material", false, L"symbol", strMatSymbol.c_str(), L"target", strMatTarget.c_str()); Writer->writeLineBreak(); // TODO: need to handle second UV-set // Writer->writeElement(L"bind_vertex_input", true, L"semantic", L"uv", L"input_semantic", L"TEXCOORD", L"input_set", L"0" ); Writer->writeLineBreak(); Writer->writeClosingTag(L"instance_material"); Writer->writeLineBreak(); } Writer->writeClosingTag(L"technique_common"); Writer->writeLineBreak(); Writer->writeClosingTag(L"bind_material"); Writer->writeLineBreak(); Writer->writeClosingTag(L"instance_geometry"); Writer->writeLineBreak(); } void CColladaMeshWriter::writeLightInstance(const irr::core::stringw& lightName) { Writer->writeElement(L"instance_light", true, L"url", toRef(lightName).c_str()); Writer->writeLineBreak(); } void CColladaMeshWriter::writeCameraInstance(const irr::core::stringw& cameraName) { Writer->writeElement(L"instance_camera", true, L"url", toRef(cameraName).c_str()); Writer->writeLineBreak(); } bool CColladaMeshWriter::hasSecondTextureCoordinates(video::E_VERTEX_TYPE type) const { return type == video::EVT_2TCOORDS; } void CColladaMeshWriter::writeVector(const irr::core::vector3df& vec) { wchar_t tmpbuf[255]; swprintf_irr(tmpbuf, 255, L"%f %f %f", vec.X, vec.Y, vec.Z); Writer->writeText(tmpbuf); } void CColladaMeshWriter::writeUv(const irr::core::vector2df& vec) { // change handedness wchar_t tmpbuf[255]; swprintf_irr(tmpbuf, 255, L"%f %f", vec.X, 1.f-vec.Y); Writer->writeText(tmpbuf); } void CColladaMeshWriter::writeVector(const irr::core::vector2df& vec) { wchar_t tmpbuf[255]; swprintf_irr(tmpbuf, 255, L"%f %f", vec.X, vec.Y); Writer->writeText(tmpbuf); } void CColladaMeshWriter::writeColor(const irr::video::SColorf& colorf, bool writeAlpha) { wchar_t tmpbuf[255]; if ( writeAlpha ) swprintf_irr(tmpbuf, 255, L"%f %f %f %f", colorf.getRed(), colorf.getGreen(), colorf.getBlue(), colorf.getAlpha()); else swprintf_irr(tmpbuf, 255, L"%f %f %f", colorf.getRed(), colorf.getGreen(), colorf.getBlue()); Writer->writeText(tmpbuf); } irr::core::stringw CColladaMeshWriter::toString(const irr::video::ECOLOR_FORMAT format) const { switch ( format ) { case video::ECF_A1R5G5B5: return irr::core::stringw(L"A1R5G5B5"); case video::ECF_R5G6B5: return irr::core::stringw(L"R5G6B5"); case video::ECF_R8G8B8: return irr::core::stringw(L"R8G8B8"); case video::ECF_A8R8G8B8: return irr::core::stringw(L"A8R8G8B8"); default: return irr::core::stringw(L""); } } irr::core::stringw CColladaMeshWriter::toString(const irr::video::E_TEXTURE_CLAMP clamp) const { switch ( clamp ) { case video::ETC_REPEAT: return core::stringw(L"WRAP"); case video::ETC_CLAMP: case video::ETC_CLAMP_TO_EDGE: return core::stringw(L"CLAMP"); case video::ETC_CLAMP_TO_BORDER: return core::stringw(L"BORDER"); case video::ETC_MIRROR: case video::ETC_MIRROR_CLAMP: case video::ETC_MIRROR_CLAMP_TO_EDGE: case video::ETC_MIRROR_CLAMP_TO_BORDER: return core::stringw(L"MIRROR"); } return core::stringw(L"NONE"); } irr::core::stringw CColladaMeshWriter::toString(const irr::scene::E_COLLADA_TRANSPARENT_FX transparent) const { if ( transparent & ECOF_RGB_ZERO ) return core::stringw(L"RGB_ZERO"); else return core::stringw(L"A_ONE"); } irr::core::stringw CColladaMeshWriter::toRef(const irr::core::stringw& source) const { irr::core::stringw ref(L"#"); ref += source; return ref; } bool CColladaMeshWriter::isCamera(const scene::ISceneNode* node) const { // TODO: we need some ISceneNode::hasType() function to get rid of those checks if ( node->getType() == ESNT_CAMERA || node->getType() == ESNT_CAMERA_MAYA || node->getType() == ESNT_CAMERA_FPS ) return true; return false; } irr::core::stringw CColladaMeshWriter::nameForMesh(const scene::IMesh* mesh, int instance) const { IColladaMeshWriterNames * nameGenerator = getNameGenerator(); if ( nameGenerator ) { return nameGenerator->nameForMesh(mesh, instance); } return irr::core::stringw(L"missing_name_generator"); } irr::core::stringw CColladaMeshWriter::nameForNode(const scene::ISceneNode* node) const { IColladaMeshWriterNames * nameGenerator = getNameGenerator(); if ( nameGenerator ) { return nameGenerator->nameForNode(node); } return irr::core::stringw(L"missing_name_generator"); } irr::core::stringw CColladaMeshWriter::nameForMaterial(const video::SMaterial & material, int materialId, const scene::IMesh* mesh, const scene::ISceneNode* node) { irr::core::stringw matName; if ( getExportSMaterialsOnlyOnce() ) { matName = findCachedMaterialName(material); if ( !matName.empty() ) return matName; } IColladaMeshWriterNames * nameGenerator = getNameGenerator(); if ( nameGenerator ) { matName = nameGenerator->nameForMaterial(material, materialId, mesh, node); } else matName = irr::core::stringw(L"missing_name_generator"); if ( getExportSMaterialsOnlyOnce() ) MaterialNameCache.push_back (MaterialName(material, matName)); return matName; } // Each mesh-material has one symbol which is replaced on instantiation irr::core::stringw CColladaMeshWriter::nameForMaterialSymbol(const scene::IMesh* mesh, int materialId) const { wchar_t buf[100]; swprintf_irr(buf, 100, L"mat_symb_%p_%d", mesh, materialId); return irr::core::stringw(buf); } irr::core::stringw CColladaMeshWriter::findCachedMaterialName(const irr::video::SMaterial& material) const { for ( u32 i=0; i= 'A' && c <= 'Z') || c == L'_' || (c >= 'a' && c <= 'z') || (c >= 0xC0 && c <= 0xD6) || (c >= 0xD8 && c <= 0xF6) || (c >= 0xF8 && c <= 0x2FF) || (c >= 0x370 && c <= 0x37D) || (c >= 0x37F && c <= 0x1FFF) || (c >= 0x200C && c <= 0x200D) || (c >= 0x2070 && c <= 0x218F) || (c >= 0x2C00 && c <= 0x2FEF) || (c >= 0x3001 && c <= 0xD7FF) || (c >= 0xF900 && c <= 0xFDCF) || (c >= 0xFDF0 && c <= 0xFFFD) #if __SIZEOF_WCHAR_T__ == 4 || __WCHAR_MAX__ > 0x10000 || (c >= 0x10000 && c <= 0xEFFFF) #endif ; } bool CColladaMeshWriter::isXmlNameChar(wchar_t c) const { return isXmlNameStartChar(c) || c == L'-' || c == L'.' || (c >= '0' && c <= '9') || c == 0xB7 || (c >= 0x0300 && c <= 0x036F) || (c >= 0x203F && c <= 0x2040); } // Restrict the characters to a set of allowed characters in xs::NCName. irr::core::stringw CColladaMeshWriter::toNCName(const irr::core::stringw& oldString, const irr::core::stringw& prefix) const { irr::core::stringw result(prefix); // help to ensure id starts with a valid char and reduce chance of name-conflicts if ( oldString.empty() ) return result; result.append( oldString ); // We replace all characters not allowed by a replacement char const wchar_t REPLACMENT = L'-'; for ( irr::u32 i=1; i < result.size(); ++i ) { if ( result[i] == L':' || !isXmlNameChar(result[i]) ) { result[i] = REPLACMENT; } } return result; } const irr::core::stringw* CColladaMeshWriter::findGeometryNameForNode(ISceneNode* node) { IMesh* mesh = getProperties()->getMesh(node); if ( !mesh ) return NULL; MeshNode * n = Meshes.find(mesh); if ( !n ) return NULL; const SColladaMesh& colladaMesh = n->getValue(); return &colladaMesh.findGeometryNameForNode(node); } // Restrict the characters to a set of allowed characters in xs::NCName. irr::core::stringw CColladaMeshWriter::pathToURI(const irr::io::path& path) const { irr::core::stringw result; // is this a relative path? if ( path.size() > 1 && path[0] != _IRR_TEXT('/') && path[0] != _IRR_TEXT('\\') && path[1] != _IRR_TEXT(':') ) { // not already starting with "./" ? if ( path[0] != _IRR_TEXT('.') || path[1] != _IRR_TEXT('/') ) { result.append(L"./"); } } result.append(path); // TODO: make correct URI (without whitespaces) return result; } void CColladaMeshWriter::writeAsset() { Writer->writeElement(L"asset", false); Writer->writeLineBreak(); Writer->writeElement(L"contributor", false); Writer->writeLineBreak(); Writer->writeElement(L"authoring_tool", false); Writer->writeText(L"Irrlicht Engine / irrEdit"); // this code has originated from irrEdit 0.7 Writer->writeClosingTag(L"authoring_tool"); Writer->writeLineBreak(); Writer->writeClosingTag(L"contributor"); Writer->writeLineBreak(); // The next two are required Writer->writeElement(L"created", false); Writer->writeText(L"2008-01-31T00:00:00Z"); Writer->writeClosingTag(L"created"); Writer->writeLineBreak(); Writer->writeElement(L"modified", false); Writer->writeText(L"2008-01-31T00:00:00Z"); Writer->writeClosingTag(L"modified"); Writer->writeLineBreak(); Writer->writeElement(L"revision", false); Writer->writeText(L"1.0"); Writer->writeClosingTag(L"revision"); Writer->writeLineBreak(); Writer->writeClosingTag(L"asset"); Writer->writeLineBreak(); } void CColladaMeshWriter::writeMeshMaterials(scene::IMesh* mesh, irr::core::array * materialNamesOut) { u32 i; for (i=0; igetMeshBufferCount(); ++i) { video::SMaterial & material = mesh->getMeshBuffer(i)->getMaterial(); core::stringw strMat(nameForMaterial(material, i, mesh, NULL)); writeMaterial(strMat); if ( materialNamesOut ) materialNamesOut->push_back(strMat); } } void CColladaMeshWriter::writeMaterialEffect(const irr::core::stringw& materialfxname, const video::SMaterial & material) { if ( EffectsWritten.find(materialfxname) ) return; EffectsWritten.insert(materialfxname, true); Writer->writeElement(L"effect", false, L"id", materialfxname.c_str(), L"name", materialfxname.c_str()); Writer->writeLineBreak(); Writer->writeElement(L"profile_COMMON", false); Writer->writeLineBreak(); int numTextures = 0; if ( getWriteTextures() ) { // write texture surfaces and samplers and buffer all used imagess for ( int t=0; t<4; ++t ) { const video::SMaterialLayer& layer = material.TextureLayer[t]; if ( !layer.Texture ) break; ++numTextures; if ( LibraryImages.linear_search(layer.Texture) < 0 ) LibraryImages.push_back( layer.Texture ); irr::core::stringw texName("tex"); texName += irr::core::stringw(t); // write texture surface // irr::core::stringw texSurface(texName); texSurface += L"-surface"; Writer->writeElement(L"newparam", false, L"sid", texSurface.c_str()); Writer->writeLineBreak(); // Writer->writeElement(L"surface", false, L"type", L"2D"); Writer->writeLineBreak(); // internal_texturename Writer->writeElement(L"init_from", false); irr::io::path p(FileSystem->getRelativeFilename(layer.Texture->getName().getPath(), Directory)); Writer->writeText(toNCName(irr::core::stringw(p)).c_str()); Writer->writeClosingTag(L"init_from"); Writer->writeLineBreak(); // A8R8G8B8 Writer->writeElement(L"format", false); video::ECOLOR_FORMAT format = layer.Texture->getColorFormat(); Writer->writeText(toString(format).c_str()); Writer->writeClosingTag(L"format"); Writer->writeLineBreak(); // Writer->writeClosingTag(L"surface"); Writer->writeLineBreak(); // Writer->writeClosingTag(L"newparam"); Writer->writeLineBreak(); // write texture sampler // irr::core::stringw texSampler(texName); texSampler += L"-sampler"; Writer->writeElement(L"newparam", false, L"sid", texSampler.c_str()); Writer->writeLineBreak(); // Writer->writeElement(L"sampler2D", false); Writer->writeLineBreak(); // tex0-surface Writer->writeElement(L"source", false); Writer->writeText(texSurface.c_str()); Writer->writeClosingTag(L"source"); Writer->writeLineBreak(); // WRAP Writer->writeElement(L"wrap_s", false); Writer->writeText(toString((video::E_TEXTURE_CLAMP)layer.TextureWrapU).c_str()); Writer->writeClosingTag(L"wrap_s"); Writer->writeLineBreak(); // WRAP Writer->writeElement(L"wrap_t", false); Writer->writeText(toString((video::E_TEXTURE_CLAMP)layer.TextureWrapV).c_str()); Writer->writeClosingTag(L"wrap_t"); Writer->writeLineBreak(); // WRAP Writer->writeElement(L"wrap_p", false); Writer->writeText(toString((video::E_TEXTURE_CLAMP)layer.TextureWrapW).c_str()); Writer->writeClosingTag(L"wrap_p"); Writer->writeLineBreak(); // LINEAR_MIPMAP_LINEAR Writer->writeElement(L"minfilter", false); Writer->writeText(minTexfilterToString(layer.BilinearFilter, layer.TrilinearFilter).c_str()); Writer->writeClosingTag(L"minfilter"); Writer->writeLineBreak(); // LINEAR Writer->writeElement(L"magfilter", false); Writer->writeText(magTexfilterToString(layer.BilinearFilter, layer.TrilinearFilter).c_str()); Writer->writeClosingTag(L"magfilter"); Writer->writeLineBreak(); // TBD - actually not sure how anisotropic should be written, so for now it writes in a way // that works with the way the loader reads it again. if ( layer.AnisotropicFilter ) { // LINEAR_MIPMAP_LINEAR Writer->writeElement(L"mipfilter", false); Writer->writeText(L"LINEAR_MIPMAP_LINEAR"); Writer->writeClosingTag(L"mipfilter"); Writer->writeLineBreak(); } // Writer->writeClosingTag(L"sampler2D"); Writer->writeLineBreak(); // Writer->writeClosingTag(L"newparam"); Writer->writeLineBreak(); } } Writer->writeElement(L"technique", false, L"sid", L"common"); Writer->writeLineBreak(); E_COLLADA_TECHNIQUE_FX techFx = getProperties() ? getProperties()->getTechniqueFx(material) : ECTF_BLINN; writeFxElement(material, techFx); Writer->writeClosingTag(L"technique"); Writer->writeLineBreak(); Writer->writeClosingTag(L"profile_COMMON"); Writer->writeLineBreak(); Writer->writeClosingTag(L"effect"); Writer->writeLineBreak(); } void CColladaMeshWriter::writeMeshEffects(scene::IMesh* mesh) { for (u32 i=0; igetMeshBufferCount(); ++i) { video::SMaterial & material = mesh->getMeshBuffer(i)->getMaterial(); irr::core::stringw materialfxname(nameForMaterial(material, i, mesh, NULL)); materialfxname += L"-fx"; writeMaterialEffect(materialfxname, material); } } void CColladaMeshWriter::writeMeshGeometry(const irr::core::stringw& meshname, scene::IMesh* mesh) { core::stringw meshId(meshname); Writer->writeElement(L"geometry", false, L"id", meshId.c_str(), L"name", meshId.c_str()); Writer->writeLineBreak(); Writer->writeElement(L"mesh"); Writer->writeLineBreak(); // do some statistics for the mesh to know which stuff needs to be saved into // the file: // - count vertices // - check for the need of a second texture coordinate // - count amount of second texture coordinates // - check for the need of tangents (TODO) u32 totalVertexCount = 0; u32 totalTCoords2Count = 0; bool needsTangents = false; // TODO: tangents not supported here yet u32 i=0; for (i=0; igetMeshBufferCount(); ++i) { totalVertexCount += mesh->getMeshBuffer(i)->getVertexCount(); if (hasSecondTextureCoordinates(mesh->getMeshBuffer(i)->getVertexType())) totalTCoords2Count += mesh->getMeshBuffer(i)->getVertexCount(); if (!needsTangents) needsTangents = mesh->getMeshBuffer(i)->getVertexType() == video::EVT_TANGENTS; } SComponentGlobalStartPos* globalIndices = new SComponentGlobalStartPos[mesh->getMeshBufferCount()]; // write positions core::stringw meshPosId(meshId); meshPosId += L"-Pos"; Writer->writeElement(L"source", false, L"id", meshPosId.c_str()); Writer->writeLineBreak(); core::stringw vertexCountStr(totalVertexCount*3); core::stringw meshPosArrayId(meshPosId); meshPosArrayId += L"-array"; Writer->writeElement(L"float_array", false, L"id", meshPosArrayId.c_str(), L"count", vertexCountStr.c_str()); Writer->writeLineBreak(); for (i=0; igetMeshBufferCount(); ++i) { scene::IMeshBuffer* buffer = mesh->getMeshBuffer(i); video::E_VERTEX_TYPE vtxType = buffer->getVertexType(); u32 vertexCount = buffer->getVertexCount(); globalIndices[i].PosStartIndex = 0; if (i!=0) globalIndices[i].PosStartIndex = globalIndices[i-1].PosLastIndex + 1; globalIndices[i].PosLastIndex = globalIndices[i].PosStartIndex + vertexCount - 1; switch(vtxType) { case video::EVT_STANDARD: { video::S3DVertex* vtx = (video::S3DVertex*)buffer->getVertices(); for (u32 j=0; jwriteLineBreak(); } } break; case video::EVT_2TCOORDS: { video::S3DVertex2TCoords* vtx = (video::S3DVertex2TCoords*)buffer->getVertices(); for (u32 j=0; jwriteLineBreak(); } } break; case video::EVT_TANGENTS: { video::S3DVertexTangents* vtx = (video::S3DVertexTangents*)buffer->getVertices(); for (u32 j=0; jwriteLineBreak(); } } break; } } Writer->writeClosingTag(L"float_array"); Writer->writeLineBreak(); Writer->writeElement(L"technique_common", false); Writer->writeLineBreak(); vertexCountStr = core::stringw(totalVertexCount); Writer->writeElement(L"accessor", false, L"source", toRef(meshPosArrayId).c_str(), L"count", vertexCountStr.c_str(), L"stride", L"3"); Writer->writeLineBreak(); Writer->writeElement(L"param", true, L"name", L"X", L"type", L"float"); Writer->writeLineBreak(); Writer->writeElement(L"param", true, L"name", L"Y", L"type", L"float"); Writer->writeLineBreak(); Writer->writeElement(L"param", true, L"name", L"Z", L"type", L"float"); Writer->writeLineBreak(); Writer->writeClosingTag(L"accessor"); Writer->writeLineBreak(); Writer->writeClosingTag(L"technique_common"); Writer->writeLineBreak(); Writer->writeClosingTag(L"source"); Writer->writeLineBreak(); // write texture coordinates core::stringw meshTexCoord0Id(meshId); meshTexCoord0Id += L"-TexCoord0"; Writer->writeElement(L"source", false, L"id", meshTexCoord0Id.c_str()); Writer->writeLineBreak(); vertexCountStr = core::stringw(totalVertexCount*2); core::stringw meshTexCoordArrayId(meshTexCoord0Id); meshTexCoordArrayId += L"-array"; Writer->writeElement(L"float_array", false, L"id", meshTexCoordArrayId.c_str(), L"count", vertexCountStr.c_str()); Writer->writeLineBreak(); for (i=0; igetMeshBufferCount(); ++i) { scene::IMeshBuffer* buffer = mesh->getMeshBuffer(i); video::E_VERTEX_TYPE vtxType = buffer->getVertexType(); u32 vertexCount = buffer->getVertexCount(); globalIndices[i].TCoord0StartIndex = 0; if (i!=0) globalIndices[i].TCoord0StartIndex = globalIndices[i-1].TCoord0LastIndex + 1; globalIndices[i].TCoord0LastIndex = globalIndices[i].TCoord0StartIndex + vertexCount - 1; switch(vtxType) { case video::EVT_STANDARD: { video::S3DVertex* vtx = (video::S3DVertex*)buffer->getVertices(); for (u32 j=0; jwriteLineBreak(); } } break; case video::EVT_2TCOORDS: { video::S3DVertex2TCoords* vtx = (video::S3DVertex2TCoords*)buffer->getVertices(); for (u32 j=0; jwriteLineBreak(); } } break; case video::EVT_TANGENTS: { video::S3DVertexTangents* vtx = (video::S3DVertexTangents*)buffer->getVertices(); for (u32 j=0; jwriteLineBreak(); } } break; } } Writer->writeClosingTag(L"float_array"); Writer->writeLineBreak(); Writer->writeElement(L"technique_common", false); Writer->writeLineBreak(); vertexCountStr = core::stringw(totalVertexCount); Writer->writeElement(L"accessor", false, L"source", toRef(meshTexCoordArrayId).c_str(), L"count", vertexCountStr.c_str(), L"stride", L"2"); Writer->writeLineBreak(); Writer->writeElement(L"param", true, L"name", L"U", L"type", L"float"); Writer->writeLineBreak(); Writer->writeElement(L"param", true, L"name", L"V", L"type", L"float"); Writer->writeLineBreak(); Writer->writeClosingTag(L"accessor"); Writer->writeLineBreak(); Writer->writeClosingTag(L"technique_common"); Writer->writeLineBreak(); Writer->writeClosingTag(L"source"); Writer->writeLineBreak(); // write normals core::stringw meshNormalId(meshId); meshNormalId += L"-Normal"; Writer->writeElement(L"source", false, L"id", meshNormalId.c_str()); Writer->writeLineBreak(); vertexCountStr = core::stringw(totalVertexCount*3); core::stringw meshNormalArrayId(meshNormalId); meshNormalArrayId += L"-array"; Writer->writeElement(L"float_array", false, L"id", meshNormalArrayId.c_str(), L"count", vertexCountStr.c_str()); Writer->writeLineBreak(); for (i=0; igetMeshBufferCount(); ++i) { scene::IMeshBuffer* buffer = mesh->getMeshBuffer(i); video::E_VERTEX_TYPE vtxType = buffer->getVertexType(); u32 vertexCount = buffer->getVertexCount(); globalIndices[i].NormalStartIndex = 0; if (i!=0) globalIndices[i].NormalStartIndex = globalIndices[i-1].NormalLastIndex + 1; globalIndices[i].NormalLastIndex = globalIndices[i].NormalStartIndex + vertexCount - 1; switch(vtxType) { case video::EVT_STANDARD: { video::S3DVertex* vtx = (video::S3DVertex*)buffer->getVertices(); for (u32 j=0; jwriteLineBreak(); } } break; case video::EVT_2TCOORDS: { video::S3DVertex2TCoords* vtx = (video::S3DVertex2TCoords*)buffer->getVertices(); for (u32 j=0; jwriteLineBreak(); } } break; case video::EVT_TANGENTS: { video::S3DVertexTangents* vtx = (video::S3DVertexTangents*)buffer->getVertices(); for (u32 j=0; jwriteLineBreak(); } } break; } } Writer->writeClosingTag(L"float_array"); Writer->writeLineBreak(); Writer->writeElement(L"technique_common", false); Writer->writeLineBreak(); vertexCountStr = core::stringw(totalVertexCount); Writer->writeElement(L"accessor", false, L"source", toRef(meshNormalArrayId).c_str(), L"count", vertexCountStr.c_str(), L"stride", L"3"); Writer->writeLineBreak(); Writer->writeElement(L"param", true, L"name", L"X", L"type", L"float"); Writer->writeLineBreak(); Writer->writeElement(L"param", true, L"name", L"Y", L"type", L"float"); Writer->writeLineBreak(); Writer->writeElement(L"param", true, L"name", L"Z", L"type", L"float"); Writer->writeLineBreak(); Writer->writeClosingTag(L"accessor"); Writer->writeLineBreak(); Writer->writeClosingTag(L"technique_common"); Writer->writeLineBreak(); Writer->writeClosingTag(L"source"); Writer->writeLineBreak(); // write second set of texture coordinates core::stringw meshTexCoord1Id(meshId); meshTexCoord1Id += L"-TexCoord1"; if (totalTCoords2Count) { Writer->writeElement(L"source", false, L"id", meshTexCoord1Id.c_str()); Writer->writeLineBreak(); vertexCountStr = core::stringw(totalTCoords2Count*2); core::stringw meshTexCoord1ArrayId(meshTexCoord1Id); meshTexCoord1ArrayId += L"-array"; Writer->writeElement(L"float_array", false, L"id", meshTexCoord1ArrayId.c_str(), L"count", vertexCountStr.c_str()); Writer->writeLineBreak(); for (i=0; igetMeshBufferCount(); ++i) { scene::IMeshBuffer* buffer = mesh->getMeshBuffer(i); video::E_VERTEX_TYPE vtxType = buffer->getVertexType(); u32 vertexCount = buffer->getVertexCount(); if (hasSecondTextureCoordinates(vtxType)) { globalIndices[i].TCoord1StartIndex = 0; if (i!=0 && globalIndices[i-1].TCoord1LastIndex != -1) globalIndices[i].TCoord1StartIndex = globalIndices[i-1].TCoord1LastIndex + 1; globalIndices[i].TCoord1LastIndex = globalIndices[i].TCoord1StartIndex + vertexCount - 1; switch(vtxType) { case video::EVT_2TCOORDS: { video::S3DVertex2TCoords* vtx = (video::S3DVertex2TCoords*)buffer->getVertices(); for (u32 j=0; jwriteLineBreak(); } } break; default: break; } } // end this buffer has 2 texture coordinates } Writer->writeClosingTag(L"float_array"); Writer->writeLineBreak(); Writer->writeElement(L"technique_common", false); Writer->writeLineBreak(); vertexCountStr = core::stringw(totalTCoords2Count); Writer->writeElement(L"accessor", false, L"source", toRef(meshTexCoord1ArrayId).c_str(), L"count", vertexCountStr.c_str(), L"stride", L"2"); Writer->writeLineBreak(); Writer->writeElement(L"param", true, L"name", L"U", L"type", L"float"); Writer->writeLineBreak(); Writer->writeElement(L"param", true, L"name", L"V", L"type", L"float"); Writer->writeLineBreak(); Writer->writeClosingTag(L"accessor"); Writer->writeLineBreak(); Writer->writeClosingTag(L"technique_common"); Writer->writeLineBreak(); Writer->writeClosingTag(L"source"); Writer->writeLineBreak(); } // write tangents // TODO // write vertices core::stringw meshVtxId(meshId); meshVtxId += L"-Vtx"; Writer->writeElement(L"vertices", false, L"id", meshVtxId.c_str()); Writer->writeLineBreak(); Writer->writeElement(L"input", true, L"semantic", L"POSITION", L"source", toRef(meshPosId).c_str()); Writer->writeLineBreak(); Writer->writeClosingTag(L"vertices"); Writer->writeLineBreak(); // write polygons for (i=0; igetMeshBufferCount(); ++i) { scene::IMeshBuffer* buffer = mesh->getMeshBuffer(i); const u32 polyCount = buffer->getIndexCount() / 3; core::stringw strPolyCount(polyCount); irr::core::stringw strMat(nameForMaterialSymbol(mesh, i)); Writer->writeElement(L"triangles", false, L"count", strPolyCount.c_str(), L"material", strMat.c_str()); Writer->writeLineBreak(); Writer->writeElement(L"input", true, L"semantic", L"VERTEX", L"source", toRef(meshVtxId).c_str(), L"offset", L"0"); Writer->writeLineBreak(); Writer->writeElement(L"input", true, L"semantic", L"TEXCOORD", L"source", toRef(meshTexCoord0Id).c_str(), L"offset", L"1", L"set", L"0"); Writer->writeLineBreak(); Writer->writeElement(L"input", true, L"semantic", L"NORMAL", L"source", toRef(meshNormalId).c_str(), L"offset", L"2"); Writer->writeLineBreak(); bool has2ndTexCoords = hasSecondTextureCoordinates(buffer->getVertexType()); if (has2ndTexCoords) { // TODO: when working on second uv-set - my suspicion is that this one should be called "TEXCOORD2" // to allow bind_vertex_input to differentiate the uv-sets. Writer->writeElement(L"input", true, L"semantic", L"TEXCOORD", L"source", toRef(meshTexCoord1Id).c_str(), L"idx", L"3"); Writer->writeLineBreak(); } // write indices now s32 posIdx = globalIndices[i].PosStartIndex; s32 tCoordIdx = globalIndices[i].TCoord0StartIndex; s32 normalIdx = globalIndices[i].NormalStartIndex; s32 tCoord2Idx = globalIndices[i].TCoord1StartIndex; Writer->writeElement(L"p", false); core::stringw strP; strP.reserve(100); for (u32 p=0; pgetIndices()[(p*3) + 0] + posIdx; strP += " "; strP += buffer->getIndices()[(p*3) + 0] + tCoordIdx; strP += " "; strP += buffer->getIndices()[(p*3) + 0] + normalIdx; strP += " "; if (has2ndTexCoords) { strP += buffer->getIndices()[(p*3) + 0] + tCoord2Idx; strP += " "; } strP += buffer->getIndices()[(p*3) + 1] + posIdx; strP += " "; strP += buffer->getIndices()[(p*3) + 1] + tCoordIdx; strP += " "; strP += buffer->getIndices()[(p*3) + 1] + normalIdx; strP += " "; if (has2ndTexCoords) { strP += buffer->getIndices()[(p*3) + 1] + tCoord2Idx; strP += " "; } strP += buffer->getIndices()[(p*3) + 2] + posIdx; strP += " "; strP += buffer->getIndices()[(p*3) + 2] + tCoordIdx; strP += " "; strP += buffer->getIndices()[(p*3) + 2] + normalIdx; if (has2ndTexCoords) { strP += " "; strP += buffer->getIndices()[(p*3) + 2] + tCoord2Idx; } strP += " "; Writer->writeText(strP.c_str()); } Writer->writeClosingTag(L"p"); Writer->writeLineBreak(); // close index buffer section Writer->writeClosingTag(L"triangles"); Writer->writeLineBreak(); } // close mesh and geometry delete [] globalIndices; Writer->writeClosingTag(L"mesh"); Writer->writeLineBreak(); Writer->writeClosingTag(L"geometry"); Writer->writeLineBreak(); } void CColladaMeshWriter::writeLibraryImages() { if ( getWriteTextures() && !LibraryImages.empty() ) { Writer->writeElement(L"library_images", false); Writer->writeLineBreak(); for ( irr::u32 i=0; igetRelativeFilename(LibraryImages[i]->getName().getPath(), Directory)); // irr::core::stringw ncname( toNCName(irr::core::stringw(p)) ); Writer->writeElement(L"image", false, L"id", ncname.c_str(), L"name", ncname.c_str()); Writer->writeLineBreak(); // ../flowers/rose01.jpg Writer->writeElement(L"init_from", false); Writer->writeText(pathToURI(p).c_str()); Writer->writeClosingTag(L"init_from"); Writer->writeLineBreak(); // Writer->writeClosingTag(L"image"); Writer->writeLineBreak(); } Writer->writeClosingTag(L"library_images"); Writer->writeLineBreak(); } } void CColladaMeshWriter::writeColorElement(const video::SColorf & col, bool writeAlpha) { Writer->writeElement(L"color", false); writeColor(col, writeAlpha); Writer->writeClosingTag(L"color"); Writer->writeLineBreak(); } void CColladaMeshWriter::writeColorElement(const video::SColor & col, bool writeAlpha) { writeColorElement( video::SColorf(col), writeAlpha ); } void CColladaMeshWriter::writeAmbientLightElement(const video::SColorf & col) { Writer->writeElement(L"light", false, L"id", L"ambientlight"); Writer->writeLineBreak(); Writer->writeElement(L"technique_common", false); Writer->writeLineBreak(); Writer->writeElement(L"ambient", false); Writer->writeLineBreak(); writeColorElement(col, false); Writer->writeClosingTag(L"ambient"); Writer->writeLineBreak(); Writer->writeClosingTag(L"technique_common"); Writer->writeLineBreak(); Writer->writeClosingTag(L"light"); Writer->writeLineBreak(); } s32 CColladaMeshWriter::getCheckedTextureIdx(const video::SMaterial & material, E_COLLADA_COLOR_SAMPLER cs) { if ( !getWriteTextures() || !getProperties() ) return -1; s32 idx = getProperties()->getTextureIdx(material, cs); if ( idx >= 0 && !material.TextureLayer[idx].Texture ) return -1; return idx; } video::SColor CColladaMeshWriter::getColorMapping(const video::SMaterial & material, E_COLLADA_COLOR_SAMPLER cs, E_COLLADA_IRR_COLOR colType) { switch ( colType ) { case ECIC_NONE: return video::SColor(255, 0, 0, 0); case ECIC_CUSTOM: return getProperties()->getCustomColor(material, cs); case ECIC_DIFFUSE: return material.DiffuseColor; case ECIC_AMBIENT: return material.AmbientColor; case ECIC_EMISSIVE: return material.EmissiveColor; case ECIC_SPECULAR: return material.SpecularColor; } return video::SColor(255, 0, 0, 0); } void CColladaMeshWriter::writeTextureSampler(s32 textureIdx) { irr::core::stringw sampler(L"tex"); sampler += irr::core::stringw(textureIdx); sampler += L"-sampler"; // Writer->writeElement(L"texture", true, L"texture", sampler.c_str(), L"texcoord", L"uv" ); Writer->writeLineBreak(); } void CColladaMeshWriter::writeFxElement(const video::SMaterial & material, E_COLLADA_TECHNIQUE_FX techFx) { core::stringw fxLabel; bool writeEmission = true; bool writeAmbient = true; bool writeDiffuse = true; bool writeSpecular = true; bool writeShininess = true; bool writeReflective = true; bool writeReflectivity = true; bool writeTransparent = true; bool writeTransparency = true; bool writeIndexOfRefraction = true; switch ( techFx ) { case ECTF_BLINN: fxLabel = L"blinn"; break; case ECTF_PHONG: fxLabel = L"phong"; break; case ECTF_LAMBERT: fxLabel = L"lambert"; writeSpecular = false; writeShininess = false; break; case ECTF_CONSTANT: fxLabel = L"constant"; writeAmbient = false; writeDiffuse = false; writeSpecular = false; writeShininess = false; break; } Writer->writeElement(fxLabel.c_str(), false); Writer->writeLineBreak(); // write all interesting material parameters // attributes must be written in fixed order if ( getProperties() ) { if ( writeEmission ) { writeColorFx(material, L"emission", ECCS_EMISSIVE); } if ( writeAmbient ) { writeColorFx(material, L"ambient", ECCS_AMBIENT); } if ( writeDiffuse ) { writeColorFx(material, L"diffuse", ECCS_DIFFUSE); } if ( writeSpecular ) { writeColorFx(material, L"specular", ECCS_SPECULAR); } if ( writeShininess ) { Writer->writeElement(L"shininess", false); Writer->writeLineBreak(); writeFloatElement(material.Shininess); Writer->writeClosingTag(L"shininess"); Writer->writeLineBreak(); } if ( writeReflective ) { writeColorFx(material, L"reflective", ECCS_REFLECTIVE); } if ( writeReflectivity ) { f32 t = getProperties()->getReflectivity(material); if ( t >= 0.f ) { // 1.000000 Writer->writeElement(L"reflectivity", false); Writer->writeLineBreak(); writeFloatElement(t); Writer->writeClosingTag(L"reflectivity"); Writer->writeLineBreak(); } } if ( writeTransparent ) { E_COLLADA_TRANSPARENT_FX transparentFx = getProperties()->getTransparentFx(material); writeColorFx(material, L"transparent", ECCS_TRANSPARENT, L"opaque", toString(transparentFx).c_str()); } if ( writeTransparency ) { f32 t = getProperties()->getTransparency(material); if ( t >= 0.f ) { // 1.000000 Writer->writeElement(L"transparency", false); Writer->writeLineBreak(); writeFloatElement(t); Writer->writeClosingTag(L"transparency"); Writer->writeLineBreak(); } } if ( writeIndexOfRefraction ) { f32 t = getProperties()->getIndexOfRefraction(material); if ( t >= 0.f ) { Writer->writeElement(L"index_of_refraction", false); Writer->writeLineBreak(); writeFloatElement(t); Writer->writeClosingTag(L"index_of_refraction"); Writer->writeLineBreak(); } } } Writer->writeClosingTag(fxLabel.c_str()); Writer->writeLineBreak(); } void CColladaMeshWriter::writeColorFx(const video::SMaterial & material, const wchar_t * colorname, E_COLLADA_COLOR_SAMPLER cs, const wchar_t* attr1Name, const wchar_t* attr1Value) { irr::s32 idx = getCheckedTextureIdx(material, cs); E_COLLADA_IRR_COLOR colType = idx < 0 ? getProperties()->getColorMapping(material, cs) : ECIC_NONE; if ( idx >= 0 || colType != ECIC_NONE ) { Writer->writeElement(colorname, false, attr1Name, attr1Value); Writer->writeLineBreak(); if ( idx >= 0 ) writeTextureSampler(idx); else writeColorElement(getColorMapping(material, cs, colType)); Writer->writeClosingTag(colorname); Writer->writeLineBreak(); } } void CColladaMeshWriter::writeNode(const wchar_t * nodeName, const wchar_t * content) { Writer->writeElement(nodeName, false); Writer->writeText(content); Writer->writeClosingTag(nodeName); Writer->writeLineBreak(); } void CColladaMeshWriter::writeFloatElement(irr::f32 value) { Writer->writeElement(L"float", false); Writer->writeText(core::stringw((double)value).c_str()); Writer->writeClosingTag(L"float"); Writer->writeLineBreak(); } void CColladaMeshWriter::writeRotateElement(const irr::core::vector3df& axis, irr::f32 angle) { Writer->writeElement(L"rotate", false); irr::core::stringw txt(axis.X); txt += L" "; txt += irr::core::stringw(axis.Y); txt += L" "; txt += irr::core::stringw(axis.Z); txt += L" "; txt += irr::core::stringw((double)angle); Writer->writeText(txt.c_str()); Writer->writeClosingTag(L"rotate"); Writer->writeLineBreak(); } void CColladaMeshWriter::writeScaleElement(const irr::core::vector3df& scale) { Writer->writeElement(L"scale", false); irr::core::stringw txt(scale.X); txt += L" "; txt += irr::core::stringw(scale.Y); txt += L" "; txt += irr::core::stringw(scale.Z); Writer->writeText(txt.c_str()); Writer->writeClosingTag(L"scale"); Writer->writeLineBreak(); } void CColladaMeshWriter::writeTranslateElement(const irr::core::vector3df& translate) { Writer->writeElement(L"translate", false); irr::core::stringw txt(translate.X); txt += L" "; txt += irr::core::stringw(translate.Y); txt += L" "; txt += irr::core::stringw(translate.Z); Writer->writeText(txt.c_str()); Writer->writeClosingTag(L"translate"); Writer->writeLineBreak(); } void CColladaMeshWriter::writeLookAtElement(const irr::core::vector3df& eyePos, const irr::core::vector3df& targetPos, const irr::core::vector3df& upVector) { Writer->writeElement(L"lookat", false); wchar_t tmpbuf[255]; swprintf_irr(tmpbuf, 255, L"%f %f %f %f %f %f %f %f %f", eyePos.X, eyePos.Y, eyePos.Z, targetPos.X, targetPos.Y, targetPos.Z, upVector.X, upVector.Y, upVector.Z); Writer->writeText(tmpbuf); Writer->writeClosingTag(L"lookat"); Writer->writeLineBreak(); } void CColladaMeshWriter::writeMatrixElement(const irr::core::matrix4& matrix) { Writer->writeElement(L"matrix", false); Writer->writeLineBreak(); for ( int a=0; a<4; ++a ) { irr::core::stringw txt; for ( int b=0; b<4; ++b ) { if ( b > 0 ) txt += " "; // row-column switched compared to Irrlicht txt += irr::core::stringw(matrix[b*4+a]); } Writer->writeText(txt.c_str()); Writer->writeLineBreak(); } Writer->writeClosingTag(L"matrix"); Writer->writeLineBreak(); } } // end namespace } // end namespace #endif