Skip to content

Commit

Permalink
Export lights to dae (#228)
Browse files Browse the repository at this point in the history
* Exports lights for the collada world exporter. Based off the collada 1.4 spec
* Test added for collada exporter exporting lights

Signed-off-by: ddengster <ed.fan@osrfoundation.org>
  • Loading branch information
ddengster authored Jul 19, 2021
1 parent d739215 commit d1f45f4
Show file tree
Hide file tree
Showing 3 changed files with 319 additions and 5 deletions.
54 changes: 54 additions & 0 deletions graphics/include/ignition/common/ColladaExporter.hh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <ignition/common/MeshExporter.hh>
#include <ignition/common/graphics/Export.hh>

#include <ignition/math/Color.hh>
#include <ignition/math/Matrix4.hh>

#include <ignition/utils/ImplPtr.hh>
Expand All @@ -32,6 +33,41 @@ namespace ignition
{
namespace common
{
/// \brief This struct contains light data specifically for collada export
/// Defaults set based on collada 1.4 specifications
struct ColladaLight
{
/// \brief Name of the light
std::string name;

/// \brief Type of the light. Either "point", "directional" or "spot"
std::string type;

/// \brief Light direction (directional/spot lights only)
math::Vector3d direction;

/// \brief Light position (non directional lights only)
math::Vector3d position;

/// \brief Light diffuse color
math::Color diffuse;

/// \brief Constant attentuation
double constantAttenuation = 1.0;

/// \brief Linear attentuation
double linearAttenuation = 0.0;

/// \brief Quadratic attentuation
double quadraticAttenuation = 0.0;

/// \brief Falloff angle in degrees
double falloffAngleDeg = 180.0;

/// \brief Fallof exponent
double falloffExponent = 0.0;
};

/// \brief Class used to export Collada mesh files
class IGNITION_COMMON_GRAPHICS_VISIBLE ColladaExporter : public MeshExporter
{
Expand All @@ -49,10 +85,28 @@ namespace ignition
public: virtual void Export(const Mesh *_mesh,
const std::string &_filename, bool _exportTextures = false);

/// \brief Export a mesh to a file
/// \param[in] _mesh Pointer to the mesh to be exported
/// \param[in] _filename Exported file's path and name
/// \param[in] _exportTextures True to export texture images to
/// '../materials/textures' folder
/// \param[in] _submeshToMatrix Matrices of submeshes
public: void Export(const Mesh *_mesh,
const std::string &_filename, bool _exportTextures,
const std::vector<math::Matrix4d> &_submeshToMatrix);

/// \brief Export a mesh to a file
/// \param[in] _mesh Pointer to the mesh to be exported
/// \param[in] _filename Exported file's path and name
/// \param[in] _exportTextures True to export texture images to
/// '../materials/textures' folder
/// \param[in] _submeshToMatrix Matrices of submeshes
/// \param[in] _lights List of lights to export
public: void Export(const Mesh *_mesh,
const std::string &_filename, bool _exportTextures,
const std::vector<math::Matrix4d> &_submeshToMatrix,
const std::vector<ColladaLight> &_lights);

/// \brief Pointer to private data.
IGN_UTILS_IMPL_PTR(dataPtr)
};
Expand Down
141 changes: 136 additions & 5 deletions graphics/src/ColladaExporter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ class ignition::common::ColladaExporter::Implementation
/// scenes XML instance
public: void ExportVisualScenes(
tinyxml2::XMLElement *_libraryVisualScenesXml,
const std::vector<math::Matrix4d> &_submeshToMatrix);
const std::vector<math::Matrix4d> &_submeshToMatrix,
const std::vector<ColladaLight> &_lights);

/// \brief Export scene element
/// \param[in] _sceneXml Pointer to the scene XML instance
Expand Down Expand Up @@ -164,12 +165,24 @@ void ColladaExporter::Export(const Mesh *_mesh, const std::string &_filename,
bool _exportTextures)
{
std::vector<math::Matrix4d> empty;
this->Export(_mesh, _filename, _exportTextures, empty);
std::vector<ColladaLight> empty_lights;
this->Export(_mesh, _filename, _exportTextures, empty, empty_lights);
}

//////////////////////////////////////////////////
void ColladaExporter::Export(const Mesh *_mesh,
const std::string &_filename, bool _exportTextures,
const std::vector<math::Matrix4d> &_submeshToMatrix)
{
std::vector<ColladaLight> empty_lights;
this->Export(_mesh, _filename, _exportTextures,
_submeshToMatrix, empty_lights);
}

//////////////////////////////////////////////////
void ColladaExporter::Export(const Mesh *_mesh, const std::string &_filename,
bool _exportTextures, const std::vector<math::Matrix4d> &_submeshToMatrix)
bool _exportTextures, const std::vector<math::Matrix4d> &_submeshToMatrix,
const std::vector<ColladaLight> &_lights)
{
if ( _submeshToMatrix.size() > 0 &&
(_mesh->SubMeshCount() != _submeshToMatrix.size()) )
Expand Down Expand Up @@ -239,10 +252,75 @@ void ColladaExporter::Export(const Mesh *_mesh, const std::string &_filename,
colladaXml->LinkEndChild(libraryEffectsXml);
}

tinyxml2::XMLElement *libraryLightsXml =
xmlDoc.NewElement("library_lights");
for (const auto& light : _lights)
{
tinyxml2::XMLElement *lightXml =
xmlDoc.NewElement("light");
lightXml->SetAttribute("name", light.name.c_str());
lightXml->SetAttribute("id", light.name.c_str());
libraryLightsXml->LinkEndChild(lightXml);

tinyxml2::XMLElement *techniqueCommonXml =
xmlDoc.NewElement("technique_common");
lightXml->LinkEndChild(techniqueCommonXml);

tinyxml2::XMLElement *lightTypeXml =
xmlDoc.NewElement(light.type.c_str());
techniqueCommonXml->LinkEndChild(lightTypeXml);

// color
tinyxml2::XMLElement *colorXml =
xmlDoc.NewElement("color");
char color_str[100] = { 0 };
snprintf(color_str, sizeof(color_str), "%g %g %g",
light.diffuse.R(), light.diffuse.G(), light.diffuse.B());
colorXml->SetText(color_str);
lightTypeXml->LinkEndChild(colorXml);

// attenuations
if (light.type == "point" || light.type == "spot")
{
auto attenuation_tag = [&](const char* tagname, double value)
{
tinyxml2::XMLElement *attenXml =
xmlDoc.NewElement(tagname);

char str[100] = { 0 };
snprintf(str, sizeof(str), "%g", value);
attenXml->SetText(str);
lightTypeXml->LinkEndChild(attenXml);
};
attenuation_tag("constant_attenuation", light.constantAttenuation);
attenuation_tag("linear_attenuation", light.linearAttenuation);
attenuation_tag("quadratic_attenuation", light.quadraticAttenuation);
}

// falloff
if (light.type == "spot")
{
tinyxml2::XMLElement *falloffAngleXml =
xmlDoc.NewElement("falloff_angle");
char str[100] = { 0 };
snprintf(str, sizeof(str), "%g", light.falloffAngleDeg);
falloffAngleXml->SetText(str);
lightTypeXml->LinkEndChild(falloffAngleXml);

tinyxml2::XMLElement *falloffExpoXml =
xmlDoc.NewElement("falloff_exponent");
snprintf(str, sizeof(str), "%g", light.falloffExponent);
falloffExpoXml->SetText(str);
lightTypeXml->LinkEndChild(falloffExpoXml);
}
}
colladaXml->LinkEndChild(libraryLightsXml);

// Library visual scenes element
tinyxml2::XMLElement *libraryVisualScenesXml =
xmlDoc.NewElement("library_visual_scenes");
this->dataPtr->ExportVisualScenes(libraryVisualScenesXml, _submeshToMatrix);
this->dataPtr->ExportVisualScenes(libraryVisualScenesXml,
_submeshToMatrix, _lights);
colladaXml->LinkEndChild(libraryVisualScenesXml);

// Scene element
Expand Down Expand Up @@ -765,7 +843,8 @@ void ColladaExporter::Implementation::ExportEffects(
//////////////////////////////////////////////////
void ColladaExporter::Implementation::ExportVisualScenes(
tinyxml2::XMLElement *_libraryVisualScenesXml,
const std::vector<math::Matrix4d> &_submeshToMatrix)
const std::vector<math::Matrix4d> &_submeshToMatrix,
const std::vector<ColladaLight> &_lights)
{
tinyxml2::XMLElement *visualSceneXml =
_libraryVisualScenesXml->GetDocument()->NewElement("visual_scene");
Expand Down Expand Up @@ -848,6 +927,58 @@ void ColladaExporter::Implementation::ExportVisualScenes(
}
}
}

for (const auto& light : _lights)
{
tinyxml2::XMLElement *nodeXml =
_libraryVisualScenesXml->GetDocument()->NewElement("node");
visualSceneXml->LinkEndChild(nodeXml);

nodeXml->SetAttribute("name", light.name.c_str());

tinyxml2::XMLElement *lightInstXml =
_libraryVisualScenesXml->GetDocument()->NewElement("instance_light");
char lightId[100] = { 0 };
snprintf(lightId, sizeof(lightId), "#%s", light.name.c_str());
lightInstXml->SetAttribute("url", lightId);
nodeXml->LinkEndChild(lightInstXml);

const auto& position = light.position;
// translation
{
tinyxml2::XMLElement *translateXml =
_libraryVisualScenesXml->GetDocument()->NewElement("translate");
translateXml->SetAttribute("sid", "translate");

char tx_value[100] = { 0 };
snprintf(tx_value, sizeof(tx_value), "%f %f %f",
position.X(), position.Y(), position.Z());
translateXml->SetText(tx_value);
nodeXml->LinkEndChild(translateXml);
}

// rotation; blender starts off with light direction (0,0,-1),
// we output an axis-angle rotation to our desired direction
{
auto lightdir_norm = light.direction.Normalized();
math::Vector3d initial_dir(0, 0, -1);
math::Quaterniond q;
q.From2Axes(initial_dir, lightdir_norm);

math::Vector3d axis;
double angle = 0.0;
q.ToAxis(axis, angle);

tinyxml2::XMLElement *rotateXml =
_libraryVisualScenesXml->GetDocument()->NewElement("rotate");

char str[100] = { 0 };
snprintf(str, sizeof(str), "%g %g %g %g",
axis.X(), axis.Y(), axis.Z(), angle / IGN_PI * 180.0);
rotateXml->SetText(str);
nodeXml->LinkEndChild(rotateXml);
}
}
}

