Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export lights to dae #228

Merged
merged 6 commits into from
Jul 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
ahcorde marked this conversation as resolved.
Show resolved Hide resolved
#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
ahcorde marked this conversation as resolved.
Show resolved Hide resolved
{
/// \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;
mjcarroll marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I commented on gazebosim/gz-sim#912 (comment), we should use the light's full math::Pose3d and apply a direction on top of that.

Now that this has been released, we can't extend this struct though. That's why we prefer full classes with getters and setters using the PIMPL pattern; they involve a lot more boilerplate, but at least they can be extended without breaking ABI.

I think the solution will have to be calculating the direction field based on the light's pose orientation together with the light's direction.

/// \brief Light position (non directional lights only)

For directional lights, the position doesn't affect how they light up the scene, but it's often useful to set so we choose where the light's visual representation shows up on the GUI.

Copy link
Contributor Author

@ddengster ddengster Aug 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delayed reply, was pushing some deadlines.

Now that this has been released, we can't extend this struct though.

If I may ask, why so? Is there some technical reason?

This struct is specifically used for collada exporting - I tried to make it stick as close as possible to collada specifications. Most notably their <light> tag is omitting information on all orientations and direction - meaning that I have to push the transform data to it's parent node that instantiates the light under <node>.

We can use math::Pose3d but it's orientation component has it's members set to private as seen here, and in addition there's another problem where the collada specification's <rotate> tag is not be the same as a quaternion.

Reference https://www.khronos.org/files/collada_spec_1_4.pdf, search for <light>

This does mean if anyone decides to use it for something else they are taking on certain risks. It's something I would expect not many users to be using.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I may ask, why so? Is there some technical reason?

ABI compatibility 😕


I think you may have solved the issue in gazebosim/gz-sim#912 (comment)


/// \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,
mjcarroll marked this conversation as resolved.
Show resolved Hide resolved
const std::string &_filename, bool _exportTextures,
const std::vector<math::Matrix4d> &_submeshToMatrix,
chapulina marked this conversation as resolved.
Show resolved Hide resolved
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());
ahcorde marked this conversation as resolved.
Show resolved Hide resolved
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