Skip to content

Commit

Permalink
Add OBJ export functionality to SFCGAL
Browse files Browse the repository at this point in the history
This commit introduces a new feature to export SFCGAL geometries to OBJ format.
The implementation includes:

- New namespace SFCGAL::io::OBJ with export functions
- Support for all SFCGAL geometry types
- Multiple export methods: to stream, file, string, and C-style buffer
- Comprehensive unit tests

Key features:
1. Exports points, lines, polygons, and complex 3D geometries
2. Handles both 2D (z=0.0) and 3D geometries
3. Provides a C-compatible API for integration with other software
4. Includes error handling for invalid geometries and file operations

The OBJ export functionality allows SFCGAL users to easily convert geometries
to a widely supported 3D format, facilitating interoperability with various
3D modeling and visualization software.

Files added:
- include/SFCGAL/io/OBJ.h
- src/io/OBJ.cpp
- test/unit/SFCGAL/io/OBJTest.cpp
- test/data/objfiles directory
  • Loading branch information
lbartoletti committed Aug 21, 2024
1 parent c03c1bd commit 94f3747
Show file tree
Hide file tree
Showing 27 changed files with 713 additions and 0 deletions.
167 changes: 167 additions & 0 deletions src/io/OBJ.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#include "SFCGAL/io/OBJ.h"
#include "SFCGAL/GeometryCollection.h"
#include "SFCGAL/LineString.h"
#include "SFCGAL/MultiLineString.h"
#include "SFCGAL/MultiPoint.h"
#include "SFCGAL/MultiPolygon.h"
#include "SFCGAL/MultiSolid.h"
#include "SFCGAL/Point.h"
#include "SFCGAL/Polygon.h"
#include "SFCGAL/PolyhedralSurface.h"
#include "SFCGAL/Solid.h"
#include "SFCGAL/Triangle.h"
#include "SFCGAL/TriangulatedSurface.h"
#include <fstream>
#include <functional>
#include <sstream>
#include <vector>

namespace SFCGAL {
namespace io {
namespace OBJ {

void
save(const Geometry &geom, std::ostream &out)
{
std::vector<Point> all_points;
std::vector<std::vector<size_t>> all_faces;
std::vector<std::vector<size_t>> all_lines;
std::vector<size_t> all_points_indices;

std::function<void(const Geometry &)> process_geometry =
[&](const Geometry &g) {
switch (g.geometryTypeId()) {
case TYPE_POINT: {
const Point &p = g.as<Point>();
all_points.push_back(p);
all_points_indices.push_back(all_points.size());
break;
}
case TYPE_LINESTRING: {
const LineString &ls = g.as<LineString>();
std::vector<size_t> line;
for (size_t i = 0; i < ls.numPoints(); ++i) {
all_points.push_back(ls.pointN(i));
line.push_back(all_points.size());
}
all_lines.push_back(line);
break;
}
case TYPE_TRIANGLE: {
const Triangle &tri = g.as<Triangle>();
std::vector<size_t> face;
for (int i = 0; i < 3; ++i) {
all_points.push_back(tri.vertex(i));
face.push_back(all_points.size());
}
all_faces.push_back(face);
break;
}
case TYPE_POLYGON: {
const Polygon &poly = g.as<Polygon>();
std::vector<size_t> face;
for (size_t i = 0; i < poly.exteriorRing().numPoints() - 1; ++i) {
all_points.push_back(poly.exteriorRing().pointN(i));
face.push_back(all_points.size());
}
all_faces.push_back(face);
break;
}
case TYPE_TRIANGULATEDSURFACE: {
const TriangulatedSurface &ts = g.as<TriangulatedSurface>();
for (size_t i = 0; i < ts.numTriangles(); ++i) {
process_geometry(ts.triangleN(i));
}
break;
}
case TYPE_POLYHEDRALSURFACE: {
const PolyhedralSurface &ps = g.as<PolyhedralSurface>();
for (size_t i = 0; i < ps.numPolygons(); ++i) {
process_geometry(ps.polygonN(i));
}
break;
}
case TYPE_SOLID: {
const Solid &solid = g.as<Solid>();
process_geometry(solid.exteriorShell());
break;
}
case TYPE_MULTIPOINT:
case TYPE_MULTILINESTRING:
case TYPE_MULTIPOLYGON:
case TYPE_MULTISOLID:
case TYPE_GEOMETRYCOLLECTION: {
const GeometryCollection &gc = g.as<GeometryCollection>();
for (size_t i = 0; i < gc.numGeometries(); ++i) {
process_geometry(gc.geometryN(i));
}
break;
}
default:
throw std::runtime_error("Unsupported geometry type: " +
g.geometryType());
}
};

process_geometry(geom);

for (const auto &p : all_points) {
out << "v " << p.x() << " " << p.y() << " " << (p.is3D() ? p.z() : 0.0)
<< "\n";
}

for (size_t idx : all_points_indices) {
out << "p " << idx << "\n";
}

for (const auto &line : all_lines) {
out << "l";
for (size_t idx : line) {
out << " " << idx;
}
out << "\n";
}

for (const auto &face : all_faces) {
out << "f";
for (size_t idx : face) {
out << " " << idx;
}
out << "\n";
}
}

void
save(const Geometry &geom, const std::string &filename)
{
std::ofstream out(filename);
if (!out) {
throw std::runtime_error("Unable to open file " + filename +
" for writing.");
}
save(geom, out);
}

std::string
saveToString(const Geometry &geom)
{
std::ostringstream oss;
save(geom, oss);
return oss.str();
}

void
saveToBuffer(const Geometry &geom, char *buffer, size_t *size)
{
std::string result = saveToString(geom);
if (buffer && *size >= result.size()) {
std::copy(result.begin(), result.end(), buffer);
*size = result.size();
} else {
*size = result.size();
}
}

} // namespace OBJ
} // namespace io
} // namespace SFCGAL
66 changes: 66 additions & 0 deletions src/io/OBJ.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2024-2024, Oslandia.
// SPDX-License-Identifier: LGPL-2.0-or-later