//////////////////////////////////////////////////
Expand Down
129 changes: 129 additions & 0 deletions graphics/src/ColladaExporter_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,135 @@ TEST_F(ColladaExporter, ExportMeshWithSubmeshes)
meshReloaded->TexCoordCount());
}

TEST_F(ColladaExporter, ExportLights)
{
const auto filenameIn = common::testing::TestFile("data", "box.dae");
const auto filenameOut = common::joinPaths(this->pathOut,
"box_with_lights_exported");
const auto filenameOutExt = filenameOut + ".dae";

// Load original mesh
common::ColladaLoader loader;
const common::Mesh *meshOriginal = loader.Load(filenameIn);

// Export with extension
common::ColladaExporter exporter;
std::vector<math::Matrix4d> submesh_mtx;
std::vector<common::ColladaLight> lights;

// add some lights
{
common::ColladaLight directional;
directional.name = "sun";
directional.type = "directional";
directional.direction = math::Vector3d(0, 1, -1);
directional.position = math::Vector3d(0, 0, 0);
directional.diffuse = math::Color(1, 0.5, 1);
lights.push_back(directional);

common::ColladaLight point;
point.name = "lamp";
point.type = "point";
point.position = math::Vector3d(0, 0, 10);
point.diffuse = math::Color(1, 0.5, 1);
point.constantAttenuation = 0.8;
point.linearAttenuation = 0.8;
point.quadraticAttenuation = 0.1;
lights.push_back(point);

common::ColladaLight spot;
spot.name = "torch";
spot.type = "spot";
spot.position = math::Vector3d(0, 10, 10);
spot.diffuse = math::Color(1, 0.5, 1);
spot.constantAttenuation = 0.8;
spot.linearAttenuation = 0.8;
spot.quadraticAttenuation = 0.1;
spot.falloffAngleDeg = 90.0;
spot.falloffExponent = 0.125;
lights.push_back(spot);
}

exporter.Export(meshOriginal, filenameOut, false, submesh_mtx, lights);

tinyxml2::XMLDocument xmlDoc;
ASSERT_EQ(xmlDoc.LoadFile(filenameOutExt.c_str()), tinyxml2::XML_SUCCESS);

tinyxml2::XMLElement* collada = xmlDoc.FirstChildElement("COLLADA");
ASSERT_TRUE(xmlDoc.FirstChildElement("COLLADA") != nullptr);

auto lib_lights = collada->FirstChildElement("library_lights");
EXPECT_TRUE(lib_lights);

int light_count = 0;
auto light_ele = lib_lights->FirstChildElement("light");
for (; light_ele != nullptr; light_ele = light_ele->NextSiblingElement())
{
const char* light_name_cstr = light_ele->Attribute("name");
ASSERT_TRUE(light_name_cstr);
std::string light_name = light_name_cstr;

auto technique = light_ele->FirstChildElement("technique_common");
EXPECT_TRUE(technique);

if (light_name == "sun")
{
auto directional = technique->FirstChildElement("directional");
EXPECT_TRUE(directional);
EXPECT_TRUE(directional->FirstChildElement("color"));
}
else if (light_name == "lamp")
{
auto point = technique->FirstChildElement("point");
EXPECT_TRUE(point);
EXPECT_TRUE(point->FirstChildElement("color"));
EXPECT_TRUE(point->FirstChildElement("constant_attenuation"));
EXPECT_TRUE(point->FirstChildElement("linear_attenuation"));
EXPECT_TRUE(point->FirstChildElement("quadratic_attenuation"));
}
else if (light_name == "torch")
{
auto spot = technique->FirstChildElement("spot");
EXPECT_TRUE(spot);
EXPECT_TRUE(spot->FirstChildElement("color"));
EXPECT_TRUE(spot->FirstChildElement("constant_attenuation"));
EXPECT_TRUE(spot->FirstChildElement("linear_attenuation"));
EXPECT_TRUE(spot->FirstChildElement("quadratic_attenuation"));
EXPECT_TRUE(spot->FirstChildElement("falloff_angle"));
EXPECT_TRUE(spot->FirstChildElement("falloff_exponent"));
}
else
ASSERT_TRUE(0); // Invalid light name given

++light_count;
}
EXPECT_EQ(light_count, 3);

// instantiation
auto lib_visual_scenes = collada->FirstChildElement("library_visual_scenes");
EXPECT_TRUE(lib_visual_scenes);
auto scene = lib_visual_scenes->FirstChildElement("visual_scene");
EXPECT_TRUE(scene);

int node_with_light_count = 0;
auto node_ele = scene->FirstChildElement("node");
for (; node_ele != nullptr; node_ele = node_ele->NextSiblingElement())
{
const char* node_name_cstr = node_ele->Attribute("name");
ASSERT_TRUE(node_name_cstr);
std::string node_name = node_name_cstr;
if (node_name == "sun" || node_name == "lamp" || node_name == "torch")
{
EXPECT_TRUE(node_ele->FirstChildElement("instance_light"));
EXPECT_TRUE(node_ele->FirstChildElement("translate"));
EXPECT_TRUE(node_ele->FirstChildElement("rotate"));

++node_with_light_count;
}
}
EXPECT_EQ(node_with_light_count, 3);
}

/////////////////////////////////////////////////
int main(int argc, char **argv)
{
Expand Down

0 comments on commit d1f45f4

Please sign in to comment.