Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/io/wavefront_obj')
-rw-r--r--source/blender/io/wavefront_obj/CMakeLists.txt4
-rw-r--r--source/blender/io/wavefront_obj/IO_wavefront_obj.h39
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc73
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh17
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_io.hh6
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc15
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh7
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc6
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc7
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh2
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_exporter.cc13
-rw-r--r--source/blender/io/wavefront_obj/importer/importer_mesh_utils.cc9
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc367
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh2
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_mesh.cc67
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_mesh.hh7
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_mtl.cc3
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_objects.hh5
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc100
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh82
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_importer.cc1
-rw-r--r--source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc83
-rw-r--r--source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh7
-rw-r--r--source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc131
-rw-r--r--source/blender/io/wavefront_obj/tests/obj_importer_tests.cc141
25 files changed, 906 insertions, 288 deletions
diff --git a/source/blender/io/wavefront_obj/CMakeLists.txt b/source/blender/io/wavefront_obj/CMakeLists.txt
index e0fe7ed4992..f7958ef4ec6 100644
--- a/source/blender/io/wavefront_obj/CMakeLists.txt
+++ b/source/blender/io/wavefront_obj/CMakeLists.txt
@@ -15,6 +15,7 @@ set(INC
../../makesrna
../../nodes
../../windowmanager
+ ../../../../extern/fast_float
../../../../extern/fmtlib/include
../../../../intern/guardedalloc
)
@@ -35,6 +36,7 @@ set(SRC
importer/obj_import_mesh.cc
importer/obj_import_mtl.cc
importer/obj_import_nurbs.cc
+ importer/obj_import_string_utils.cc
importer/obj_importer.cc
IO_wavefront_obj.h
@@ -50,6 +52,7 @@ set(SRC
importer/obj_import_mtl.hh
importer/obj_import_nurbs.hh
importer/obj_import_objects.hh
+ importer/obj_import_string_utils.hh
importer/obj_importer.hh
)
@@ -69,6 +72,7 @@ blender_add_lib(bf_wavefront_obj "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
set(TEST_SRC
tests/obj_exporter_tests.cc
+ tests/obj_import_string_utils_tests.cc
tests/obj_importer_tests.cc
tests/obj_mtl_parser_tests.cc
diff --git a/source/blender/io/wavefront_obj/IO_wavefront_obj.h b/source/blender/io/wavefront_obj/IO_wavefront_obj.h
index 8b71ec750c0..b4a00deb99c 100644
--- a/source/blender/io/wavefront_obj/IO_wavefront_obj.h
+++ b/source/blender/io/wavefront_obj/IO_wavefront_obj.h
@@ -9,34 +9,20 @@
#include "BKE_context.h"
#include "BLI_path_util.h"
#include "DEG_depsgraph.h"
+#include "IO_orientation.h"
+#include "IO_path_util_types.h"
#ifdef __cplusplus
extern "C" {
#endif
-typedef enum {
- OBJ_AXIS_X_UP = 0,
- OBJ_AXIS_Y_UP = 1,
- OBJ_AXIS_Z_UP = 2,
- OBJ_AXIS_NEGATIVE_X_UP = 3,
- OBJ_AXIS_NEGATIVE_Y_UP = 4,
- OBJ_AXIS_NEGATIVE_Z_UP = 5,
-} eTransformAxisUp;
-
-typedef enum {
- OBJ_AXIS_X_FORWARD = 0,
- OBJ_AXIS_Y_FORWARD = 1,
- OBJ_AXIS_Z_FORWARD = 2,
- OBJ_AXIS_NEGATIVE_X_FORWARD = 3,
- OBJ_AXIS_NEGATIVE_Y_FORWARD = 4,
- OBJ_AXIS_NEGATIVE_Z_FORWARD = 5,
-} eTransformAxisForward;
-
static const int TOTAL_AXES = 3;
struct OBJExportParams {
/** Full path to the destination .OBJ file. */
char filepath[FILE_MAX];
+ /** Pretend that destination file folder is this, if non-empty. Used only for tests. */
+ char file_base_for_tests[FILE_MAX];
/** Full path to current blender file (used for comments in output). */
const char *blen_filepath;
@@ -49,8 +35,8 @@ struct OBJExportParams {
int end_frame;
/* Geometry Transform options. */
- eTransformAxisForward forward_axis;
- eTransformAxisUp up_axis;
+ eIOAxis forward_axis;
+ eIOAxis up_axis;
float scaling_factor;
/* File Write Options. */
@@ -59,9 +45,11 @@ struct OBJExportParams {
eEvaluationMode export_eval_mode;
bool export_uv;
bool export_normals;
+ bool export_colors;
bool export_materials;
bool export_triangulated_mesh;
bool export_curves_as_nurbs;
+ ePathReferenceMode path_mode;
/* Grouping options. */
bool export_object_groups;
@@ -82,18 +70,21 @@ struct OBJImportParams {
char filepath[FILE_MAX];
/** Value 0 disables clamping. */
float clamp_size;
- eTransformAxisForward forward_axis;
- eTransformAxisUp up_axis;
+ eIOAxis forward_axis;
+ eIOAxis up_axis;
+ bool import_vertex_groups;
bool validate_meshes;
};
/**
- * Time the full import process.
+ * Perform the full import process.
+ * Import also changes the selection & the active object; callers
+ * need to update the UI bits if needed.
*/
void OBJ_import(bContext *C, const struct OBJImportParams *import_params);
/**
- * C-interface for the exporter.
+ * Perform the full export process.
*/
void OBJ_export(bContext *C, const struct OBJExportParams *export_params);
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc
index 194583e71fe..cb95c561547 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc
@@ -8,11 +8,15 @@
#include <cstdio>
#include "BKE_blender_version.h"
+#include "BKE_geometry_set.hh"
+#include "BLI_color.hh"
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_path_util.h"
#include "BLI_task.hh"
+#include "IO_path_util.hh"
+
#include "obj_export_mesh.hh"
#include "obj_export_mtl.hh"
#include "obj_export_nurbs.hh"
@@ -239,13 +243,38 @@ void obj_parallel_chunked_output(FormatHandler<eFileType::OBJ> &fh,
}
void OBJWriter::write_vertex_coords(FormatHandler<eFileType::OBJ> &fh,
- const OBJMesh &obj_mesh_data) const
+ const OBJMesh &obj_mesh_data,
+ bool write_colors) const
{
const int tot_count = obj_mesh_data.tot_vertices();
- obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) {
- float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor);
- buf.write<eOBJSyntaxElement::vertex_coords>(vertex[0], vertex[1], vertex[2]);
- });
+
+ Mesh *mesh = obj_mesh_data.get_mesh();
+ CustomDataLayer *colors_layer = nullptr;
+ if (write_colors) {
+ colors_layer = BKE_id_attributes_active_color_get(&mesh->id);
+ }
+ if (write_colors && (colors_layer != nullptr)) {
+ MeshComponent component;
+ component.replace(mesh, GeometryOwnershipType::ReadOnly);
+ VArray<ColorGeometry4f> attribute = component.attribute_get_for_read<ColorGeometry4f>(
+ colors_layer->name, ATTR_DOMAIN_POINT, {0.0f, 0.0f, 0.0f, 0.0f});
+
+ BLI_assert(tot_count == attribute.size());
+ obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) {
+ float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor);
+ ColorGeometry4f linear = attribute.get(i);
+ float srgb[3];
+ linearrgb_to_srgb_v3_v3(srgb, linear);
+ buf.write<eOBJSyntaxElement::vertex_coords_color>(
+ vertex[0], vertex[1], vertex[2], srgb[0], srgb[1], srgb[2]);
+ });
+ }
+ else {
+ obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) {
+ float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor);
+ buf.write<eOBJSyntaxElement::vertex_coords>(vertex[0], vertex[1], vertex[2]);
+ });
+ }
}
void OBJWriter::write_uv_coords(FormatHandler<eFileType::OBJ> &fh, OBJMesh &r_obj_mesh_data) const
@@ -383,7 +412,7 @@ void OBJWriter::write_edges_indices(FormatHandler<eFileType::OBJ> &fh,
const IndexOffsets &offsets,
const OBJMesh &obj_mesh_data) const
{
- /* Note: ensure_mesh_edges should be called before. */
+ /* NOTE: ensure_mesh_edges should be called before. */
const int tot_edges = obj_mesh_data.tot_edges();
for (int edge_index = 0; edge_index < tot_edges; edge_index++) {
const std::optional<std::array<int, 2>> vertex_indices =
@@ -530,7 +559,11 @@ void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl_material)
void MTLWriter::write_texture_map(
const MTLMaterial &mtl_material,
- const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map)
+ const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map,
+ const char *blen_filedir,
+ const char *dest_dir,
+ ePathReferenceMode path_mode,
+ Set<std::pair<std::string, std::string>> &copy_set)
{
std::string options;
/* Option strings should have their own leading spaces. */
@@ -546,7 +579,11 @@ void MTLWriter::write_texture_map(
#define SYNTAX_DISPATCH(eMTLSyntaxElement) \
if (texture_map.key == eMTLSyntaxElement) { \
- fmt_handler_.write<eMTLSyntaxElement>(options, texture_map.value.image_path); \
+ std::string path = path_reference( \
+ texture_map.value.image_path.c_str(), blen_filedir, dest_dir, path_mode, &copy_set); \
+ /* Always emit forward slashes for cross-platform compatibility. */ \
+ std::replace(path.begin(), path.end(), '\\', '/'); \
+ fmt_handler_.write<eMTLSyntaxElement>(options, path.c_str()); \
return; \
}
@@ -561,25 +598,35 @@ void MTLWriter::write_texture_map(
BLI_assert(!"This map type was not written to the file.");
}
-void MTLWriter::write_materials()
+void MTLWriter::write_materials(const char *blen_filepath,
+ ePathReferenceMode path_mode,
+ const char *dest_dir)
{
if (mtlmaterials_.size() == 0) {
return;
}
+
+ char blen_filedir[PATH_MAX];
+ BLI_split_dir_part(blen_filepath, blen_filedir, PATH_MAX);
+ BLI_path_slash_native(blen_filedir);
+ BLI_path_normalize(nullptr, blen_filedir);
+
std::sort(mtlmaterials_.begin(),
mtlmaterials_.end(),
[](const MTLMaterial &a, const MTLMaterial &b) { return a.name < b.name; });
+ Set<std::pair<std::string, std::string>> copy_set;
for (const MTLMaterial &mtlmat : mtlmaterials_) {
fmt_handler_.write<eMTLSyntaxElement::string>("\n");
fmt_handler_.write<eMTLSyntaxElement::newmtl>(mtlmat.name);
write_bsdf_properties(mtlmat);
- for (const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map :
- mtlmat.texture_maps.items()) {
- if (!texture_map.value.image_path.empty()) {
- write_texture_map(mtlmat, texture_map);
+ for (const auto &tex : mtlmat.texture_maps.items()) {
+ if (tex.value.image_path.empty()) {
+ continue;
}
+ write_texture_map(mtlmat, tex, blen_filedir, dest_dir, path_mode, copy_set);
}
}
+ path_reference_copy(copy_set);
}
Vector<int> MTLWriter::add_materials(const OBJMesh &mesh_to_export)
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh
index 96f7d434338..97c23484426 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh
@@ -9,6 +9,7 @@
#include "DNA_meshdata_types.h"
#include "BLI_map.hh"
+#include "BLI_set.hh"
#include "BLI_vector.hh"
#include "IO_wavefront_obj.h"
@@ -71,9 +72,11 @@ class OBJWriter : NonMovable, NonCopyable {
*/
void write_mtllib_name(const StringRefNull mtl_filepath) const;
/**
- * Write vertex coordinates for all vertices as "v x y z".
+ * Write vertex coordinates for all vertices as "v x y z" or "v x y z r g b".
*/
- void write_vertex_coords(FormatHandler<eFileType::OBJ> &fh, const OBJMesh &obj_mesh_data) const;
+ void write_vertex_coords(FormatHandler<eFileType::OBJ> &fh,
+ const OBJMesh &obj_mesh_data,
+ bool write_colors) const;
/**
* Write UV vertex coordinates for all vertices as `vt u v`.
* \note UV indices are stored here, but written with polygons later.
@@ -181,7 +184,9 @@ class MTLWriter : NonMovable, NonCopyable {
* For consistency of output from run to run (useful for testing),
* the materials are sorted by name before writing.
*/
- void write_materials();
+ void write_materials(const char *blen_filepath,
+ ePathReferenceMode path_mode,
+ const char *dest_dir);
StringRefNull mtl_file_path() const;
/**
* Add the materials of the given object to #MTLWriter, de-duplicating
@@ -203,6 +208,10 @@ class MTLWriter : NonMovable, NonCopyable {
* Write a texture map in the form "map_XX -s 1. 1. 1. -o 0. 0. 0. [-bm 1.] path/to/image".
*/
void write_texture_map(const MTLMaterial &mtl_material,
- const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map);
+ const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map,
+ const char *blen_filedir,
+ const char *dest_dir,
+ ePathReferenceMode mode,
+ Set<std::pair<std::string, std::string>> &copy_set);
};
} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh
index 0d7c089ec14..5413c9969e3 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh
@@ -30,6 +30,7 @@ enum class eFileType {
enum class eOBJSyntaxElement {
vertex_coords,
+ vertex_coords_color,
uv_vertex_coords,
normal,
poly_element_begin,
@@ -130,6 +131,9 @@ constexpr FormattingSyntax syntax_elem_to_formatting(const eOBJSyntaxElement key
case eOBJSyntaxElement::vertex_coords: {
return {"v {:.6f} {:.6f} {:.6f}\n", 3, is_type_float<T...>};
}
+ case eOBJSyntaxElement::vertex_coords_color: {
+ return {"v {:.6f} {:.6f} {:.6f} {:.4f} {:.4f} {:.4f}\n", 6, is_type_float<T...>};
+ }
case eOBJSyntaxElement::uv_vertex_coords: {
return {"vt {:.6f} {:.6f}\n", 2, is_type_float<T...>};
}
@@ -236,7 +240,7 @@ constexpr FormattingSyntax syntax_elem_to_formatting(const eMTLSyntaxElement key
case eMTLSyntaxElement::Ke: {
return {"Ke {:.6f} {:.6f} {:.6f}\n", 3, is_type_float<T...>};
}
- /* Note: first texture map related argument, if present, will have its own leading space. */
+ /* NOTE: first texture map related argument, if present, will have its own leading space. */
case eMTLSyntaxElement::map_Kd: {
return {"map_Kd{} {}\n", 2, is_type_string_related<T...>};
}
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc
index c2a9e0574eb..e2ecda32717 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc
@@ -117,15 +117,12 @@ std::pair<Mesh *, bool> OBJMesh::triangulate_mesh_eval()
return {triangulated, true};
}
-void OBJMesh::set_world_axes_transform(const eTransformAxisForward forward,
- const eTransformAxisUp up)
+void OBJMesh::set_world_axes_transform(const eIOAxis forward, const eIOAxis up)
{
float axes_transform[3][3];
unit_m3(axes_transform);
/* +Y-forward and +Z-up are the default Blender axis settings. */
- mat3_from_axis_conversion(OBJ_AXIS_Y_FORWARD, OBJ_AXIS_Z_UP, forward, up, axes_transform);
- /* mat3_from_axis_conversion returns a transposed matrix! */
- transpose_m3(axes_transform);
+ mat3_from_axis_conversion(forward, up, IO_AXIS_Y, IO_AXIS_Z, axes_transform);
mul_m4_m3m4(world_and_axes_transform_, axes_transform, export_object_eval_.obmat);
/* mul_m4_m3m4 does not transform last row of obmat, i.e. location data. */
mul_v3_m3v3(world_and_axes_transform_[3], axes_transform, export_object_eval_.obmat[3]);
@@ -290,7 +287,7 @@ void OBJMesh::store_uv_coords_and_indices()
const MLoop *mloop = export_mesh_eval_->mloop;
const int totpoly = export_mesh_eval_->totpoly;
const int totvert = export_mesh_eval_->totvert;
- const MLoopUV *mloopuv = static_cast<MLoopUV *>(
+ const MLoopUV *mloopuv = static_cast<const MLoopUV *>(
CustomData_get_layer(&export_mesh_eval_->ldata, CD_MLOOPUV));
if (!mloopuv) {
tot_uv_vertices_ = 0;
@@ -382,8 +379,8 @@ void OBJMesh::store_normal_coords_and_indices()
normal_to_index.reserve(export_mesh_eval_->totpoly);
loop_to_normal_index_.resize(export_mesh_eval_->totloop);
loop_to_normal_index_.fill(-1);
- const float(
- *lnors)[3] = (const float(*)[3])(CustomData_get_layer(&export_mesh_eval_->ldata, CD_NORMAL));
+ const float(*lnors)[3] = static_cast<const float(*)[3]>(
+ CustomData_get_layer(&export_mesh_eval_->ldata, CD_NORMAL));
for (int poly_index = 0; poly_index < export_mesh_eval_->totpoly; ++poly_index) {
const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
bool need_per_loop_normals = lnors != nullptr || (mpoly.flag & ME_SMOOTH);
@@ -453,7 +450,7 @@ int16_t OBJMesh::get_poly_deform_group_index(const int poly_index,
BLI_assert(poly_index < export_mesh_eval_->totpoly);
BLI_assert(group_weights.size() == BKE_object_defgroup_count(&export_object_eval_));
- const MDeformVert *dvert_layer = static_cast<MDeformVert *>(
+ const MDeformVert *dvert_layer = static_cast<const MDeformVert *>(
CustomData_get_layer(&export_mesh_eval_->vdata, CD_MDEFORMVERT));
if (!dvert_layer) {
return NOT_FOUND;
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
index f47ca423dbc..ee2e6227700 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
@@ -241,6 +241,11 @@ class OBJMesh : NonCopyable {
return i < 0 || i >= poly_order_.size() ? i : poly_order_[i];
}
+ Mesh *get_mesh() const
+ {
+ return export_mesh_eval_;
+ }
+
private:
/**
* Free the mesh if _the exporter_ created it.
@@ -256,6 +261,6 @@ class OBJMesh : NonCopyable {
/**
* Set the final transform after applying axes settings and an Object's world transform.
*/
- void set_world_axes_transform(eTransformAxisForward forward, eTransformAxisUp up);
+ void set_world_axes_transform(eIOAxis forward, eIOAxis up);
};
} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc
index c48d5a5f7f0..4ed148ec64e 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc
@@ -113,8 +113,7 @@ static const bNode *get_node_of_type(Span<const nodes::OutputSocketRef *> socket
/**
* From a texture image shader node, get the image's filepath.
- * Returned filepath is stripped of initial "//". If packed image is found,
- * only the file "name" is returned.
+ * If packed image is found, only the file "name" is returned.
*/
static const char *get_image_filepath(const bNode *tex_node)
{
@@ -134,9 +133,6 @@ static const char *get_image_filepath(const bNode *tex_node)
"directory as the .MTL file.\n",
path);
}
- if (path[0] == '/' && path[1] == '/') {
- path += 2;
- }
return path;
}
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc
index c247048ce13..172a59e5341 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc
@@ -25,15 +25,12 @@ OBJCurve::OBJCurve(const Depsgraph *depsgraph,
set_world_axes_transform(export_params.forward_axis, export_params.up_axis);
}
-void OBJCurve::set_world_axes_transform(const eTransformAxisForward forward,
- const eTransformAxisUp up)
+void OBJCurve::set_world_axes_transform(const eIOAxis forward, const eIOAxis up)
{
float axes_transform[3][3];
unit_m3(axes_transform);
/* +Y-forward and +Z-up are the Blender's default axis settings. */
- mat3_from_axis_conversion(OBJ_AXIS_Y_FORWARD, OBJ_AXIS_Z_UP, forward, up, axes_transform);
- /* mat3_from_axis_conversion returns a transposed matrix! */
- transpose_m3(axes_transform);
+ mat3_from_axis_conversion(forward, up, IO_AXIS_Y, IO_AXIS_Z, axes_transform);
mul_m4_m3m4(world_axes_transform_, axes_transform, export_object_eval_->obmat);
/* #mul_m4_m3m4 does not transform last row of #Object.obmat, i.e. location data. */
mul_v3_m3v3(world_axes_transform_[3], axes_transform, export_object_eval_->obmat[3]);
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh
index fe826725daf..65389d44f59 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh
@@ -56,7 +56,7 @@ class OBJCurve : NonCopyable {
/**
* Set the final transform after applying axes settings and an Object's world transform.
*/
- void set_world_axes_transform(eTransformAxisForward forward, eTransformAxisUp up);
+ void set_world_axes_transform(eIOAxis forward, eIOAxis up);
};
} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
index 78b709c884a..b0938084efb 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
@@ -195,7 +195,7 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me
auto &fh = buffers[i];
obj_writer.write_object_name(fh, obj);
- obj_writer.write_vertex_coords(fh, obj);
+ obj_writer.write_vertex_coords(fh, obj, export_params.export_colors);
if (obj.tot_polygons() > 0) {
if (export_params.export_smooth_groups) {
@@ -284,7 +284,16 @@ void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, co
std::move(exportable_as_mesh), *frame_writer, mtl_writer.get(), export_params);
if (mtl_writer) {
mtl_writer->write_header(export_params.blen_filepath);
- mtl_writer->write_materials();
+ char dest_dir[PATH_MAX];
+ if (export_params.file_base_for_tests[0] == '\0') {
+ BLI_split_dir_part(export_params.filepath, dest_dir, PATH_MAX);
+ }
+ else {
+ BLI_strncpy(dest_dir, export_params.file_base_for_tests, PATH_MAX);
+ }
+ BLI_path_slash_native(dest_dir);
+ BLI_path_normalize(nullptr, dest_dir);
+ mtl_writer->write_materials(export_params.blen_filepath, export_params.path_mode, dest_dir);
}
write_nurbs_curve_objects(std::move(exportable_as_nurbs), *frame_writer);
}
diff --git a/source/blender/io/wavefront_obj/importer/importer_mesh_utils.cc b/source/blender/io/wavefront_obj/importer/importer_mesh_utils.cc
index 7019e67419e..f33753d720d 100644
--- a/source/blender/io/wavefront_obj/importer/importer_mesh_utils.cc
+++ b/source/blender/io/wavefront_obj/importer/importer_mesh_utils.cc
@@ -99,13 +99,8 @@ void transform_object(Object *object, const OBJImportParams &import_params)
float obmat[4][4];
unit_m4(obmat);
/* +Y-forward and +Z-up are the default Blender axis settings. */
- mat3_from_axis_conversion(import_params.forward_axis,
- import_params.up_axis,
- OBJ_AXIS_Y_FORWARD,
- OBJ_AXIS_Z_UP,
- axes_transform);
- /* mat3_from_axis_conversion returns a transposed matrix! */
- transpose_m3(axes_transform);
+ mat3_from_axis_conversion(
+ IO_AXIS_Y, IO_AXIS_Z, import_params.forward_axis, import_params.up_axis, axes_transform);
copy_m4_m3(obmat, axes_transform);
BKE_object_apply_mat4(object, obmat, true, false);
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc
index be322f49840..3cc17e7d8e6 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc
+++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc
@@ -5,12 +5,15 @@
*/
#include "BLI_map.hh"
+#include "BLI_math_color.h"
+#include "BLI_math_vector.h"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
-#include "IO_string_utils.hh"
-
#include "obj_import_file_reader.hh"
+#include "obj_import_string_utils.hh"
+
+#include <charconv>
namespace blender::io::obj {
@@ -35,6 +38,7 @@ static Geometry *create_geometry(Geometry *const prev_geometry,
g->geom_type_ = new_type;
g->geometry_name_ = name.is_empty() ? "New object" : name;
g->vertex_start_ = global_vertices.vertices.size();
+ g->vertex_color_start_ = global_vertices.vertex_colors.size();
r_offset.set_index_offset(g->vertex_start_);
return g;
};
@@ -67,40 +71,89 @@ static Geometry *create_geometry(Geometry *const prev_geometry,
}
static void geom_add_vertex(Geometry *geom,
- const StringRef line,
+ const char *p,
+ const char *end,
GlobalVertices &r_global_vertices)
{
float3 vert;
- parse_floats(line, 0.0f, vert, 3);
+ p = parse_floats(p, end, 0.0f, vert, 3);
r_global_vertices.vertices.append(vert);
geom->vertex_count_++;
+ /* OBJ extension: `xyzrgb` vertex colors, when the vertex position
+ * is followed by 3 more RGB color components. See
+ * http://paulbourke.net/dataformats/obj/colour.html */
+ if (p < end) {
+ float3 srgb;
+ p = parse_floats(p, end, -1.0f, srgb, 3);
+ if (srgb.x >= 0 && srgb.y >= 0 && srgb.z >= 0) {
+ float3 linear;
+ srgb_to_linearrgb_v3_v3(linear, srgb);
+ r_global_vertices.vertex_colors.append(linear);
+ geom->vertex_color_count_++;
+ }
+ }
+}
+
+static void geom_add_mrgb_colors(Geometry *geom,
+ const char *p,
+ const char *end,
+ GlobalVertices &r_global_vertices)
+{
+ /* MRGB color extension, in the form of
+ * "#MRGB MMRRGGBBMMRRGGBB ..."
+ * http://paulbourke.net/dataformats/obj/colour.html */
+ p = drop_whitespace(p, end);
+ const int mrgb_length = 8;
+ while (p + mrgb_length <= end) {
+ uint32_t value = 0;
+ std::from_chars_result res = std::from_chars(p, p + mrgb_length, value, 16);
+ if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) {
+ return;
+ }
+ unsigned char srgb[4];
+ srgb[0] = (value >> 16) & 0xFF;
+ srgb[1] = (value >> 8) & 0xFF;
+ srgb[2] = value & 0xFF;
+ srgb[3] = 0xFF;
+ float linear[4];
+ srgb_to_linearrgb_uchar4(linear, srgb);
+ r_global_vertices.vertex_colors.append({linear[0], linear[1], linear[2]});
+ geom->vertex_color_count_++;
+ p += mrgb_length;
+ }
}
static void geom_add_vertex_normal(Geometry *geom,
- const StringRef line,
+ const char *p,
+ const char *end,
GlobalVertices &r_global_vertices)
{
float3 normal;
- parse_floats(line, 0.0f, normal, 3);
+ parse_floats(p, end, 0.0f, normal, 3);
+ /* Normals can be printed with only several digits in the file,
+ * making them ever-so-slightly non unit length. Make sure they are
+ * normalized. */
+ normalize_v3(normal);
r_global_vertices.vertex_normals.append(normal);
geom->has_vertex_normals_ = true;
}
-static void geom_add_uv_vertex(const StringRef line, GlobalVertices &r_global_vertices)
+static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
{
float2 uv;
- parse_floats(line, 0.0f, uv, 2);
+ parse_floats(p, end, 0.0f, uv, 2);
r_global_vertices.uv_vertices.append(uv);
}
static void geom_add_edge(Geometry *geom,
- StringRef line,
+ const char *p,
+ const char *end,
const VertexIndexOffset &offsets,
GlobalVertices &r_global_vertices)
{
int edge_v1, edge_v2;
- line = parse_int(line, -1, edge_v1);
- line = parse_int(line, -1, edge_v2);
+ p = parse_int(p, end, -1, edge_v1);
+ p = parse_int(p, end, -1, edge_v2);
/* Always keep stored indices non-negative and zero-based. */
edge_v1 += edge_v1 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1;
edge_v2 += edge_v2 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1;
@@ -109,7 +162,8 @@ static void geom_add_edge(Geometry *geom,
}
static void geom_add_polygon(Geometry *geom,
- StringRef line,
+ const char *p,
+ const char *end,
const GlobalVertices &global_vertices,
const VertexIndexOffset &offsets,
const int material_index,
@@ -121,32 +175,32 @@ static void geom_add_polygon(Geometry *geom,
curr_face.material_index = material_index;
if (group_index >= 0) {
curr_face.vertex_group_index = group_index;
- geom->use_vertex_groups_ = true;
+ geom->has_vertex_groups_ = true;
}
const int orig_corners_size = geom->face_corners_.size();
curr_face.start_index_ = orig_corners_size;
bool face_valid = true;
- line = drop_whitespace(line);
- while (!line.is_empty() && face_valid) {
+ p = drop_whitespace(p, end);
+ while (p < end && face_valid) {
PolyCorner corner;
bool got_uv = false, got_normal = false;
/* Parse vertex index. */
- line = parse_int(line, INT32_MAX, corner.vert_index, false);
+ p = parse_int(p, end, INT32_MAX, corner.vert_index, false);
face_valid &= corner.vert_index != INT32_MAX;
- if (!line.is_empty() && line[0] == '/') {
+ if (p < end && *p == '/') {
/* Parse UV index. */
- line = line.drop_prefix(1);
- if (!line.is_empty() && line[0] != '/') {
- line = parse_int(line, INT32_MAX, corner.uv_vert_index, false);
+ ++p;
+ if (p < end && *p != '/') {
+ p = parse_int(p, end, INT32_MAX, corner.uv_vert_index, false);
got_uv = corner.uv_vert_index != INT32_MAX;
}
/* Parse normal index. */
- if (!line.is_empty() && line[0] == '/') {
- line = line.drop_prefix(1);
- line = parse_int(line, INT32_MAX, corner.vertex_normal_index, false);
- got_normal = corner.uv_vert_index != INT32_MAX;
+ if (p < end && *p == '/') {
+ ++p;
+ p = parse_int(p, end, INT32_MAX, corner.vertex_normal_index, false);
+ got_normal = corner.vertex_normal_index != INT32_MAX;
}
}
/* Always keep stored indices non-negative and zero-based. */
@@ -169,7 +223,10 @@ static void geom_add_polygon(Geometry *geom,
face_valid = false;
}
}
- if (got_normal) {
+ /* Ignore corner normal index, if the geometry does not have any normals.
+ * Some obj files out there do have face definitions that refer to normal indices,
+ * without any normals being present (T98782). */
+ if (got_normal && geom->has_vertex_normals_) {
corner.vertex_normal_index += corner.vertex_normal_index < 0 ?
global_vertices.vertex_normals.size() :
-1;
@@ -186,7 +243,7 @@ static void geom_add_polygon(Geometry *geom,
curr_face.corner_count_++;
/* Skip whitespace to get to the next face corner. */
- line = drop_whitespace(line);
+ p = drop_whitespace(p, end);
}
if (face_valid) {
@@ -201,14 +258,16 @@ static void geom_add_polygon(Geometry *geom,
}
static Geometry *geom_set_curve_type(Geometry *geom,
- const StringRef rest_line,
+ const char *p,
+ const char *end,
const GlobalVertices &global_vertices,
const StringRef group_name,
VertexIndexOffset &r_offsets,
Vector<std::unique_ptr<Geometry>> &r_all_geometries)
{
- if (rest_line.find("bspline") == StringRef::not_found) {
- std::cerr << "Curve type not supported:'" << rest_line << "'" << std::endl;
+ p = drop_whitespace(p, end);
+ if (!StringRef(p, end).startswith("bspline")) {
+ std::cerr << "Curve type not supported: '" << std::string(p, end) << "'" << std::endl;
return geom;
}
geom = create_geometry(
@@ -217,22 +276,23 @@ static Geometry *geom_set_curve_type(Geometry *geom,
return geom;
}
-static void geom_set_curve_degree(Geometry *geom, const StringRef line)
+static void geom_set_curve_degree(Geometry *geom, const char *p, const char *end)
{
- parse_int(line, 3, geom->nurbs_element_.degree);
+ parse_int(p, end, 3, geom->nurbs_element_.degree);
}
static void geom_add_curve_vertex_indices(Geometry *geom,
- StringRef line,
+ const char *p,
+ const char *end,
const GlobalVertices &global_vertices)
{
/* Curve lines always have "0.0" and "1.0", skip over them. */
float dummy[2];
- line = parse_floats(line, 0, dummy, 2);
+ p = parse_floats(p, end, 0, dummy, 2);
/* Parse indices. */
- while (!line.is_empty()) {
+ while (p < end) {
int index;
- line = parse_int(line, INT32_MAX, index);
+ p = parse_int(p, end, INT32_MAX, index);
if (index == INT32_MAX) {
return;
}
@@ -242,22 +302,22 @@ static void geom_add_curve_vertex_indices(Geometry *geom,
}
}
-static void geom_add_curve_parameters(Geometry *geom, StringRef line)
+static void geom_add_curve_parameters(Geometry *geom, const char *p, const char *end)
{
- line = drop_whitespace(line);
- if (line.is_empty()) {
- std::cerr << "Invalid OBJ curve parm line: '" << line << "'" << std::endl;
+ p = drop_whitespace(p, end);
+ if (p == end) {
+ std::cerr << "Invalid OBJ curve parm line" << std::endl;
return;
}
- if (line[0] != 'u') {
- std::cerr << "OBJ curve surfaces are not supported: '" << line[0] << "'" << std::endl;
+ if (*p != 'u') {
+ std::cerr << "OBJ curve surfaces are not supported: '" << *p << "'" << std::endl;
return;
}
- line = line.drop_prefix(1);
+ ++p;
- while (!line.is_empty()) {
+ while (p < end) {
float val;
- line = parse_float(line, FLT_MAX, val);
+ p = parse_float(p, end, FLT_MAX, val);
if (val != FLT_MAX) {
geom->nurbs_element_.parm.append(val);
}
@@ -270,7 +330,6 @@ static void geom_add_curve_parameters(Geometry *geom, StringRef line)
static void geom_update_group(const StringRef rest_line, std::string &r_group_name)
{
-
if (rest_line.find("off") != string::npos || rest_line.find("null") != string::npos ||
rest_line.find("default") != string::npos) {
/* Set group for future elements like faces or curves to empty. */
@@ -280,17 +339,18 @@ static void geom_update_group(const StringRef rest_line, std::string &r_group_na
r_group_name = rest_line;
}
-static void geom_update_smooth_group(StringRef line, bool &r_state_shaded_smooth)
+static void geom_update_smooth_group(const char *p, const char *end, bool &r_state_shaded_smooth)
{
- line = drop_whitespace(line);
+ p = drop_whitespace(p, end);
/* Some implementations use "0" and "null" too, in addition to "off". */
+ const StringRef line = StringRef(p, end);
if (line == "0" || line.startswith("off") || line.startswith("null")) {
r_state_shaded_smooth = false;
return;
}
int smooth = 0;
- parse_int(line, 0, smooth);
+ parse_int(p, end, 0, smooth);
r_state_shaded_smooth = smooth != 0;
}
@@ -312,21 +372,21 @@ OBJParser::~OBJParser()
}
/* If line starts with keyword followed by whitespace, returns true and drops it from the line. */
-static bool parse_keyword(StringRef &line, StringRef keyword)
+static bool parse_keyword(const char *&p, const char *end, StringRef keyword)
{
const size_t keyword_len = keyword.size();
- if (line.size() < keyword_len + 1) {
+ if (end - p < keyword_len + 1) {
return false;
}
- if (!line.startswith(keyword)) {
+ if (memcmp(p, keyword.data(), keyword_len) != 0) {
return false;
}
/* Treat any ASCII control character as white-space;
* don't use `isspace()` for performance reasons. */
- if (line[keyword_len] > ' ') {
+ if (p[keyword_len] > ' ') {
return false;
}
- line = line.drop_prefix(keyword_len + 1);
+ p += keyword_len + 1;
return true;
}
@@ -337,9 +397,14 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
return;
}
+ /* Use the filename as the default name given to the initial object. */
+ char ob_name[FILE_MAXFILE];
+ BLI_strncpy(ob_name, BLI_path_basename(import_params_.filepath), FILE_MAXFILE);
+ BLI_path_extension_replace(ob_name, FILE_MAXFILE, "");
+
VertexIndexOffset offsets;
Geometry *curr_geom = create_geometry(
- nullptr, GEOM_MESH, "", r_global_vertices, r_all_geometries, offsets);
+ nullptr, GEOM_MESH, ob_name, r_global_vertices, r_all_geometries, offsets);
/* State variables: once set, they remain the same for the remaining
* elements in the object. */
@@ -400,27 +465,29 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
StringRef buffer_str{buffer.data(), (int64_t)last_nl};
while (!buffer_str.is_empty()) {
StringRef line = read_next_line(buffer_str);
- line = drop_whitespace(line);
+ const char *p = line.begin(), *end = line.end();
+ p = drop_whitespace(p, end);
++line_number;
- if (line.is_empty()) {
+ if (p == end) {
continue;
}
/* Most common things that start with 'v': vertices, normals, UVs. */
- if (line[0] == 'v') {
- if (parse_keyword(line, "v")) {
- geom_add_vertex(curr_geom, line, r_global_vertices);
+ if (*p == 'v') {
+ if (parse_keyword(p, end, "v")) {
+ geom_add_vertex(curr_geom, p, end, r_global_vertices);
}
- else if (parse_keyword(line, "vn")) {
- geom_add_vertex_normal(curr_geom, line, r_global_vertices);
+ else if (parse_keyword(p, end, "vn")) {
+ geom_add_vertex_normal(curr_geom, p, end, r_global_vertices);
}
- else if (parse_keyword(line, "vt")) {
- geom_add_uv_vertex(line, r_global_vertices);
+ else if (parse_keyword(p, end, "vt")) {
+ geom_add_uv_vertex(p, end, r_global_vertices);
}
}
/* Faces. */
- else if (parse_keyword(line, "f")) {
+ else if (parse_keyword(p, end, "f")) {
geom_add_polygon(curr_geom,
- line,
+ p,
+ end,
r_global_vertices,
offsets,
state_material_index,
@@ -428,20 +495,24 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
state_shaded_smooth);
}
/* Faces. */
- else if (parse_keyword(line, "l")) {
- geom_add_edge(curr_geom, line, offsets, r_global_vertices);
+ else if (parse_keyword(p, end, "l")) {
+ geom_add_edge(curr_geom, p, end, offsets, r_global_vertices);
}
/* Objects. */
- else if (parse_keyword(line, "o")) {
+ else if (parse_keyword(p, end, "o")) {
state_shaded_smooth = false;
state_group_name = "";
state_material_name = "";
- curr_geom = create_geometry(
- curr_geom, GEOM_MESH, line.trim(), r_global_vertices, r_all_geometries, offsets);
+ curr_geom = create_geometry(curr_geom,
+ GEOM_MESH,
+ StringRef(p, end).trim(),
+ r_global_vertices,
+ r_all_geometries,
+ offsets);
}
/* Groups. */
- else if (parse_keyword(line, "g")) {
- geom_update_group(line.trim(), state_group_name);
+ else if (parse_keyword(p, end, "g")) {
+ geom_update_group(StringRef(p, end).trim(), state_group_name);
int new_index = curr_geom->group_indices_.size();
state_group_index = curr_geom->group_indices_.lookup_or_add(state_group_name, new_index);
if (new_index == state_group_index) {
@@ -449,12 +520,12 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
}
}
/* Smoothing groups. */
- else if (parse_keyword(line, "s")) {
- geom_update_smooth_group(line, state_shaded_smooth);
+ else if (parse_keyword(p, end, "s")) {
+ geom_update_smooth_group(p, end, state_shaded_smooth);
}
/* Materials and their libraries. */
- else if (parse_keyword(line, "usemtl")) {
- state_material_name = line.trim();
+ else if (parse_keyword(p, end, "usemtl")) {
+ state_material_name = StringRef(p, end).trim();
int new_mat_index = curr_geom->material_indices_.size();
state_material_index = curr_geom->material_indices_.lookup_or_add(state_material_name,
new_mat_index);
@@ -462,32 +533,35 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
curr_geom->material_order_.append(state_material_name);
}
}
- else if (parse_keyword(line, "mtllib")) {
- add_mtl_library(line.trim());
+ else if (parse_keyword(p, end, "mtllib")) {
+ add_mtl_library(StringRef(p, end).trim());
+ }
+ else if (parse_keyword(p, end, "#MRGB")) {
+ geom_add_mrgb_colors(curr_geom, p, end, r_global_vertices);
}
/* Comments. */
- else if (line.startswith("#")) {
+ else if (*p == '#') {
/* Nothing to do. */
}
/* Curve related things. */
- else if (parse_keyword(line, "cstype")) {
+ else if (parse_keyword(p, end, "cstype")) {
curr_geom = geom_set_curve_type(
- curr_geom, line, r_global_vertices, state_group_name, offsets, r_all_geometries);
+ curr_geom, p, end, r_global_vertices, state_group_name, offsets, r_all_geometries);
}
- else if (parse_keyword(line, "deg")) {
- geom_set_curve_degree(curr_geom, line);
+ else if (parse_keyword(p, end, "deg")) {
+ geom_set_curve_degree(curr_geom, p, end);
}
- else if (parse_keyword(line, "curv")) {
- geom_add_curve_vertex_indices(curr_geom, line, r_global_vertices);
+ else if (parse_keyword(p, end, "curv")) {
+ geom_add_curve_vertex_indices(curr_geom, p, end, r_global_vertices);
}
- else if (parse_keyword(line, "parm")) {
- geom_add_curve_parameters(curr_geom, line);
+ else if (parse_keyword(p, end, "parm")) {
+ geom_add_curve_parameters(curr_geom, p, end);
}
- else if (line.startswith("end")) {
+ else if (StringRef(p, end).startswith("end")) {
/* End of curve definition, nothing else to do. */
}
else {
- std::cout << "OBJ element not recognized: '" << line << "'" << std::endl;
+ std::cout << "OBJ element not recognized: '" << std::string(p, end) << "'" << std::endl;
}
}
@@ -501,33 +575,33 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
add_default_mtl_library();
}
-static eMTLSyntaxElement mtl_line_start_to_enum(StringRef &line)
+static eMTLSyntaxElement mtl_line_start_to_enum(const char *&p, const char *end)
{
- if (parse_keyword(line, "map_Kd")) {
+ if (parse_keyword(p, end, "map_Kd")) {
return eMTLSyntaxElement::map_Kd;
}
- if (parse_keyword(line, "map_Ks")) {
+ if (parse_keyword(p, end, "map_Ks")) {
return eMTLSyntaxElement::map_Ks;
}
- if (parse_keyword(line, "map_Ns")) {
+ if (parse_keyword(p, end, "map_Ns")) {
return eMTLSyntaxElement::map_Ns;
}
- if (parse_keyword(line, "map_d")) {
+ if (parse_keyword(p, end, "map_d")) {
return eMTLSyntaxElement::map_d;
}
- if (parse_keyword(line, "refl")) {
+ if (parse_keyword(p, end, "refl")) {
return eMTLSyntaxElement::map_refl;
}
- if (parse_keyword(line, "map_refl")) {
+ if (parse_keyword(p, end, "map_refl")) {
return eMTLSyntaxElement::map_refl;
}
- if (parse_keyword(line, "map_Ke")) {
+ if (parse_keyword(p, end, "map_Ke")) {
return eMTLSyntaxElement::map_Ke;
}
- if (parse_keyword(line, "bump")) {
+ if (parse_keyword(p, end, "bump")) {
return eMTLSyntaxElement::map_Bump;
}
- if (parse_keyword(line, "map_Bump") || parse_keyword(line, "map_bump")) {
+ if (parse_keyword(p, end, "map_Bump") || parse_keyword(p, end, "map_bump")) {
return eMTLSyntaxElement::map_Bump;
}
return eMTLSyntaxElement::string;
@@ -545,39 +619,43 @@ static const std::pair<StringRef, int> unsupported_texture_options[] = {
{"-texres", 1},
};
-static bool parse_texture_option(StringRef &line, MTLMaterial *material, tex_map_XX &tex_map)
+static bool parse_texture_option(const char *&p,
+ const char *end,
+ MTLMaterial *material,
+ tex_map_XX &tex_map)
{
- line = drop_whitespace(line);
- if (parse_keyword(line, "-o")) {
- line = parse_floats(line, 0.0f, tex_map.translation, 3);
+ p = drop_whitespace(p, end);
+ if (parse_keyword(p, end, "-o")) {
+ p = parse_floats(p, end, 0.0f, tex_map.translation, 3);
return true;
}
- if (parse_keyword(line, "-s")) {
- line = parse_floats(line, 1.0f, tex_map.scale, 3);
+ if (parse_keyword(p, end, "-s")) {
+ p = parse_floats(p, end, 1.0f, tex_map.scale, 3);
return true;
}
- if (parse_keyword(line, "-bm")) {
- line = parse_float(line, 1.0f, material->map_Bump_strength);
+ if (parse_keyword(p, end, "-bm")) {
+ p = parse_float(p, end, 1.0f, material->map_Bump_strength);
return true;
}
- if (parse_keyword(line, "-type")) {
- line = drop_whitespace(line);
+ if (parse_keyword(p, end, "-type")) {
+ p = drop_whitespace(p, end);
/* Only sphere is supported. */
tex_map.projection_type = SHD_PROJ_SPHERE;
+ const StringRef line = StringRef(p, end);
if (!line.startswith("sphere")) {
std::cerr << "OBJ import: only sphere MTL projection type is supported: '" << line << "'"
<< std::endl;
}
- line = drop_non_whitespace(line);
+ p = drop_non_whitespace(p, end);
return true;
}
/* Check for unsupported options and skip them. */
for (const auto &opt : unsupported_texture_options) {
- if (parse_keyword(line, opt.first)) {
+ if (parse_keyword(p, end, opt.first)) {
/* Drop the arguments. */
for (int i = 0; i < opt.second; ++i) {
- line = drop_whitespace(line);
- line = drop_non_whitespace(line);
+ p = drop_whitespace(p, end);
+ p = drop_non_whitespace(p, end);
}
return true;
}
@@ -586,15 +664,19 @@ static bool parse_texture_option(StringRef &line, MTLMaterial *material, tex_map
return false;
}
-static void parse_texture_map(StringRef line, MTLMaterial *material, const char *mtl_dir_path)
+static void parse_texture_map(const char *p,
+ const char *end,
+ MTLMaterial *material,
+ const char *mtl_dir_path)
{
+ const StringRef line = StringRef(p, end);
bool is_map = line.startswith("map_");
bool is_refl = line.startswith("refl");
bool is_bump = line.startswith("bump");
if (!is_map && !is_refl && !is_bump) {
return;
}
- eMTLSyntaxElement key = mtl_line_start_to_enum(line);
+ eMTLSyntaxElement key = mtl_line_start_to_enum(p, end);
if (key == eMTLSyntaxElement::string || !material->texture_maps.contains(key)) {
/* No supported texture map found. */
std::cerr << "OBJ import: MTL texture map type not supported: '" << line << "'" << std::endl;
@@ -604,12 +686,11 @@ static void parse_texture_map(StringRef line, MTLMaterial *material, const char
tex_map.mtl_dir_path = mtl_dir_path;
/* Parse texture map options. */
- while (parse_texture_option(line, material, tex_map)) {
+ while (parse_texture_option(p, end, material, tex_map)) {
}
/* What remains is the image path. */
- line = line.trim();
- tex_map.image_path = line;
+ tex_map.image_path = StringRef(p, end).trim();
}
Span<std::string> OBJParser::mtl_libraries() const
@@ -667,51 +748,53 @@ void MTLParser::parse_and_store(Map<string, std::unique_ptr<MTLMaterial>> &r_mat
StringRef buffer_str{(const char *)buffer, (int64_t)buffer_len};
while (!buffer_str.is_empty()) {
- StringRef line = read_next_line(buffer_str);
- line = drop_whitespace(line);
- if (line.is_empty()) {
+ const StringRef line = read_next_line(buffer_str);
+ const char *p = line.begin(), *end = line.end();
+ p = drop_whitespace(p, end);
+ if (p == end) {
continue;
}
- if (parse_keyword(line, "newmtl")) {
- line = line.trim();
- if (r_materials.contains(line)) {
+ if (parse_keyword(p, end, "newmtl")) {
+ StringRef mat_name = StringRef(p, end).trim();
+ if (r_materials.contains(mat_name)) {
material = nullptr;
}
else {
- material = r_materials.lookup_or_add(string(line), std::make_unique<MTLMaterial>()).get();
+ material =
+ r_materials.lookup_or_add(string(mat_name), std::make_unique<MTLMaterial>()).get();
}
}
else if (material != nullptr) {
- if (parse_keyword(line, "Ns")) {
- parse_float(line, 324.0f, material->Ns);
+ if (parse_keyword(p, end, "Ns")) {
+ parse_float(p, end, 324.0f, material->Ns);
}
- else if (parse_keyword(line, "Ka")) {
- parse_floats(line, 0.0f, material->Ka, 3);
+ else if (parse_keyword(p, end, "Ka")) {
+ parse_floats(p, end, 0.0f, material->Ka, 3);
}
- else if (parse_keyword(line, "Kd")) {
- parse_floats(line, 0.8f, material->Kd, 3);
+ else if (parse_keyword(p, end, "Kd")) {
+ parse_floats(p, end, 0.8f, material->Kd, 3);
}
- else if (parse_keyword(line, "Ks")) {
- parse_floats(line, 0.5f, material->Ks, 3);
+ else if (parse_keyword(p, end, "Ks")) {
+ parse_floats(p, end, 0.5f, material->Ks, 3);
}
- else if (parse_keyword(line, "Ke")) {
- parse_floats(line, 0.0f, material->Ke, 3);
+ else if (parse_keyword(p, end, "Ke")) {
+ parse_floats(p, end, 0.0f, material->Ke, 3);
}
- else if (parse_keyword(line, "Ni")) {
- parse_float(line, 1.45f, material->Ni);
+ else if (parse_keyword(p, end, "Ni")) {
+ parse_float(p, end, 1.45f, material->Ni);
}
- else if (parse_keyword(line, "d")) {
- parse_float(line, 1.0f, material->d);
+ else if (parse_keyword(p, end, "d")) {
+ parse_float(p, end, 1.0f, material->d);
}
- else if (parse_keyword(line, "illum")) {
+ else if (parse_keyword(p, end, "illum")) {
/* Some files incorrectly use a float (T60135). */
float val;
- parse_float(line, 1.0f, val);
+ parse_float(p, end, 1.0f, val);
material->illum = val;
}
else {
- parse_texture_map(line, material, mtl_dir_path_);
+ parse_texture_map(p, end, material, mtl_dir_path_);
}
}
}
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh
index e41a7f8518e..8bfc5fe8bf0 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh
+++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh
@@ -13,7 +13,7 @@
namespace blender::io::obj {
-/* Note: the OBJ parser implementation is planned to get fairly large changes "soon",
+/* NOTE: the OBJ parser implementation is planned to get fairly large changes "soon",
* so don't read too much into current implementation... */
class OBJParser {
private:
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc
index fc40333c24d..aa38a4d6715 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc
+++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc
@@ -8,7 +8,9 @@
#include "DNA_mesh_types.h"
#include "DNA_scene_types.h"
+#include "BKE_attribute.h"
#include "BKE_customdata.h"
+#include "BKE_deform.h"
#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_node_tree_update.h"
@@ -46,10 +48,11 @@ Object *MeshFromGeometry::create_mesh(Main *bmain,
obj->data = BKE_object_obdata_add_from_type(bmain, OB_MESH, ob_name.c_str());
create_vertices(mesh);
- create_polys_loops(obj, mesh);
+ create_polys_loops(mesh, import_params.import_vertex_groups);
create_edges(mesh);
create_uv_verts(mesh);
create_normals(mesh);
+ create_colors(mesh);
create_materials(bmain, materials, created_materials, obj);
if (import_params.validate_meshes || mesh_geometry_.has_invalid_polys_) {
@@ -62,11 +65,14 @@ Object *MeshFromGeometry::create_mesh(Main *bmain,
transform_object(obj, import_params);
/* FIXME: after 2.80; `mesh->flag` isn't copied by #BKE_mesh_nomain_to_mesh() */
- const short autosmooth = (mesh->flag & ME_AUTOSMOOTH);
+ const uint16_t autosmooth = (mesh->flag & ME_AUTOSMOOTH);
Mesh *dst = static_cast<Mesh *>(obj->data);
BKE_mesh_nomain_to_mesh(mesh, dst, obj, &CD_MASK_EVERYTHING, true);
dst->flag |= autosmooth;
+ /* Note: vertex groups have to be created after final mesh is assigned to the object. */
+ create_vertex_groups(obj);
+
return obj;
}
@@ -161,19 +167,13 @@ void MeshFromGeometry::create_vertices(Mesh *mesh)
}
}
-void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh)
+void MeshFromGeometry::create_polys_loops(Mesh *mesh, bool use_vertex_groups)
{
- /* Will not be used if vertex groups are not imported. */
mesh->dvert = nullptr;
- float weight = 0.0f;
const int64_t total_verts = mesh_geometry_.vertex_count_;
- if (total_verts && mesh_geometry_.use_vertex_groups_) {
+ if (use_vertex_groups && total_verts && mesh_geometry_.has_vertex_groups_) {
mesh->dvert = static_cast<MDeformVert *>(
CustomData_add_layer(&mesh->vdata, CD_MDEFORMVERT, CD_CALLOC, nullptr, total_verts));
- weight = 1.0f / total_verts;
- }
- else {
- UNUSED_VARS(weight);
}
const int64_t tot_face_elems{mesh->totpoly};
@@ -206,28 +206,23 @@ void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh)
tot_loop_idx++;
mloop.v = curr_corner.vert_index;
+ /* Setup vertex group data, if needed. */
if (!mesh->dvert) {
continue;
}
- /* Iterating over mloop results in finding the same vertex multiple times.
- * Another way is to allocate memory for dvert while creating vertices and fill them here.
- */
- MDeformVert &def_vert = mesh->dvert[mloop.v];
- if (!def_vert.dw) {
- def_vert.dw = static_cast<MDeformWeight *>(
- MEM_callocN(sizeof(MDeformWeight), "OBJ Import Deform Weight"));
- }
- /* Every vertex in a face is assigned the same deform group. */
- int group_idx = curr_face.vertex_group_index;
- /* Deform group number (def_nr) must behave like an index into the names' list. */
- *(def_vert.dw) = {static_cast<unsigned int>(group_idx), weight};
+ const int group_index = curr_face.vertex_group_index;
+ MDeformWeight *dw = BKE_defvert_ensure_index(mesh->dvert + mloop.v, group_index);
+ dw->weight = 1.0f;
}
}
+}
- if (!mesh->dvert) {
+void MeshFromGeometry::create_vertex_groups(Object *obj)
+{
+ Mesh *mesh = static_cast<Mesh *>(obj->data);
+ if (mesh->dvert == nullptr) {
return;
}
- /* Add deform group names. */
for (const std::string &name : mesh_geometry_.group_order_) {
BKE_object_defgroup_add_name(obj, name.data());
}
@@ -289,7 +284,7 @@ static Material *get_or_create_material(Main *bmain,
/* We have not, will have to create it. Create a new default
* MTLMaterial too, in case the OBJ file tries to use a material
* that was not in the MTL file. */
- const MTLMaterial &mtl = *materials.lookup_or_add(name, std::make_unique<MTLMaterial>()).get();
+ const MTLMaterial &mtl = *materials.lookup_or_add(name, std::make_unique<MTLMaterial>());
Material *mat = BKE_material_add(bmain, name.c_str());
ShaderNodetreeWrap mat_wrap{bmain, mtl, mat};
@@ -345,4 +340,26 @@ void MeshFromGeometry::create_normals(Mesh *mesh)
MEM_freeN(loop_normals);
}
+void MeshFromGeometry::create_colors(Mesh *mesh)
+{
+ /* Nothing to do if we don't have vertex colors. */
+ if (mesh_geometry_.vertex_color_count_ < 1) {
+ return;
+ }
+ if (mesh_geometry_.vertex_color_count_ != mesh_geometry_.vertex_count_) {
+ std::cerr << "Mismatching number of vertices (" << mesh_geometry_.vertex_count_
+ << ") and colors (" << mesh_geometry_.vertex_color_count_ << ") on object '"
+ << mesh_geometry_.geometry_name_ << "', ignoring colors." << std::endl;
+ return;
+ }
+
+ CustomDataLayer *color_layer = BKE_id_attribute_new(
+ &mesh->id, "Color", CD_PROP_COLOR, ATTR_DOMAIN_POINT, nullptr);
+ float4 *colors = (float4 *)color_layer->data;
+ for (int i = 0; i < mesh_geometry_.vertex_color_count_; ++i) {
+ float3 c = global_vertices_.vertex_colors[mesh_geometry_.vertex_color_start_ + i];
+ colors[i] = float4(c.x, c.y, c.z, 1.0f);
+ }
+}
+
} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh b/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh
index cf4a2aee394..591a7b81e63 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh
+++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh
@@ -45,10 +45,9 @@ class MeshFromGeometry : NonMovable, NonCopyable {
void fixup_invalid_faces();
void create_vertices(Mesh *mesh);
/**
- * Create polygons for the Mesh, set smooth shading flags, deform group names,
- * Materials.
+ * Create polygons for the Mesh, set smooth shading flags, Materials.
*/
- void create_polys_loops(Object *obj, Mesh *mesh);
+ void create_polys_loops(Mesh *mesh, bool use_vertex_groups);
/**
* Add explicitly imported OBJ edges to the mesh.
*/
@@ -65,6 +64,8 @@ class MeshFromGeometry : NonMovable, NonCopyable {
Map<std::string, Material *> &created_materials,
Object *obj);
void create_normals(Mesh *mesh);
+ void create_colors(Mesh *mesh);
+ void create_vertex_groups(Object *obj);
};
} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc b/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc
index c2ecd8a37de..f39def0a4af 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc
+++ b/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc
@@ -13,13 +13,12 @@
#include "DNA_material_types.h"
#include "DNA_node_types.h"
-#include "IO_string_utils.hh"
-
#include "NOD_shader.h"
/* TODO: move eMTLSyntaxElement out of following file into a more neutral place */
#include "obj_export_io.hh"
#include "obj_import_mtl.hh"
+#include "obj_import_string_utils.hh"
namespace blender::io::obj {
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh
index b67ba46af03..3d6733d661e 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh
+++ b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh
@@ -26,6 +26,7 @@ struct GlobalVertices {
Vector<float3> vertices;
Vector<float2> uv_vertices;
Vector<float3> vertex_normals;
+ Vector<float3> vertex_colors;
};
/**
@@ -102,6 +103,8 @@ struct Geometry {
int vertex_start_ = 0;
int vertex_count_ = 0;
+ int vertex_color_start_ = 0;
+ int vertex_color_count_ = 0;
/** Edges written in the file in addition to (or even without polygon) elements. */
Vector<MEdge> edges_;
@@ -110,7 +113,7 @@ struct Geometry {
bool has_invalid_polys_ = false;
bool has_vertex_normals_ = false;
- bool use_vertex_groups_ = false;
+ bool has_vertex_groups_ = false;
NurbsElement nurbs_element_;
int total_loops_ = 0;
};
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc
new file mode 100644
index 00000000000..c8eaa046e68
--- /dev/null
+++ b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "obj_import_string_utils.hh"
+
+/* NOTE: we could use C++17 <charconv> from_chars to parse
+ * floats, but even if some compilers claim full support,
+ * their standard libraries are not quite there yet.
+ * LLVM/libc++ only has a float parser since LLVM 14,
+ * and gcc/libstdc++ since 11.1. So until at least these are
+ * the minimum spec, use an external library. */
+#include "fast_float.h"
+#include <charconv>
+
+namespace blender::io::obj {
+
+StringRef read_next_line(StringRef &buffer)
+{
+ const char *start = buffer.begin();
+ const char *end = buffer.end();
+ size_t len = 0;
+ char prev = 0;
+ const char *ptr = start;
+ while (ptr < end) {
+ char c = *ptr++;
+ if (c == '\n' && prev != '\\') {
+ break;
+ }
+ prev = c;
+ ++len;
+ }
+
+ buffer = StringRef(ptr, end);
+ return StringRef(start, len);
+}
+
+static bool is_whitespace(char c)
+{
+ return c <= ' ' || c == '\\';
+}
+
+const char *drop_whitespace(const char *p, const char *end)
+{
+ while (p < end && is_whitespace(*p)) {
+ ++p;
+ }
+ return p;
+}
+
+const char *drop_non_whitespace(const char *p, const char *end)
+{
+ while (p < end && !is_whitespace(*p)) {
+ ++p;
+ }
+ return p;
+}
+
+static const char *drop_plus(const char *p, const char *end)
+{
+ if (p < end && *p == '+') {
+ ++p;
+ }
+ return p;
+}
+
+const char *parse_float(
+ const char *p, const char *end, float fallback, float &dst, bool skip_space)
+{
+ if (skip_space) {
+ p = drop_whitespace(p, end);
+ }
+ p = drop_plus(p, end);
+ fast_float::from_chars_result res = fast_float::from_chars(p, end, dst);
+ if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) {
+ dst = fallback;
+ }
+ return res.ptr;
+}
+
+const char *parse_floats(const char *p, const char *end, float fallback, float *dst, int count)
+{
+ for (int i = 0; i < count; ++i) {
+ p = parse_float(p, end, fallback, dst[i]);
+ }
+ return p;
+}
+
+const char *parse_int(const char *p, const char *end, int fallback, int &dst, bool skip_space)
+{
+ if (skip_space) {
+ p = drop_whitespace(p, end);
+ }
+ p = drop_plus(p, end);
+ std::from_chars_result res = std::from_chars(p, end, dst);
+ if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) {
+ dst = fallback;
+ }
+ return res.ptr;
+}
+
+} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh
new file mode 100644
index 00000000000..3f428b1ab5c
--- /dev/null
+++ b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+#include "BLI_string_ref.hh"
+
+/*
+ * Various text parsing utilities used by OBJ importer.
+ * The utilities are not directly usable by other formats, since
+ * they treat backslash (\) as a whitespace character (OBJ format
+ * allows backslashes to function as a line-continuation character).
+ *
+ * Many of these functions take two pointers (p, end) indicating
+ * which part of a string to operate on, and return a possibly
+ * changed new start of the string. They could be taking a StringRef
+ * as input and returning a new StringRef, but this is a hot path
+ * in OBJ parsing, and the StringRef approach does lose performance
+ * (mostly due to return of StringRef being two register-size values
+ * instead of just one pointer).
+ */
+
+namespace blender::io::obj {
+
+/**
+ * Fetches next line from an input string buffer.
+ *
+ * The returned line will not have '\n' characters at the end;
+ * the `buffer` is modified to contain remaining text without
+ * the input line.
+ *
+ * Note that backslash (\) character is treated as a line
+ * continuation.
+ */
+StringRef read_next_line(StringRef &buffer);
+
+/**
+ * Drop leading white-space from a string part.
+ * Note that backslash character is considered white-space.
+ */
+const char *drop_whitespace(const char *p, const char *end);
+
+/**
+ * Drop leading non-white-space from a string part.
+ * Note that backslash character is considered white-space.
+ */
+const char *drop_non_whitespace(const char *p, const char *end);
+
+/**
+ * Parse an integer from an input string.
+ * The parsed result is stored in `dst`. The function skips
+ * leading white-space unless `skip_space=false`. If the
+ * number can't be parsed (invalid syntax, out of range),
+ * `fallback` value is stored instead.
+ *
+ * Returns the start of remainder of the input string after parsing.
+ */
+const char *parse_int(
+ const char *p, const char *end, int fallback, int &dst, bool skip_space = true);
+
+/**
+ * Parse a float from an input string.
+ * The parsed result is stored in `dst`. The function skips
+ * leading white-space unless `skip_space=false`. If the
+ * number can't be parsed (invalid syntax, out of range),
+ * `fallback` value is stored instead.
+ *
+ * Returns the start of remainder of the input string after parsing.
+ */
+const char *parse_float(
+ const char *p, const char *end, float fallback, float &dst, bool skip_space = true);
+
+/**
+ * Parse a number of white-space separated floats from an input string.
+ * The parsed `count` numbers are stored in `dst`. If a
+ * number can't be parsed (invalid syntax, out of range),
+ * `fallback` value is stored instead.
+ *
+ * Returns the start of remainder of the input string after parsing.
+ */
+const char *parse_floats(const char *p, const char *end, float fallback, float *dst, int count);
+
+} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/importer/obj_importer.cc b/source/blender/io/wavefront_obj/importer/obj_importer.cc
index b18ff2cf454..f2051d195c8 100644
--- a/source/blender/io/wavefront_obj/importer/obj_importer.cc
+++ b/source/blender/io/wavefront_obj/importer/obj_importer.cc
@@ -88,7 +88,6 @@ void importer_main(bContext *C, const OBJImportParams &import_params)
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
importer_main(bmain, scene, view_layer, import_params);
- static_cast<void>(CTX_data_ensure_evaluated_depsgraph(C));
}
void importer_main(Main *bmain,
diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc
index f74bfc155dd..6aec848573f 100644
--- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc
+++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc
@@ -11,12 +11,15 @@
#include "BKE_appdir.h"
#include "BKE_blender_version.h"
+#include "BKE_main.h"
#include "BLI_fileops.h"
#include "BLI_index_range.hh"
#include "BLI_string_utf8.h"
#include "BLI_vector.hh"
+#include "BLO_readfile.h"
+
#include "DEG_depsgraph.h"
#include "obj_export_file_writer.hh"
@@ -259,11 +262,12 @@ class obj_exporter_regression_test : public obj_exporter_test {
std::string tempdir = std::string(BKE_tempdir_base());
std::string out_file_path = tempdir + BLI_path_basename(golden_obj.c_str());
strncpy(params.filepath, out_file_path.c_str(), FILE_MAX - 1);
- params.blen_filepath = blendfile.c_str();
+ params.blen_filepath = bfile->main->filepath;
+ std::string golden_file_path = blender::tests::flags_test_asset_dir() + "/" + golden_obj;
+ BLI_split_dir_part(golden_file_path.c_str(), params.file_base_for_tests, PATH_MAX);
export_frame(depsgraph, params, out_file_path.c_str());
std::string output_str = read_temp_file_in_string(out_file_path);
- std::string golden_file_path = blender::tests::flags_test_asset_dir() + "/" + golden_obj;
std::string golden_str = read_temp_file_in_string(golden_file_path);
bool are_equal = strings_equal_after_first_lines(output_str, golden_str);
if (save_failing_test_output && !are_equal) {
@@ -311,8 +315,8 @@ TEST_F(obj_exporter_regression_test, all_quads)
TEST_F(obj_exporter_regression_test, fgons)
{
OBJExportParamsDefault _export;
- _export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
- _export.params.up_axis = OBJ_AXIS_Z_UP;
+ _export.params.forward_axis = IO_AXIS_Y;
+ _export.params.up_axis = IO_AXIS_Z;
_export.params.export_materials = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/fgons.blend", "io_tests/obj/fgons.obj", "", _export.params);
@@ -321,8 +325,8 @@ TEST_F(obj_exporter_regression_test, fgons)
TEST_F(obj_exporter_regression_test, edges)
{
OBJExportParamsDefault _export;
- _export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
- _export.params.up_axis = OBJ_AXIS_Z_UP;
+ _export.params.forward_axis = IO_AXIS_Y;
+ _export.params.up_axis = IO_AXIS_Z;
_export.params.export_materials = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/edges.blend", "io_tests/obj/edges.obj", "", _export.params);
@@ -331,8 +335,8 @@ TEST_F(obj_exporter_regression_test, edges)
TEST_F(obj_exporter_regression_test, vertices)
{
OBJExportParamsDefault _export;
- _export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
- _export.params.up_axis = OBJ_AXIS_Z_UP;
+ _export.params.forward_axis = IO_AXIS_Y;
+ _export.params.up_axis = IO_AXIS_Z;
_export.params.export_materials = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/vertices.blend", "io_tests/obj/vertices.obj", "", _export.params);
@@ -351,8 +355,8 @@ TEST_F(obj_exporter_regression_test, non_uniform_scale)
TEST_F(obj_exporter_regression_test, nurbs_as_nurbs)
{
OBJExportParamsDefault _export;
- _export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
- _export.params.up_axis = OBJ_AXIS_Z_UP;
+ _export.params.forward_axis = IO_AXIS_Y;
+ _export.params.up_axis = IO_AXIS_Z;
_export.params.export_materials = false;
_export.params.export_curves_as_nurbs = true;
compare_obj_export_to_golden(
@@ -362,8 +366,8 @@ TEST_F(obj_exporter_regression_test, nurbs_as_nurbs)
TEST_F(obj_exporter_regression_test, nurbs_curves_as_nurbs)
{
OBJExportParamsDefault _export;
- _export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
- _export.params.up_axis = OBJ_AXIS_Z_UP;
+ _export.params.forward_axis = IO_AXIS_Y;
+ _export.params.up_axis = IO_AXIS_Z;
_export.params.export_materials = false;
_export.params.export_curves_as_nurbs = true;
compare_obj_export_to_golden("io_tests/blend_geometry/nurbs_curves.blend",
@@ -375,8 +379,8 @@ TEST_F(obj_exporter_regression_test, nurbs_curves_as_nurbs)
TEST_F(obj_exporter_regression_test, nurbs_as_mesh)
{
OBJExportParamsDefault _export;
- _export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
- _export.params.up_axis = OBJ_AXIS_Z_UP;
+ _export.params.forward_axis = IO_AXIS_Y;
+ _export.params.up_axis = IO_AXIS_Z;
_export.params.export_materials = false;
_export.params.export_curves_as_nurbs = false;
compare_obj_export_to_golden(
@@ -386,8 +390,8 @@ TEST_F(obj_exporter_regression_test, nurbs_as_mesh)
TEST_F(obj_exporter_regression_test, cube_all_data_triangulated)
{
OBJExportParamsDefault _export;
- _export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
- _export.params.up_axis = OBJ_AXIS_Z_UP;
+ _export.params.forward_axis = IO_AXIS_Y;
+ _export.params.up_axis = IO_AXIS_Z;
_export.params.export_materials = false;
_export.params.export_triangulated_mesh = true;
compare_obj_export_to_golden("io_tests/blend_geometry/cube_all_data.blend",
@@ -399,8 +403,8 @@ TEST_F(obj_exporter_regression_test, cube_all_data_triangulated)
TEST_F(obj_exporter_regression_test, cube_normal_edit)
{
OBJExportParamsDefault _export;
- _export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
- _export.params.up_axis = OBJ_AXIS_Z_UP;
+ _export.params.forward_axis = IO_AXIS_Y;
+ _export.params.up_axis = IO_AXIS_Z;
_export.params.export_materials = false;
compare_obj_export_to_golden("io_tests/blend_geometry/cube_normal_edit.blend",
"io_tests/obj/cube_normal_edit.obj",
@@ -432,24 +436,44 @@ TEST_F(obj_exporter_regression_test, cubes_positioned)
_export.params);
}
-/* Note: texture paths in the resulting mtl file currently are always
- * as they are stored in the source .blend file; not relative to where
- * the export is done. When that is properly fixed, the expected .mtl
- * file should be updated. */
-TEST_F(obj_exporter_regression_test, cubes_with_textures)
+TEST_F(obj_exporter_regression_test, cubes_vertex_colors)
+{
+ OBJExportParamsDefault _export;
+ _export.params.export_colors = true;
+ _export.params.export_normals = false;
+ _export.params.export_uv = false;
+ _export.params.export_materials = false;
+ compare_obj_export_to_golden("io_tests/blend_geometry/cubes_vertex_colors.blend",
+ "io_tests/obj/cubes_vertex_colors.obj",
+ "",
+ _export.params);
+}
+
+TEST_F(obj_exporter_regression_test, cubes_with_textures_strip)
{
OBJExportParamsDefault _export;
+ _export.params.path_mode = PATH_REFERENCE_STRIP;
compare_obj_export_to_golden("io_tests/blend_geometry/cubes_with_textures.blend",
"io_tests/obj/cubes_with_textures.obj",
"io_tests/obj/cubes_with_textures.mtl",
_export.params);
}
+TEST_F(obj_exporter_regression_test, cubes_with_textures_relative)
+{
+ OBJExportParamsDefault _export;
+ _export.params.path_mode = PATH_REFERENCE_RELATIVE;
+ compare_obj_export_to_golden("io_tests/blend_geometry/cubes_with_textures.blend",
+ "io_tests/obj/cubes_with_textures_rel.obj",
+ "io_tests/obj/cubes_with_textures_rel.mtl",
+ _export.params);
+}
+
TEST_F(obj_exporter_regression_test, suzanne_all_data)
{
OBJExportParamsDefault _export;
- _export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
- _export.params.up_axis = OBJ_AXIS_Z_UP;
+ _export.params.forward_axis = IO_AXIS_Y;
+ _export.params.up_axis = IO_AXIS_Z;
_export.params.export_materials = false;
_export.params.export_smooth_groups = true;
compare_obj_export_to_golden("io_tests/blend_geometry/suzanne_all_data.blend",
@@ -480,9 +504,10 @@ TEST_F(obj_exporter_regression_test, all_curves_as_nurbs)
TEST_F(obj_exporter_regression_test, all_objects)
{
OBJExportParamsDefault _export;
- _export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
- _export.params.up_axis = OBJ_AXIS_Z_UP;
+ _export.params.forward_axis = IO_AXIS_Y;
+ _export.params.up_axis = IO_AXIS_Z;
_export.params.export_smooth_groups = true;
+ _export.params.export_colors = true;
compare_obj_export_to_golden("io_tests/blend_scene/all_objects.blend",
"io_tests/obj/all_objects.obj",
"io_tests/obj/all_objects.mtl",
@@ -492,8 +517,8 @@ TEST_F(obj_exporter_regression_test, all_objects)
TEST_F(obj_exporter_regression_test, all_objects_mat_groups)
{
OBJExportParamsDefault _export;
- _export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
- _export.params.up_axis = OBJ_AXIS_Z_UP;
+ _export.params.forward_axis = IO_AXIS_Y;
+ _export.params.up_axis = IO_AXIS_Z;
_export.params.export_smooth_groups = true;
_export.params.export_material_groups = true;
compare_obj_export_to_golden("io_tests/blend_scene/all_objects.blend",
diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh
index 6a821e0b1bf..7d3b41ed527 100644
--- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh
+++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh
@@ -11,13 +11,14 @@ struct OBJExportParamsDefault {
OBJExportParamsDefault()
{
params.filepath[0] = '\0';
+ params.file_base_for_tests[0] = '\0';
params.blen_filepath = "";
params.export_animation = false;
params.start_frame = 0;
params.end_frame = 1;
- params.forward_axis = OBJ_AXIS_NEGATIVE_Z_FORWARD;
- params.up_axis = OBJ_AXIS_Y_UP;
+ params.forward_axis = IO_AXIS_NEGATIVE_Z;
+ params.up_axis = IO_AXIS_Y;
params.scaling_factor = 1.f;
params.apply_modifiers = true;
@@ -25,7 +26,9 @@ struct OBJExportParamsDefault {
params.export_selected_objects = false;
params.export_uv = true;
params.export_normals = true;
+ params.export_colors = false;
params.export_materials = true;
+ params.path_mode = PATH_REFERENCE_AUTO;
params.export_triangulated_mesh = false;
params.export_curves_as_nurbs = false;
diff --git a/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc b/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc
new file mode 100644
index 00000000000..46e093bb8a7
--- /dev/null
+++ b/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#include "obj_import_string_utils.hh"
+
+#include "testing/testing.h"
+
+namespace blender::io::obj {
+
+#define EXPECT_STRREF_EQ(str1, str2) EXPECT_STREQ(str1, std::string(str2).c_str())
+
+TEST(obj_import_string_utils, read_next_line)
+{
+ std::string str = "abc\n \n\nline with \\\ncontinuation\nCRLF ending:\r\na";
+ StringRef s = str;
+ EXPECT_STRREF_EQ("abc", read_next_line(s));
+ EXPECT_STRREF_EQ(" ", read_next_line(s));
+ EXPECT_STRREF_EQ("", read_next_line(s));
+ EXPECT_STRREF_EQ("line with \\\ncontinuation", read_next_line(s));
+ EXPECT_STRREF_EQ("CRLF ending:\r", read_next_line(s));
+ EXPECT_STRREF_EQ("a", read_next_line(s));
+ EXPECT_TRUE(s.is_empty());
+}
+
+static StringRef drop_whitespace(StringRef s)
+{
+ return StringRef(drop_whitespace(s.begin(), s.end()), s.end());
+}
+static StringRef parse_int(StringRef s, int fallback, int &dst, bool skip_space = true)
+{
+ return StringRef(parse_int(s.begin(), s.end(), fallback, dst, skip_space), s.end());
+}
+static StringRef parse_float(StringRef s, float fallback, float &dst, bool skip_space = true)
+{
+ return StringRef(parse_float(s.begin(), s.end(), fallback, dst, skip_space), s.end());
+}
+
+TEST(obj_import_string_utils, drop_whitespace)
+{
+ /* Empty */
+ EXPECT_STRREF_EQ("", drop_whitespace(""));
+ /* Only whitespace */
+ EXPECT_STRREF_EQ("", drop_whitespace(" "));
+ EXPECT_STRREF_EQ("", drop_whitespace(" "));
+ EXPECT_STRREF_EQ("", drop_whitespace(" \t\n\r "));
+ /* Drops leading whitespace */
+ EXPECT_STRREF_EQ("a", drop_whitespace(" a"));
+ EXPECT_STRREF_EQ("a b", drop_whitespace(" a b"));
+ EXPECT_STRREF_EQ("a b ", drop_whitespace(" a b "));
+ /* No leading whitespace */
+ EXPECT_STRREF_EQ("c", drop_whitespace("c"));
+ /* Case with backslash, should be treated as whitespace */
+ EXPECT_STRREF_EQ("d", drop_whitespace(" \\ d"));
+}
+
+TEST(obj_import_string_utils, parse_int_valid)
+{
+ std::string str = "1 -10 \t 1234 1234567890 +7 123a";
+ StringRef s = str;
+ int val;
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(1, val);
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(-10, val);
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(1234, val);
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(1234567890, val);
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(7, val);
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(123, val);
+ EXPECT_STRREF_EQ("a", s);
+}
+
+TEST(obj_import_string_utils, parse_int_invalid)
+{
+ int val;
+ /* Invalid syntax */
+ EXPECT_STRREF_EQ("--123", parse_int("--123", -1, val));
+ EXPECT_EQ(val, -1);
+ EXPECT_STRREF_EQ("foobar", parse_int("foobar", -2, val));
+ EXPECT_EQ(val, -2);
+ /* Out of integer range */
+ EXPECT_STRREF_EQ(" a", parse_int("1234567890123 a", -3, val));
+ EXPECT_EQ(val, -3);
+ /* Has leading white-space when we don't expect it */
+ EXPECT_STRREF_EQ(" 1", parse_int(" 1", -4, val, false));
+ EXPECT_EQ(val, -4);
+}
+
+TEST(obj_import_string_utils, parse_float_valid)
+{
+ std::string str = "1 -10 123.5 -17.125 0.1 1e6 50.0e-1";
+ StringRef s = str;
+ float val;
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(1.0f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(-10.0f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(123.5f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(-17.125f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(0.1f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(1.0e6f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(5.0f, val);
+ EXPECT_TRUE(s.is_empty());
+}
+
+TEST(obj_import_string_utils, parse_float_invalid)
+{
+ float val;
+ /* Invalid syntax */
+ EXPECT_STRREF_EQ("_0", parse_float("_0", -1.0f, val));
+ EXPECT_EQ(val, -1.0f);
+ EXPECT_STRREF_EQ("..5", parse_float("..5", -2.0f, val));
+ EXPECT_EQ(val, -2.0f);
+ /* Out of float range. Current float parser (fast_float)
+ * clamps out of range numbers to +/- infinity, so this
+ * one gets a +inf instead of fallback -3.0. */
+ EXPECT_STRREF_EQ(" a", parse_float("9.0e500 a", -3.0f, val));
+ EXPECT_EQ(val, std::numeric_limits<float>::infinity());
+ /* Has leading white-space when we don't expect it */
+ EXPECT_STRREF_EQ(" 1", parse_float(" 1", -4.0f, val, false));
+ EXPECT_EQ(val, -4.0f);
+}
+
+} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc
index 3d34fb6f9c6..eeb81f5e23e 100644
--- a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc
+++ b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc
@@ -39,6 +39,7 @@ struct Expectation {
float3 vert_first, vert_last;
float3 normal_first;
float2 uv_first;
+ float4 color_first = {-1, -1, -1, -1};
};
class obj_importer_test : public BlendfileLoadingBaseTest {
@@ -55,8 +56,10 @@ class obj_importer_test : public BlendfileLoadingBaseTest {
OBJImportParams params;
params.clamp_size = 0;
- params.forward_axis = OBJ_AXIS_NEGATIVE_Z_FORWARD;
- params.up_axis = OBJ_AXIS_Y_UP;
+ params.forward_axis = IO_AXIS_NEGATIVE_Z;
+ params.up_axis = IO_AXIS_Y;
+ params.validate_meshes = true;
+ params.import_vertex_groups = false;
std::string obj_path = blender::tests::flags_test_asset_dir() + "/io_tests/obj/" + path;
strncpy(params.filepath, obj_path.c_str(), FILE_MAX - 1);
@@ -98,6 +101,15 @@ class obj_importer_test : public BlendfileLoadingBaseTest {
CustomData_get_layer(&mesh->ldata, CD_MLOOPUV));
float2 uv_first = mloopuv ? float2(mloopuv->uv) : float2(0, 0);
EXPECT_V2_NEAR(uv_first, exp.uv_first, 0.0001f);
+ if (exp.color_first.x >= 0) {
+ const float4 *colors = (const float4 *)(CustomData_get_layer(&mesh->vdata,
+ CD_PROP_COLOR));
+ EXPECT_TRUE(colors != nullptr);
+ EXPECT_V4_NEAR(colors[0], exp.color_first, 0.0001f);
+ }
+ else {
+ EXPECT_FALSE(CustomData_has_layer(&mesh->vdata, CD_PROP_COLOR));
+ }
}
if (object->type == OB_CURVES_LEGACY) {
Curve *curve = static_cast<Curve *>(DEG_get_evaluated_object(depsgraph, object)->data);
@@ -111,7 +123,7 @@ class obj_importer_test : public BlendfileLoadingBaseTest {
int endpoint = (nurb->flagu & CU_NURB_ENDPOINT) ? 1 : 0;
EXPECT_EQ(nurb->orderu, exp.mesh_totpoly_or_curve_order);
EXPECT_EQ(endpoint, exp.mesh_totedge_or_curve_endp);
- // Cyclic flag is not set by the importer yet
+ /* Cyclic flag is not set by the importer yet. */
// int cyclic = (nurb->flagu & CU_NURB_CYCLIC) ? 1 : 0;
// EXPECT_EQ(cyclic, exp.mesh_totloop_or_curve_cyclic);
}
@@ -133,7 +145,7 @@ TEST_F(obj_importer_test, import_cube)
{
Expectation expect[] = {
{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
- {"OBNew object",
+ {"OBcube",
OB_MESH,
8,
12,
@@ -168,7 +180,7 @@ TEST_F(obj_importer_test, import_nurbs)
{
Expectation expect[] = {
{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
- {"OBNew object",
+ {"OBnurbs",
OB_CURVES_LEGACY,
12,
0,
@@ -184,7 +196,7 @@ TEST_F(obj_importer_test, import_nurbs_curves)
{
Expectation expect[] = {
{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
- {"OBNew object", OB_CURVES_LEGACY, 4, 0, 4, 0, float3(2, -2, 0), float3(-2, -2, 0)},
+ {"OBnurbs_curves", OB_CURVES_LEGACY, 4, 0, 4, 0, float3(2, -2, 0), float3(-2, -2, 0)},
{"OBNurbsCurveDiffWeights",
OB_CURVES_LEGACY,
4,
@@ -211,7 +223,7 @@ TEST_F(obj_importer_test, import_nurbs_cyclic)
{
Expectation expect[] = {
{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
- {"OBNew object",
+ {"OBnurbs_cyclic",
OB_CURVES_LEGACY,
31,
0,
@@ -262,7 +274,7 @@ TEST_F(obj_importer_test, import_materials)
{
Expectation expect[] = {
{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
- {"OBNew object", OB_MESH, 8, 12, 6, 24, float3(-1, -1, 1), float3(1, -1, -1)},
+ {"OBmaterials", OB_MESH, 8, 12, 6, 24, float3(-1, -1, 1), float3(1, -1, -1)},
};
import_and_check("materials.obj", expect, std::size(expect), 4);
}
@@ -333,7 +345,7 @@ TEST_F(obj_importer_test, import_invalid_syntax)
{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
{"OBObjectWithAReallyLongNameToCheckHowImportHandlesNamesThatAreLon",
OB_MESH,
- 10, /* Note: right now parses some invalid obj syntax as valid vertices. */
+ 10, /* NOTE: right now parses some invalid obj syntax as valid vertices. */
3,
1,
3,
@@ -434,7 +446,17 @@ TEST_F(obj_importer_test, import_all_objects)
float3(16, 1, -1),
float3(14, 1, 1),
float3(0, 0, 1)},
- {"OBVColCube", OB_MESH, 8, 13, 7, 26, float3(13, 1, -1), float3(11, 1, 1), float3(0, 0, 1)},
+ {"OBVColCube",
+ OB_MESH,
+ 8,
+ 13,
+ 7,
+ 26,
+ float3(13, 1, -1),
+ float3(11, 1, 1),
+ float3(0, 0, 1),
+ float2(0, 0),
+ float4(0.0f, 0.002125f, 1.0f, 1.0f)},
{"OBUVCube",
OB_MESH,
8,
@@ -490,4 +512,103 @@ TEST_F(obj_importer_test, import_all_objects)
import_and_check("all_objects.obj", expect, std::size(expect), 7);
}
+TEST_F(obj_importer_test, import_cubes_vertex_colors)
+{
+ Expectation expect[] = {
+ {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
+ {"OBCubeVertexByte",
+ OB_MESH,
+ 8,
+ 12,
+ 6,
+ 24,
+ float3(1.0f, 1.0f, -1.0f),
+ float3(-1.0f, -1.0f, 1.0f),
+ float3(0, 0, 0),
+ float2(0, 0),
+ float4(0.846873f, 0.027321f, 0.982123f, 1.0f)},
+ {"OBCubeVertexFloat",
+ OB_MESH,
+ 8,
+ 12,
+ 6,
+ 24,
+ float3(3.392028f, 1.0f, -1.0f),
+ float3(1.392028f, -1.0f, 1.0f),
+ float3(0, 0, 0),
+ float2(0, 0),
+ float4(49.99467f, 0.027321f, 0.982123f, 1.0f)},
+ {"OBCubeCornerByte",
+ OB_MESH,
+ 8,
+ 12,
+ 6,
+ 24,
+ float3(1.0f, 1.0f, -3.812445f),
+ float3(-1.0f, -1.0f, -1.812445f),
+ float3(0, 0, 0),
+ float2(0, 0),
+ float4(0.89627f, 0.036889f, 0.47932f, 1.0f)},
+ {"OBCubeCornerFloat",
+ OB_MESH,
+ 8,
+ 12,
+ 6,
+ 24,
+ float3(3.481967f, 1.0f, -3.812445f),
+ float3(1.481967f, -1.0f, -1.812445f),
+ float3(0, 0, 0),
+ float2(0, 0),
+ float4(1.564582f, 0.039217f, 0.664309f, 1.0f)},
+ {"OBCubeMultiColorAttribs",
+ OB_MESH,
+ 8,
+ 12,
+ 6,
+ 24,
+ float3(-4.725068f, -1.0f, 1.0f),
+ float3(-2.725068f, 1.0f, -1.0f),
+ float3(0, 0, 0),
+ float2(0, 0),
+ float4(0.270498f, 0.47932f, 0.262251f, 1.0f)},
+ {"OBCubeNoColors",
+ OB_MESH,
+ 8,
+ 12,
+ 6,
+ 24,
+ float3(-4.550208f, -1.0f, -1.918042f),
+ float3(-2.550208f, 1.0f, -3.918042f)},
+ };
+ import_and_check("cubes_vertex_colors.obj", expect, std::size(expect), 0);
+}
+
+TEST_F(obj_importer_test, import_cubes_vertex_colors_mrgb)
+{
+ Expectation expect[] = {{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
+ {"OBCubeXYZRGB",
+ OB_MESH,
+ 8,
+ 12,
+ 6,
+ 24,
+ float3(1, 1, -1),
+ float3(-1, -1, 1),
+ float3(0, 0, 0),
+ float2(0, 0),
+ float4(0.6038f, 0.3185f, 0.1329f, 1.0f)},
+ {"OBCubeMRGB",
+ OB_MESH,
+ 8,
+ 12,
+ 6,
+ 24,
+ float3(4, 1, -1),
+ float3(2, -1, 1),
+ float3(0, 0, 0),
+ float2(0, 0),
+ float4(0.8714f, 0.6308f, 0.5271f, 1.0f)}};
+ import_and_check("cubes_vertex_colors_mrgb.obj", expect, std::size(expect), 0);
+}
+
} // namespace blender::io::obj