#ifndef _SFCGAL_IO_OBJ_H_
#define _SFCGAL_IO_OBJ_H_

#include <SFCGAL/Geometry.h>
#include <ostream>
#include <string>

namespace SFCGAL {
namespace io {
namespace OBJ {

/**
* @brief Saves a geometry to an OBJ format stream.
* @ingroup io
*
* @param[in] geom The geometry to save
* @param[out] out The output stream
* @throws std::runtime_error If the geometry is invalid or unsupported
*/
void
save(const Geometry &geom, std::ostream &out);

/**
* @brief Saves a geometry to an OBJ file.
* @ingroup io
*
* @param[in] geom The geometry to save
* @param[in] filename The name of the file to save to
* @throws std::runtime_error If the file cannot be opened or the geometry is
* invalid
*/
void
save(const Geometry &geom, const std::string &filename);

/**
* @brief Saves a geometry to an OBJ format string.
* @ingroup io
*
* @param[in] geom The geometry to save
* @return The OBJ format string
* @throws std::runtime_error If the geometry is invalid or unsupported
*/
std::string
saveToString(const Geometry &geom);

/**
* @brief Saves a geometry to an OBJ format buffer (C API).
* @ingroup io
*
* @param[in] geom The geometry to save
* @param[out] buffer The buffer to write to
* @param[in,out] size On input, the size of the buffer. On output, the number
* of bytes written (or required if buffer is null)
* @throws std::runtime_error If the geometry is invalid or unsupported
*/
void
saveToBuffer(const Geometry &geom, char *buffer, size_t *size);

} // namespace OBJ
} // namespace io
} // namespace SFCGAL

#endif
6 changes: 6 additions & 0 deletions test/data/objfiles/geometrycollection.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
v 2 3 0
v 0 0 0
v 1 0 0
v 1 1 0
p 1
f 2 3 4
6 changes: 6 additions & 0 deletions test/data/objfiles/geometrycollectionz.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
v 2 3 5
v 0 0 6
v 1 0 6
v 1 1 6
p 1
f 2 3 4
4 changes: 4 additions & 0 deletions test/data/objfiles/linestring.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
v 0 0 0
v 1 1 0
v 2 2 0
l 1 2 3
4 changes: 4 additions & 0 deletions test/data/objfiles/linestringz.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
v 0 0 0
v 1 1 1
v 2 2 2
l 1 2 3
6 changes: 6 additions & 0 deletions test/data/objfiles/multilinestring.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
v 0 0 0
v 1 1 0
v 2 2 0
v 3 3 0
l 1 2
l 3 4
6 changes: 6 additions & 0 deletions test/data/objfiles/multilinestringz.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
v 0 0 0
v 1 1 1
v 2 2 2
v 3 3 3
l 1 2
l 3 4
4 changes: 4 additions & 0 deletions test/data/objfiles/multipoint.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
v 1 1 0
v 2 2 0
p 1
p 2
4 changes: 4 additions & 0 deletions test/data/objfiles/multipointz.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
v 1 1 1
v 2 2 2
p 1
p 2
10 changes: 10 additions & 0 deletions test/data/objfiles/multipolygon.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
v 0 0 0
v 1 0 0
v 1 1 0
v 0 1 0
v 2 2 0
v 3 2 0
v 3 3 0
v 2 3 0
f 1 2 3 4
f 5 6 7 8
10 changes: 10 additions & 0 deletions test/data/objfiles/multipolygonz.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
v 0 0 0
v 1 0 0
v 1 1 0
v 0 1 0
v 2 2 2
v 3 2 2
v 3 3 2
v 2 3 2
f 1 2 3 4
f 5 6 7 8
60 changes: 60 additions & 0 deletions test/data/objfiles/multisolid.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
v 0 0 0
v 0 1 0
v 1 1 0
v 1 0 0
v 0 0 0
v 1 0 0
v 1 0 0
v 0 0 0
v 0 0 0
v 1 0 0
v 1 0 0
v 0 0 0
v 1 1 0
v 0 1 0
v 0 1 0
v 1 1 0
v 1 0 0
v 1 1 0
v 1 1 0
v 1 0 0
v 0 0 0
v 0 0 0
v 0 1 0
v 0 1 0
v 2 4 0
v 2 5 0
v 3 5 0
v 3 4 0
v 2 4 0
v 3 4 0
v 3 5 0
v 2 5 0
v 2 4 0
v 3 4 0
v 3 4 0
v 2 4 0
v 3 5 0
v 2 5 0
v 2 5 0
v 3 5 0
v 3 4 0
v 3 5 0
v 3 5 0
v 3 4 0
v 2 4 0
v 2 4 0
v 2 5 0
v 2 5 0
f 1 2 3 4
f 5 6 7 8
f 9 10 11 12
f 13 14 15 16
f 17 18 19 20
f 21 22 23 24
f 25 26 27 28
f 29 30 31 32
f 33 34 35 36
f 37 38 39 40
f 41 42 43 44
f 45 46 47 48
Loading

0 comments on commit 94f3747

Please sign in to comment.