Cleanup: obj: simplify import/export syntax handling code

I want to add support for PBR materials extension to OBJ, but the way
current I/O code syntax handling was done made it quite cumbersome
to extend the number of MTL textures/parameters.

Simplify all that by removing FormatHandler template on "syntax"
that gets routed through keyword enums, and instead just have
simple `write_obj_*` and `write_mtl_*` functions.

Simplify MTLMaterial to not contain a map of textures (that is always
fully filled with all possible textures), instead now there's
a simple array. Rename `tex_map_XX` to `MTLTexMap`.

All this does not affect behavior or performance, but it does result
in 170 fewer lines of code, and saves a couple kilobytes of executable
size.
This commit is contained in:
Aras Pranckevicius 2022-08-29 21:10:16 +03:00
parent caa352bc45
commit 68f234b8ab
11 changed files with 370 additions and 541 deletions

View File

@ -44,7 +44,7 @@ static const char *DEFORM_GROUP_DISABLED = "off";
* So an empty material name is written. */
static const char *MATERIAL_GROUP_DISABLED = "";
void OBJWriter::write_vert_uv_normal_indices(FormatHandler<eFileType::OBJ> &fh,
void OBJWriter::write_vert_uv_normal_indices(FormatHandler &fh,
const IndexOffsets &offsets,
Span<int> vert_indices,
Span<int> uv_indices,
@ -57,12 +57,12 @@ void OBJWriter::write_vert_uv_normal_indices(FormatHandler<eFileType::OBJ> &fh,
const int uv_offset = offsets.uv_vertex_offset + 1;
const int normal_offset = offsets.normal_offset + 1;
const int n = vert_indices.size();
fh.write<eOBJSyntaxElement::poly_element_begin>();
fh.write_obj_poly_begin();
if (!flip) {
for (int j = 0; j < n; ++j) {
fh.write<eOBJSyntaxElement::vertex_uv_normal_indices>(vert_indices[j] + vertex_offset,
uv_indices[j] + uv_offset,
normal_indices[j] + normal_offset);
fh.write_obj_poly_v_uv_normal(vert_indices[j] + vertex_offset,
uv_indices[j] + uv_offset,
normal_indices[j] + normal_offset);
}
}
else {
@ -71,15 +71,15 @@ void OBJWriter::write_vert_uv_normal_indices(FormatHandler<eFileType::OBJ> &fh,
* then go backwards. Same logic in other write_*_indices functions below. */
for (int k = 0; k < n; ++k) {
int j = k == 0 ? 0 : n - k;
fh.write<eOBJSyntaxElement::vertex_uv_normal_indices>(vert_indices[j] + vertex_offset,
uv_indices[j] + uv_offset,
normal_indices[j] + normal_offset);
fh.write_obj_poly_v_uv_normal(vert_indices[j] + vertex_offset,
uv_indices[j] + uv_offset,
normal_indices[j] + normal_offset);
}
}
fh.write<eOBJSyntaxElement::poly_element_end>();
fh.write_obj_poly_end();
}
void OBJWriter::write_vert_normal_indices(FormatHandler<eFileType::OBJ> &fh,
void OBJWriter::write_vert_normal_indices(FormatHandler &fh,
const IndexOffsets &offsets,
Span<int> vert_indices,
Span<int> /*uv_indices*/,
@ -90,24 +90,24 @@ void OBJWriter::write_vert_normal_indices(FormatHandler<eFileType::OBJ> &fh,
const int vertex_offset = offsets.vertex_offset + 1;
const int normal_offset = offsets.normal_offset + 1;
const int n = vert_indices.size();
fh.write<eOBJSyntaxElement::poly_element_begin>();
fh.write_obj_poly_begin();
if (!flip) {
for (int j = 0; j < n; ++j) {
fh.write<eOBJSyntaxElement::vertex_normal_indices>(vert_indices[j] + vertex_offset,
normal_indices[j] + normal_offset);
fh.write_obj_poly_v_normal(vert_indices[j] + vertex_offset,
normal_indices[j] + normal_offset);
}
}
else {
for (int k = 0; k < n; ++k) {
int j = k == 0 ? 0 : n - k;
fh.write<eOBJSyntaxElement::vertex_normal_indices>(vert_indices[j] + vertex_offset,
normal_indices[j] + normal_offset);
fh.write_obj_poly_v_normal(vert_indices[j] + vertex_offset,
normal_indices[j] + normal_offset);
}
}
fh.write<eOBJSyntaxElement::poly_element_end>();
fh.write_obj_poly_end();
}
void OBJWriter::write_vert_uv_indices(FormatHandler<eFileType::OBJ> &fh,
void OBJWriter::write_vert_uv_indices(FormatHandler &fh,
const IndexOffsets &offsets,
Span<int> vert_indices,
Span<int> uv_indices,
@ -118,24 +118,22 @@ void OBJWriter::write_vert_uv_indices(FormatHandler<eFileType::OBJ> &fh,
const int vertex_offset = offsets.vertex_offset + 1;
const int uv_offset = offsets.uv_vertex_offset + 1;
const int n = vert_indices.size();
fh.write<eOBJSyntaxElement::poly_element_begin>();
fh.write_obj_poly_begin();
if (!flip) {
for (int j = 0; j < n; ++j) {
fh.write<eOBJSyntaxElement::vertex_uv_indices>(vert_indices[j] + vertex_offset,
uv_indices[j] + uv_offset);
fh.write_obj_poly_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset);
}
}
else {
for (int k = 0; k < n; ++k) {
int j = k == 0 ? 0 : n - k;
fh.write<eOBJSyntaxElement::vertex_uv_indices>(vert_indices[j] + vertex_offset,
uv_indices[j] + uv_offset);
fh.write_obj_poly_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset);
}
}
fh.write<eOBJSyntaxElement::poly_element_end>();
fh.write_obj_poly_end();
}
void OBJWriter::write_vert_indices(FormatHandler<eFileType::OBJ> &fh,
void OBJWriter::write_vert_indices(FormatHandler &fh,
const IndexOffsets &offsets,
Span<int> vert_indices,
Span<int> /*uv_indices*/,
@ -144,27 +142,27 @@ void OBJWriter::write_vert_indices(FormatHandler<eFileType::OBJ> &fh,
{
const int vertex_offset = offsets.vertex_offset + 1;
const int n = vert_indices.size();
fh.write<eOBJSyntaxElement::poly_element_begin>();
fh.write_obj_poly_begin();
if (!flip) {
for (int j = 0; j < n; ++j) {
fh.write<eOBJSyntaxElement::vertex_indices>(vert_indices[j] + vertex_offset);
fh.write_obj_poly_v(vert_indices[j] + vertex_offset);
}
}
else {
for (int k = 0; k < n; ++k) {
int j = k == 0 ? 0 : n - k;
fh.write<eOBJSyntaxElement::vertex_indices>(vert_indices[j] + vertex_offset);
fh.write_obj_poly_v(vert_indices[j] + vertex_offset);
}
}
fh.write<eOBJSyntaxElement::poly_element_end>();
fh.write_obj_poly_end();
}
void OBJWriter::write_header() const
{
using namespace std::string_literals;
FormatHandler<eFileType::OBJ> fh;
fh.write<eOBJSyntaxElement::string>("# Blender "s + BKE_blender_version_string() + "\n");
fh.write<eOBJSyntaxElement::string>("# www.blender.org\n");
FormatHandler fh;
fh.write_string("# Blender "s + BKE_blender_version_string());
fh.write_string("# www.blender.org");
fh.write_to_file(outfile_);
}
@ -174,8 +172,8 @@ void OBJWriter::write_mtllib_name(const StringRefNull mtl_filepath) const
char mtl_file_name[FILE_MAXFILE];
char mtl_dir_name[FILE_MAXDIR];
BLI_split_dirfile(mtl_filepath.data(), mtl_dir_name, mtl_file_name, FILE_MAXDIR, FILE_MAXFILE);
FormatHandler<eFileType::OBJ> fh;
fh.write<eOBJSyntaxElement::mtllib>(mtl_file_name);
FormatHandler fh;
fh.write_obj_mtllib(mtl_file_name);
fh.write_to_file(outfile_);
}
@ -184,18 +182,17 @@ static void spaces_to_underscores(std::string &r_name)
std::replace(r_name.begin(), r_name.end(), ' ', '_');
}
void OBJWriter::write_object_name(FormatHandler<eFileType::OBJ> &fh,
const OBJMesh &obj_mesh_data) const
void OBJWriter::write_object_name(FormatHandler &fh, const OBJMesh &obj_mesh_data) const
{
std::string object_name = obj_mesh_data.get_object_name();
spaces_to_underscores(object_name);
if (export_params_.export_object_groups) {
std::string mesh_name = obj_mesh_data.get_object_mesh_name();
spaces_to_underscores(mesh_name);
fh.write<eOBJSyntaxElement::object_group>(object_name + "_" + mesh_name);
fh.write_obj_group(object_name + "_" + mesh_name);
return;
}
fh.write<eOBJSyntaxElement::object_name>(object_name);
fh.write_obj_object(object_name);
}
/* Split up large meshes into multi-threaded jobs; each job processes
@ -213,9 +210,7 @@ static int calc_chunk_count(int count)
* will be written into the final /fh/ buffer at the end.
*/
template<typename Function>
void obj_parallel_chunked_output(FormatHandler<eFileType::OBJ> &fh,
int tot_count,
const Function &function)
void obj_parallel_chunked_output(FormatHandler &fh, int tot_count, const Function &function)
{
if (tot_count <= 0) {
return;
@ -231,7 +226,7 @@ void obj_parallel_chunked_output(FormatHandler<eFileType::OBJ> &fh,
return;
}
/* Give each chunk its own temporary output buffer, and process them in parallel. */
std::vector<FormatHandler<eFileType::OBJ>> buffers(chunk_count);
std::vector<FormatHandler> buffers(chunk_count);
blender::threading::parallel_for(IndexRange(chunk_count), 1, [&](IndexRange range) {
for (const int r : range) {
int i_start = r * chunk_size;
@ -248,7 +243,7 @@ void obj_parallel_chunked_output(FormatHandler<eFileType::OBJ> &fh,
}
}
void OBJWriter::write_vertex_coords(FormatHandler<eFileType::OBJ> &fh,
void OBJWriter::write_vertex_coords(FormatHandler &fh,
const OBJMesh &obj_mesh_data,
bool write_colors) const
{
@ -265,41 +260,40 @@ void OBJWriter::write_vertex_coords(FormatHandler<eFileType::OBJ> &fh,
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) {
obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &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]);
buf.write_obj_vertex_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) {
obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &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]);
buf.write_obj_vertex(vertex[0], vertex[1], vertex[2]);
});
}
}
void OBJWriter::write_uv_coords(FormatHandler<eFileType::OBJ> &fh, OBJMesh &r_obj_mesh_data) const
void OBJWriter::write_uv_coords(FormatHandler &fh, OBJMesh &r_obj_mesh_data) const
{
const Vector<float2> &uv_coords = r_obj_mesh_data.get_uv_coords();
const int tot_count = uv_coords.size();
obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) {
obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) {
const float2 &uv_vertex = uv_coords[i];
buf.write<eOBJSyntaxElement::uv_vertex_coords>(uv_vertex[0], uv_vertex[1]);
buf.write_obj_uv(uv_vertex[0], uv_vertex[1]);
});
}
void OBJWriter::write_poly_normals(FormatHandler<eFileType::OBJ> &fh, OBJMesh &obj_mesh_data)
void OBJWriter::write_poly_normals(FormatHandler &fh, OBJMesh &obj_mesh_data)
{
/* Poly normals should be calculated earlier via store_normal_coords_and_indices. */
const Vector<float3> &normal_coords = obj_mesh_data.get_normal_coords();
const int tot_count = normal_coords.size();
obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) {
obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) {
const float3 &normal = normal_coords[i];
buf.write<eOBJSyntaxElement::normal>(normal[0], normal[1], normal[2]);
buf.write_obj_normal(normal[0], normal[1], normal[2]);
});
}
@ -334,7 +328,7 @@ static int get_smooth_group(const OBJMesh &mesh, const OBJExportParams &params,
return group;
}
void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
void OBJWriter::write_poly_elements(FormatHandler &fh,
const IndexOffsets &offsets,
const OBJMesh &obj_mesh_data,
std::function<const char *(int)> matname_fn)
@ -346,7 +340,7 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
const int tot_deform_groups = obj_mesh_data.tot_deform_groups();
threading::EnumerableThreadSpecific<Vector<float>> group_weights;
obj_parallel_chunked_output(fh, tot_polygons, [&](FormatHandler<eFileType::OBJ> &buf, int idx) {
obj_parallel_chunked_output(fh, tot_polygons, [&](FormatHandler &buf, int idx) {
/* Polygon order for writing into the file is not necessarily the same
* as order in the mesh; it will be sorted by material indices. Remap current
* and previous indices here according to the order. */
@ -362,7 +356,7 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
const int prev_group = get_smooth_group(obj_mesh_data, export_params_, prev_i);
const int group = get_smooth_group(obj_mesh_data, export_params_, i);
if (group != prev_group) {
buf.write<eOBJSyntaxElement::smooth_group>(group);
buf.write_obj_smooth(group);
}
}
@ -375,9 +369,8 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
prev_i, local_weights);
const int16_t group = obj_mesh_data.get_poly_deform_group_index(i, local_weights);
if (group != prev_group) {
buf.write<eOBJSyntaxElement::object_group>(
group == NOT_FOUND ? DEFORM_GROUP_DISABLED :
obj_mesh_data.get_poly_deform_group_name(group));
buf.write_obj_group(group == NOT_FOUND ? DEFORM_GROUP_DISABLED :
obj_mesh_data.get_poly_deform_group_name(group));
}
}
@ -387,7 +380,7 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
const int16_t mat = obj_mesh_data.ith_poly_matnr(i);
if (mat != prev_mat) {
if (mat == NOT_FOUND) {
buf.write<eOBJSyntaxElement::poly_usemtl>(MATERIAL_GROUP_DISABLED);
buf.write_obj_usemtl(MATERIAL_GROUP_DISABLED);
}
else {
const char *mat_name = matname_fn(mat);
@ -397,9 +390,9 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
if (export_params_.export_material_groups) {
std::string object_name = obj_mesh_data.get_object_name();
spaces_to_underscores(object_name);
fh.write<eOBJSyntaxElement::object_group>(object_name + "_" + mat_name);
fh.write_obj_group(object_name + "_" + mat_name);
}
buf.write<eOBJSyntaxElement::poly_usemtl>(mat_name);
buf.write_obj_usemtl(mat_name);
}
}
}
@ -414,7 +407,7 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
});
}
void OBJWriter::write_edges_indices(FormatHandler<eFileType::OBJ> &fh,
void OBJWriter::write_edges_indices(FormatHandler &fh,
const IndexOffsets &offsets,
const OBJMesh &obj_mesh_data) const
{
@ -426,13 +419,12 @@ void OBJWriter::write_edges_indices(FormatHandler<eFileType::OBJ> &fh,
if (!vertex_indices) {
continue;
}
fh.write<eOBJSyntaxElement::edge>((*vertex_indices)[0] + offsets.vertex_offset + 1,
(*vertex_indices)[1] + offsets.vertex_offset + 1);
fh.write_obj_edge((*vertex_indices)[0] + offsets.vertex_offset + 1,
(*vertex_indices)[1] + offsets.vertex_offset + 1);
}
}
void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh,
const OBJCurve &obj_nurbs_data) const
void OBJWriter::write_nurbs_curve(FormatHandler &fh, const OBJCurve &obj_nurbs_data) const
{
const int total_splines = obj_nurbs_data.total_splines();
for (int spline_idx = 0; spline_idx < total_splines; spline_idx++) {
@ -440,15 +432,14 @@ void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh,
for (int vertex_idx = 0; vertex_idx < total_vertices; vertex_idx++) {
const float3 vertex_coords = obj_nurbs_data.vertex_coordinates(
spline_idx, vertex_idx, export_params_.scaling_factor);
fh.write<eOBJSyntaxElement::vertex_coords>(
vertex_coords[0], vertex_coords[1], vertex_coords[2]);
fh.write_obj_vertex(vertex_coords[0], vertex_coords[1], vertex_coords[2]);
}
const char *nurbs_name = obj_nurbs_data.get_curve_name();
const int nurbs_degree = obj_nurbs_data.get_nurbs_degree(spline_idx);
fh.write<eOBJSyntaxElement::object_group>(nurbs_name);
fh.write<eOBJSyntaxElement::cstype>();
fh.write<eOBJSyntaxElement::nurbs_degree>(nurbs_degree);
fh.write_obj_group(nurbs_name);
fh.write_obj_cstype();
fh.write_obj_nurbs_degree(nurbs_degree);
/**
* The numbers written here are indices into the vertex coordinates written
* earlier, relative to the line that is going to be written.
@ -457,13 +448,13 @@ void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh,
* 0.0 1.0 -1 -2 -3 -4 -1 -2 -3 for a cyclic curve with 4 vertices.
*/
const int total_control_points = obj_nurbs_data.total_spline_control_points(spline_idx);
fh.write<eOBJSyntaxElement::curve_element_begin>();
fh.write_obj_curve_begin();
for (int i = 0; i < total_control_points; i++) {
/* "+1" to keep indices one-based, even if they're negative: i.e., -1 refers to the
* last vertex coordinate, -2 second last. */
fh.write<eOBJSyntaxElement::vertex_indices>(-((i % total_vertices) + 1));
fh.write_obj_poly_v(-((i % total_vertices) + 1));
}
fh.write<eOBJSyntaxElement::curve_element_end>();
fh.write_obj_curve_end();
/**
* In `parm u 0 0.1 ..` line:, (total control points + 2) equidistant numbers in the
@ -474,7 +465,7 @@ void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh,
const short flagsu = obj_nurbs_data.get_nurbs_flagu(spline_idx);
const bool cyclic = flagsu & CU_NURB_CYCLIC;
const bool endpoint = !cyclic && (flagsu & CU_NURB_ENDPOINT);
fh.write<eOBJSyntaxElement::nurbs_parameter_begin>();
fh.write_obj_nurbs_parm_begin();
for (int i = 1; i <= total_control_points + 2; i++) {
float parm = 1.0f * i / (total_control_points + 2 + 1);
if (endpoint) {
@ -485,11 +476,10 @@ void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh,
parm = 1;
}
}
fh.write<eOBJSyntaxElement::nurbs_parameters>(parm);
fh.write_obj_nurbs_parm(parm);
}
fh.write<eOBJSyntaxElement::nurbs_parameter_end>();
fh.write<eOBJSyntaxElement::nurbs_group_end>();
fh.write_obj_nurbs_parm_end();
fh.write_obj_nurbs_group_end();
}
}
@ -497,6 +487,18 @@ void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh,
/** \name .MTL writers.
* \{ */
static const char *tex_map_type_to_string[] = {
"map_Kd",
"map_Ks",
"map_Ns",
"map_d",
"map_refl",
"map_Ke",
"map_Bump",
};
BLI_STATIC_ASSERT(ARRAY_SIZE(tex_map_type_to_string) == (int)MTLTexMapType::Count,
"array size mismatch");
/**
* Convert #float3 to string of space-separated numbers, with no leading or trailing space.
* Only to be used in NON-performance-critical code.
@ -537,9 +539,9 @@ void MTLWriter::write_header(const char *blen_filepath)
const char *blen_basename = (blen_filepath && blen_filepath[0] != '\0') ?
BLI_path_basename(blen_filepath) :
"None";
fmt_handler_.write<eMTLSyntaxElement::string>("# Blender "s + BKE_blender_version_string() +
" MTL File: '" + blen_basename + "'\n");
fmt_handler_.write<eMTLSyntaxElement::string>("# www.blender.org\n");
fmt_handler_.write_string("# Blender "s + BKE_blender_version_string() + " MTL File: '" +
blen_basename + "'");
fmt_handler_.write_string("# www.blender.org");
}
StringRefNull MTLWriter::mtl_file_path() const
@ -552,67 +554,52 @@ void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl)
/* For various material properties, we only capture information
* coming from the texture, or the default value of the socket.
* When the texture is present, do not emit the default value. */
if (!mtl.tex_map_of_type(eMTLSyntaxElement::map_Ns).is_valid()) {
fmt_handler_.write<eMTLSyntaxElement::Ns>(mtl.Ns);
if (!mtl.tex_map_of_type(MTLTexMapType::Ns).is_valid()) {
fmt_handler_.write_mtl_float("Ns", mtl.Ns);
}
fmt_handler_.write<eMTLSyntaxElement::Ka>(mtl.Ka.x, mtl.Ka.y, mtl.Ka.z);
if (!mtl.tex_map_of_type(eMTLSyntaxElement::map_Kd).is_valid()) {
fmt_handler_.write<eMTLSyntaxElement::Kd>(mtl.Kd.x, mtl.Kd.y, mtl.Kd.z);
fmt_handler_.write_mtl_float3("Ka", mtl.Ka.x, mtl.Ka.y, mtl.Ka.z);
if (!mtl.tex_map_of_type(MTLTexMapType::Kd).is_valid()) {
fmt_handler_.write_mtl_float3("Kd", mtl.Kd.x, mtl.Kd.y, mtl.Kd.z);
}
if (!mtl.tex_map_of_type(eMTLSyntaxElement::map_Ks).is_valid()) {
fmt_handler_.write<eMTLSyntaxElement::Ks>(mtl.Ks.x, mtl.Ks.y, mtl.Ks.z);
if (!mtl.tex_map_of_type(MTLTexMapType::Ks).is_valid()) {
fmt_handler_.write_mtl_float3("Ks", mtl.Ks.x, mtl.Ks.y, mtl.Ks.z);
}
if (!mtl.tex_map_of_type(eMTLSyntaxElement::map_Ke).is_valid()) {
fmt_handler_.write<eMTLSyntaxElement::Ke>(mtl.Ke.x, mtl.Ke.y, mtl.Ke.z);
if (!mtl.tex_map_of_type(MTLTexMapType::Ke).is_valid()) {
fmt_handler_.write_mtl_float3("Ke", mtl.Ke.x, mtl.Ke.y, mtl.Ke.z);
}
fmt_handler_.write<eMTLSyntaxElement::Ni>(mtl.Ni);
if (!mtl.tex_map_of_type(eMTLSyntaxElement::map_d).is_valid()) {
fmt_handler_.write<eMTLSyntaxElement::d>(mtl.d);
fmt_handler_.write_mtl_float("Ni", mtl.Ni);
if (!mtl.tex_map_of_type(MTLTexMapType::d).is_valid()) {
fmt_handler_.write_mtl_float("d", mtl.d);
}
fmt_handler_.write<eMTLSyntaxElement::illum>(mtl.illum);
fmt_handler_.write_mtl_illum(mtl.illum);
}
void MTLWriter::write_texture_map(
const MTLMaterial &mtl_material,
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)
void MTLWriter::write_texture_map(const MTLMaterial &mtl_material,
MTLTexMapType texture_key,
const MTLTexMap &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. */
if (texture_map.value.translation != float3{0.0f, 0.0f, 0.0f}) {
options.append(" -o ").append(float3_to_string(texture_map.value.translation));
if (texture_map.translation != float3{0.0f, 0.0f, 0.0f}) {
options.append(" -o ").append(float3_to_string(texture_map.translation));
}
if (texture_map.value.scale != float3{1.0f, 1.0f, 1.0f}) {
options.append(" -s ").append(float3_to_string(texture_map.value.scale));
if (texture_map.scale != float3{1.0f, 1.0f, 1.0f}) {
options.append(" -s ").append(float3_to_string(texture_map.scale));
}
if (texture_map.key == eMTLSyntaxElement::map_Bump && mtl_material.map_Bump_strength > 0.0001f) {
if (texture_key == MTLTexMapType::bump && mtl_material.map_Bump_strength > 0.0001f) {
options.append(" -bm ").append(std::to_string(mtl_material.map_Bump_strength));
}
std::string path = path_reference(
texture_map.value.image_path.c_str(), blen_filedir, dest_dir, path_mode, &copy_set);
texture_map.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(), '\\', '/');
#define SYNTAX_DISPATCH(eMTLSyntaxElement) \
if (texture_map.key == eMTLSyntaxElement) { \
fmt_handler_.write<eMTLSyntaxElement>(options, path.c_str()); \
return; \
}
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Kd);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ks);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ns);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_d);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_refl);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ke);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Bump);
#undef SYNTAX_DISPATCH
BLI_assert(!"This map type was not written to the file.");
fmt_handler_.write_mtl_map(tex_map_type_to_string[(int)texture_key], options, path);
}
void MTLWriter::write_materials(const char *blen_filepath,
@ -633,14 +620,16 @@ void MTLWriter::write_materials(const char *blen_filepath,
[](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);
fmt_handler_.write_string("");
fmt_handler_.write_mtl_newmtl(mtlmat.name);
write_bsdf_properties(mtlmat);
for (const auto &tex : mtlmat.texture_maps.items()) {
if (!tex.value.is_valid()) {
for (int key = 0; key < (int)MTLTexMapType::Count; key++) {
const MTLTexMap &tex = mtlmat.texture_maps[key];
if (!tex.is_valid()) {
continue;
}
write_texture_map(mtlmat, tex, blen_filedir, dest_dir, path_mode, copy_set);
write_texture_map(
mtlmat, (MTLTexMapType)key, tex, blen_filedir, dest_dir, path_mode, copy_set);
}
}
path_reference_copy(copy_set);

View File

@ -66,7 +66,7 @@ class OBJWriter : NonMovable, NonCopyable {
/**
* Write object's name or group.
*/
void write_object_name(FormatHandler<eFileType::OBJ> &fh, const OBJMesh &obj_mesh_data) const;
void write_object_name(FormatHandler &fh, const OBJMesh &obj_mesh_data) const;
/**
* Write file name of Material Library in .OBJ file.
*/
@ -74,19 +74,19 @@ class OBJWriter : NonMovable, NonCopyable {
/**
* 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,
void write_vertex_coords(FormatHandler &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.
*/
void write_uv_coords(FormatHandler<eFileType::OBJ> &fh, OBJMesh &obj_mesh_data) const;
void write_uv_coords(FormatHandler &fh, OBJMesh &obj_mesh_data) const;
/**
* Write loop normals for smooth-shaded polygons, and polygon normals otherwise, as "vn x y z".
* \note Normal indices ares stored here, but written with polygons later.
*/
void write_poly_normals(FormatHandler<eFileType::OBJ> &fh, OBJMesh &obj_mesh_data);
void write_poly_normals(FormatHandler &fh, OBJMesh &obj_mesh_data);
/**
* Write polygon elements with at least vertex indices, and conditionally with UV vertex
* indices and polygon normal indices. Also write groups: smooth, vertex, material.
@ -94,23 +94,23 @@ class OBJWriter : NonMovable, NonCopyable {
* name used in the .obj file.
* \note UV indices were stored while writing UV vertices.
*/
void write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
void write_poly_elements(FormatHandler &fh,
const IndexOffsets &offsets,
const OBJMesh &obj_mesh_data,
std::function<const char *(int)> matname_fn);
/**
* Write loose edges of a mesh as "l v1 v2".
*/
void write_edges_indices(FormatHandler<eFileType::OBJ> &fh,
void write_edges_indices(FormatHandler &fh,
const IndexOffsets &offsets,
const OBJMesh &obj_mesh_data) const;
/**
* Write a NURBS curve to the .OBJ file in parameter form.
*/
void write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh, const OBJCurve &obj_nurbs_data) const;
void write_nurbs_curve(FormatHandler &fh, const OBJCurve &obj_nurbs_data) const;
private:
using func_vert_uv_normal_indices = void (OBJWriter::*)(FormatHandler<eFileType::OBJ> &fh,
using func_vert_uv_normal_indices = void (OBJWriter::*)(FormatHandler &fh,
const IndexOffsets &offsets,
Span<int> vert_indices,
Span<int> uv_indices,
@ -124,7 +124,7 @@ class OBJWriter : NonMovable, NonCopyable {
/**
* Write one line of polygon indices as "f v1/vt1/vn1 v2/vt2/vn2 ...".
*/
void write_vert_uv_normal_indices(FormatHandler<eFileType::OBJ> &fh,
void write_vert_uv_normal_indices(FormatHandler &fh,
const IndexOffsets &offsets,
Span<int> vert_indices,
Span<int> uv_indices,
@ -133,7 +133,7 @@ class OBJWriter : NonMovable, NonCopyable {
/**
* Write one line of polygon indices as "f v1//vn1 v2//vn2 ...".
*/
void write_vert_normal_indices(FormatHandler<eFileType::OBJ> &fh,
void write_vert_normal_indices(FormatHandler &fh,
const IndexOffsets &offsets,
Span<int> vert_indices,
Span<int> /*uv_indices*/,
@ -142,7 +142,7 @@ class OBJWriter : NonMovable, NonCopyable {
/**
* Write one line of polygon indices as "f v1/vt1 v2/vt2 ...".
*/
void write_vert_uv_indices(FormatHandler<eFileType::OBJ> &fh,
void write_vert_uv_indices(FormatHandler &fh,
const IndexOffsets &offsets,
Span<int> vert_indices,
Span<int> uv_indices,
@ -151,7 +151,7 @@ class OBJWriter : NonMovable, NonCopyable {
/**
* Write one line of polygon indices as "f v1 v2 ...".
*/
void write_vert_indices(FormatHandler<eFileType::OBJ> &fh,
void write_vert_indices(FormatHandler &fh,
const IndexOffsets &offsets,
Span<int> vert_indices,
Span<int> /*uv_indices*/,
@ -164,7 +164,7 @@ class OBJWriter : NonMovable, NonCopyable {
*/
class MTLWriter : NonMovable, NonCopyable {
private:
FormatHandler<eFileType::MTL> fmt_handler_;
FormatHandler fmt_handler_;
FILE *outfile_;
std::string mtl_filepath_;
Vector<MTLMaterial> mtlmaterials_;
@ -208,7 +208,8 @@ 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,
MTLTexMapType texture_key,
const MTLTexMap &texture_map,
const char *blen_filedir,
const char *dest_dir,
ePathReferenceMode mode,

View File

@ -23,268 +23,24 @@
namespace blender::io::obj {
enum class eFileType {
OBJ,
MTL,
};
enum class eOBJSyntaxElement {
vertex_coords,
vertex_coords_color,
uv_vertex_coords,
normal,
poly_element_begin,
vertex_uv_normal_indices,
vertex_normal_indices,
vertex_uv_indices,
vertex_indices,
poly_element_end,
poly_usemtl,
edge,
cstype,
nurbs_degree,
curve_element_begin,
curve_element_end,
nurbs_parameter_begin,
nurbs_parameters,
nurbs_parameter_end,
nurbs_group_end,
new_line,
mtllib,
smooth_group,
object_group,
object_name,
/* Use rarely. New line is NOT included for string. */
string,
};
enum class eMTLSyntaxElement {
newmtl,
Ni,
d,
Ns,
illum,
Ka,
Kd,
Ks,
Ke,
map_Kd,
map_Ks,
map_Ns,
map_d,
map_refl,
map_Ke,
map_Bump,
/* Use rarely. New line is NOT included for string. */
string,
};
template<eFileType filetype> struct FileTypeTraits;
/* Used to prevent mixing of say OBJ file format with MTL syntax elements. */
template<> struct FileTypeTraits<eFileType::OBJ> {
using SyntaxType = eOBJSyntaxElement;
};
template<> struct FileTypeTraits<eFileType::MTL> {
using SyntaxType = eMTLSyntaxElement;
};
struct FormattingSyntax {
/* Formatting syntax with the file format key like `newmtl %s\n`. */
const char *fmt = nullptr;
/* Number of arguments needed by the syntax. */
const int total_args = 0;
/* Whether types of the given arguments are accepted by the syntax above. Fail to compile by
* default.
*/
const bool are_types_valid = false;
};
/**
* Type dependent but always false. Use to add a `constexpr` conditional compile-time error.
*/
template<typename T> struct always_false : std::false_type {
};
template<typename... T>
constexpr bool is_type_float = (... && std::is_floating_point_v<std::decay_t<T>>);
template<typename... T>
constexpr bool is_type_integral = (... && std::is_integral_v<std::decay_t<T>>);
template<typename... T>
constexpr bool is_type_string_related = (... && std::is_constructible_v<std::string, T>);
/* GCC (at least 9.3) while compiling the obj_exporter_tests.cc with optimizations on,
* results in "obj_export_io.hh:205:18: warning: %s directive output truncated writing 34 bytes
* into a region of size 6" and similar warnings. Yes the output is truncated, and that is covered
* as an edge case by tests on purpose. */
#if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wformat-truncation"
#endif
template<typename... T>
constexpr FormattingSyntax syntax_elem_to_formatting(const eOBJSyntaxElement key)
{
switch (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...>};
}
case eOBJSyntaxElement::normal: {
return {"vn {:.4f} {:.4f} {:.4f}\n", 3, is_type_float<T...>};
}
case eOBJSyntaxElement::poly_element_begin: {
return {"f", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::vertex_uv_normal_indices: {
return {" {}/{}/{}", 3, is_type_integral<T...>};
}
case eOBJSyntaxElement::vertex_normal_indices: {
return {" {}//{}", 2, is_type_integral<T...>};
}
case eOBJSyntaxElement::vertex_uv_indices: {
return {" {}/{}", 2, is_type_integral<T...>};
}
case eOBJSyntaxElement::vertex_indices: {
return {" {}", 1, is_type_integral<T...>};
}
case eOBJSyntaxElement::poly_usemtl: {
return {"usemtl {}\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::edge: {
return {"l {} {}\n", 2, is_type_integral<T...>};
}
case eOBJSyntaxElement::cstype: {
return {"cstype bspline\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_degree: {
return {"deg {}\n", 1, is_type_integral<T...>};
}
case eOBJSyntaxElement::curve_element_begin: {
return {"curv 0.0 1.0", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_parameter_begin: {
return {"parm u 0.0", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_parameters: {
return {" {:.6f}", 1, is_type_float<T...>};
}
case eOBJSyntaxElement::nurbs_parameter_end: {
return {" 1.0\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_group_end: {
return {"end\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::poly_element_end: {
ATTR_FALLTHROUGH;
}
case eOBJSyntaxElement::curve_element_end: {
ATTR_FALLTHROUGH;
}
case eOBJSyntaxElement::new_line: {
return {"\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::mtllib: {
return {"mtllib {}\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::smooth_group: {
return {"s {}\n", 1, is_type_integral<T...>};
}
case eOBJSyntaxElement::object_group: {
return {"g {}\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::object_name: {
return {"o {}\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::string: {
return {"{}", 1, is_type_string_related<T...>};
}
}
}
template<typename... T>
constexpr FormattingSyntax syntax_elem_to_formatting(const eMTLSyntaxElement key)
{
switch (key) {
case eMTLSyntaxElement::newmtl: {
return {"newmtl {}\n", 1, is_type_string_related<T...>};
}
case eMTLSyntaxElement::Ni: {
return {"Ni {:.6f}\n", 1, is_type_float<T...>};
}
case eMTLSyntaxElement::d: {
return {"d {:.6f}\n", 1, is_type_float<T...>};
}
case eMTLSyntaxElement::Ns: {
return {"Ns {:.6f}\n", 1, is_type_float<T...>};
}
case eMTLSyntaxElement::illum: {
return {"illum {}\n", 1, is_type_integral<T...>};
}
case eMTLSyntaxElement::Ka: {
return {"Ka {:.6f} {:.6f} {:.6f}\n", 3, is_type_float<T...>};
}
case eMTLSyntaxElement::Kd: {
return {"Kd {:.6f} {:.6f} {:.6f}\n", 3, is_type_float<T...>};
}
case eMTLSyntaxElement::Ks: {
return {"Ks {:.6f} {:.6f} {:.6f}\n", 3, is_type_float<T...>};
}
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. */
case eMTLSyntaxElement::map_Kd: {
return {"map_Kd{} {}\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Ks: {
return {"map_Ks{} {}\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Ns: {
return {"map_Ns{} {}\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_d: {
return {"map_d{} {}\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_refl: {
return {"map_refl{} {}\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Ke: {
return {"map_Ke{} {}\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Bump: {
return {"map_Bump{} {}\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::string: {
return {"{}", 1, is_type_string_related<T...>};
}
}
}
#if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic pop
#endif
/**
* File format and syntax agnostic file buffer writer.
* File buffer writer.
* All writes are done into an internal chunked memory buffer
* (list of default 64 kilobyte blocks).
* Call write_fo_file once in a while to write the memory buffer(s)
* into the given file.
*/
template<eFileType filetype, size_t buffer_chunk_size = 64 * 1024>
class FormatHandler : NonCopyable, NonMovable {
private:
typedef std::vector<char> VectorChar;
std::vector<VectorChar> blocks_;
size_t buffer_chunk_size_;
public:
FormatHandler(size_t buffer_chunk_size = 64 * 1024) : buffer_chunk_size_(buffer_chunk_size)
{
}
/* Write contents to the buffer(s) into a file, and clear the buffers. */
void write_to_file(FILE *f)
{
@ -305,7 +61,7 @@ class FormatHandler : NonCopyable, NonMovable {
return blocks_.size();
}
void append_from(FormatHandler<filetype, buffer_chunk_size> &v)
void append_from(FormatHandler &v)
{
blocks_.insert(blocks_.end(),
std::make_move_iterator(v.blocks_.begin()),
@ -313,24 +69,132 @@ class FormatHandler : NonCopyable, NonMovable {
v.blocks_.clear();
}
/**
* Example invocation: `writer->write<eMTLSyntaxElement::newmtl>("foo")`.
*
* \param key: Must match what the instance's filetype expects; i.e., `eMTLSyntaxElement` for
* `eFileType::MTL`.
*/
template<typename FileTypeTraits<filetype>::SyntaxType key, typename... T>
constexpr void write(T &&...args)
void write_obj_vertex(float x, float y, float z)
{
/* Get format syntax, number of arguments expected and whether types of given arguments are
* valid.
*/
constexpr FormattingSyntax fmt_nargs_valid = syntax_elem_to_formatting<T...>(key);
BLI_STATIC_ASSERT(fmt_nargs_valid.are_types_valid &&
(sizeof...(T) == fmt_nargs_valid.total_args),
"Types of all arguments and the number of arguments should match what the "
"formatting specifies.");
write_impl(fmt_nargs_valid.fmt, std::forward<T>(args)...);
write_impl("v {:.6f} {:.6f} {:.6f}\n", x, y, z);
}
void write_obj_vertex_color(float x, float y, float z, float r, float g, float b)
{
write_impl("v {:.6f} {:.6f} {:.6f} {:.4f} {:.4f} {:.4f}\n", x, y, z, r, g, b);
}
void write_obj_uv(float x, float y)
{
write_impl("vt {:.6f} {:.6f}\n", x, y);
}
void write_obj_normal(float x, float y, float z)
{
write_impl("vn {:.4f} {:.4f} {:.4f}\n", x, y, z);
}
void write_obj_poly_begin()
{
write_impl("f");
}
void write_obj_poly_end()
{
write_obj_newline();
}
void write_obj_poly_v_uv_normal(int v, int uv, int n)
{
write_impl(" {}/{}/{}", v, uv, n);
}
void write_obj_poly_v_normal(int v, int n)
{
write_impl(" {}//{}", v, n);
}
void write_obj_poly_v_uv(int v, int uv)
{
write_impl(" {}/{}", v, uv);
}
void write_obj_poly_v(int v)
{
write_impl(" {}", v);
}
void write_obj_usemtl(StringRef s)
{
write_impl("usemtl {}\n", s);
}
void write_obj_mtllib(StringRef s)
{
write_impl("mtllib {}\n", s);
}
void write_obj_smooth(int s)
{
write_impl("s {}\n", s);
}
void write_obj_group(StringRef s)
{
write_impl("g {}\n", s);
}
void write_obj_object(StringRef s)
{
write_impl("o {}\n", s);
}
void write_obj_edge(int a, int b)
{
write_impl("l {} {}\n", a, b);
}
void write_obj_cstype()
{
write_impl("cstype bspline\n");
}
void write_obj_nurbs_degree(int deg)
{
write_impl("deg {}\n", deg);
}
void write_obj_curve_begin()
{
write_impl("curv 0.0 1.0");
}
void write_obj_curve_end()
{
write_obj_newline();
}
void write_obj_nurbs_parm_begin()
{
write_impl("parm u 0.0");
}
void write_obj_nurbs_parm(float v)
{
write_impl(" {:.6f}", v);
}
void write_obj_nurbs_parm_end()
{
write_impl(" 1.0\n");
}
void write_obj_nurbs_group_end()
{
write_impl("end\n");
}
void write_obj_newline()
{
write_impl("\n");
}
void write_mtl_newmtl(StringRef s)
{
write_impl("newmtl {}\n", s);
}
void write_mtl_float(const char *type, float v)
{
write_impl("{} {:.6f}\n", type, v);
}
void write_mtl_float3(const char *type, float r, float g, float b)
{
write_impl("{} {:.6f} {:.6f} {:.6f}\n", type, r, g, b);
}
void write_mtl_illum(int mode)
{
write_impl("illum {}\n", mode);
}
/* Note: options, if present, will have its own leading space. */
void write_mtl_map(const char *type, StringRef options, StringRef value)
{
write_impl("{}{} {}\n", type, options, value);
}
void write_string(StringRef s)
{
write_impl("{}\n", s);
}
private:
@ -340,7 +204,7 @@ class FormatHandler : NonCopyable, NonMovable {
{
if (blocks_.empty() || (blocks_.back().capacity() - blocks_.back().size() < at_least)) {
VectorChar &b = blocks_.emplace_back(VectorChar());
b.reserve(std::max(at_least, buffer_chunk_size));
b.reserve(std::max(at_least, buffer_chunk_size_));
}
}

View File

@ -22,6 +22,18 @@
namespace blender::io::obj {
const char *tex_map_type_to_socket_id[] = {
"Base Color",
"Specular",
"Roughness",
"Alpha",
"Metallic",
"Emission",
"Normal",
};
BLI_STATIC_ASSERT(ARRAY_SIZE(tex_map_type_to_socket_id) == (int)MTLTexMapType::Count,
"array size mismatch");
/**
* Copy a float property of the given type from the bNode to given buffer.
*/
@ -73,7 +85,7 @@ static void copy_property_from_node(const eNodeSocketDatatype property_type,
*/
static void linked_sockets_to_dest_id(const bNode *dest_node,
const nodes::NodeTreeRef &node_tree,
StringRefNull dest_socket_id,
const char *dest_socket_id,
Vector<const nodes::OutputSocketRef *> &r_linked_sockets)
{
r_linked_sockets.clear();
@ -84,7 +96,7 @@ static void linked_sockets_to_dest_id(const bNode *dest_node,
Span<const nodes::InputSocketRef *> dest_inputs = object_dest_nodes.first()->inputs();
const nodes::InputSocketRef *dest_socket = nullptr;
for (const nodes::InputSocketRef *curr_socket : dest_inputs) {
if (STREQ(curr_socket->bsocket()->identifier, dest_socket_id.c_str())) {
if (STREQ(curr_socket->bsocket()->identifier, dest_socket_id)) {
dest_socket = curr_socket;
break;
}
@ -269,12 +281,12 @@ static void store_image_textures(const nodes::NodeRef *bsdf_node,
* - finding "Strength" property of the node for `-bm` option.
*/
for (Map<const eMTLSyntaxElement, tex_map_XX>::MutableItem texture_map :
r_mtl_mat.texture_maps.items()) {
for (int key = 0; key < (int)MTLTexMapType::Count; ++key) {
MTLTexMap &value = r_mtl_mat.texture_maps[key];
Vector<const nodes::OutputSocketRef *> linked_sockets;
const bNode *normal_map_node{nullptr};
if (texture_map.key == eMTLSyntaxElement::map_Bump) {
if (key == (int)MTLTexMapType::bump) {
/* Find sockets linked to destination "Normal" socket in P-BSDF node. */
linked_sockets_to_dest_id(bnode, *node_tree, "Normal", linked_sockets);
/* Among the linked sockets, find Normal Map shader node. */
@ -285,7 +297,7 @@ static void store_image_textures(const nodes::NodeRef *bsdf_node,
}
else {
/* Skip emission map if emission strength is zero. */
if (texture_map.key == eMTLSyntaxElement::map_Ke) {
if (key == (int)MTLTexMapType::Ke) {
float emission_strength = 0.0f;
copy_property_from_node(SOCK_FLOAT, bnode, "Emission Strength", {&emission_strength, 1});
if (emission_strength == 0.0f) {
@ -293,8 +305,7 @@ static void store_image_textures(const nodes::NodeRef *bsdf_node,
}
}
/* Find sockets linked to the destination socket of interest, in P-BSDF node. */
linked_sockets_to_dest_id(
bnode, *node_tree, texture_map.value.dest_socket_id, linked_sockets);
linked_sockets_to_dest_id(bnode, *node_tree, tex_map_type_to_socket_id[key], linked_sockets);
}
/* Among the linked sockets, find Image Texture shader node. */
@ -317,10 +328,10 @@ static void store_image_textures(const nodes::NodeRef *bsdf_node,
}
/* Texture transform options. Only translation (origin offset, "-o") and scale
* ("-o") are supported. */
copy_property_from_node(SOCK_VECTOR, mapping, "Location", {texture_map.value.translation, 3});
copy_property_from_node(SOCK_VECTOR, mapping, "Scale", {texture_map.value.scale, 3});
copy_property_from_node(SOCK_VECTOR, mapping, "Location", {value.translation, 3});
copy_property_from_node(SOCK_VECTOR, mapping, "Scale", {value.scale, 3});
texture_map.value.image_path = tex_image_filepath;
value.image_path = tex_image_filepath;
}
}

View File

@ -6,36 +6,23 @@
#pragma once
#include "BLI_map.hh"
#include "BLI_math_vec_types.hh"
#include "DNA_node_types.h"
#include "obj_export_io.hh"
namespace blender {
template<> struct DefaultHash<io::obj::eMTLSyntaxElement> {
uint64_t operator()(const io::obj::eMTLSyntaxElement value) const
{
return static_cast<uint64_t>(value);
}
};
} // namespace blender
struct Material;
namespace blender::io::obj {
/**
* Generic container for texture node properties.
*/
struct tex_map_XX {
tex_map_XX(StringRef to_socket_id) : dest_socket_id(to_socket_id){};
enum class MTLTexMapType { Kd = 0, Ks, Ns, d, refl, Ke, bump, Count };
extern const char *tex_map_type_to_socket_id[];
struct MTLTexMap {
bool is_valid() const
{
return !image_path.empty();
}
/* Target socket which this texture node connects to. */
const std::string dest_socket_id;
float3 translation{0.0f};
float3 scale{1.0f};
/* Only Flat and Sphere projections are supported. */
@ -48,26 +35,13 @@ struct tex_map_XX {
* Container suited for storing Material data for/from a .MTL file.
*/
struct MTLMaterial {
MTLMaterial()
const MTLTexMap &tex_map_of_type(MTLTexMapType key) const
{
texture_maps.add(eMTLSyntaxElement::map_Kd, tex_map_XX("Base Color"));
texture_maps.add(eMTLSyntaxElement::map_Ks, tex_map_XX("Specular"));
texture_maps.add(eMTLSyntaxElement::map_Ns, tex_map_XX("Roughness"));
texture_maps.add(eMTLSyntaxElement::map_d, tex_map_XX("Alpha"));
texture_maps.add(eMTLSyntaxElement::map_refl, tex_map_XX("Metallic"));
texture_maps.add(eMTLSyntaxElement::map_Ke, tex_map_XX("Emission"));
texture_maps.add(eMTLSyntaxElement::map_Bump, tex_map_XX("Normal"));
return texture_maps[(int)key];
}
const tex_map_XX &tex_map_of_type(const eMTLSyntaxElement key) const
MTLTexMap &tex_map_of_type(MTLTexMapType key)
{
BLI_assert(texture_maps.contains(key));
return texture_maps.lookup(key);
}
tex_map_XX &tex_map_of_type(const eMTLSyntaxElement key)
{
BLI_assert(texture_maps.contains(key));
return texture_maps.lookup(key);
return texture_maps[(int)key];
}
std::string name;
@ -81,7 +55,7 @@ struct MTLMaterial {
float Ni{-1.0f};
float d{-1.0f};
int illum{-1};
Map<const eMTLSyntaxElement, tex_map_XX> texture_maps;
MTLTexMap texture_maps[(int)MTLTexMapType::Count];
/** Only used for Normal Map node: "map_Bump". */
float map_Bump_strength{-1.0f};
};

View File

@ -143,7 +143,7 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me
* we have to have the output text buffer for each object,
* and write them all into the file at the end. */
size_t count = exportable_as_mesh.size();
std::vector<FormatHandler<eFileType::OBJ>> buffers(count);
std::vector<FormatHandler> buffers(count);
/* Serial: gather material indices, ensure normals & edges. */
Vector<Vector<int>> mtlindices;
@ -242,7 +242,7 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me
static void write_nurbs_curve_objects(const Vector<std::unique_ptr<OBJCurve>> &exportable_as_nurbs,
const OBJWriter &obj_writer)
{
FormatHandler<eFileType::OBJ> fh;
FormatHandler fh;
/* #OBJCurve doesn't have any dynamically allocated memory, so it's fine
* to wait for #blender::Vector to clean the objects up. */
for (const std::unique_ptr<OBJCurve> &obj_curve : exportable_as_nurbs) {

View File

@ -592,36 +592,31 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
add_default_mtl_library();
}
static eMTLSyntaxElement mtl_line_start_to_enum(const char *&p, const char *end)
static MTLTexMapType mtl_line_start_to_texture_type(const char *&p, const char *end)
{
if (parse_keyword(p, end, "map_Kd")) {
return eMTLSyntaxElement::map_Kd;
return MTLTexMapType::Kd;
}
if (parse_keyword(p, end, "map_Ks")) {
return eMTLSyntaxElement::map_Ks;
return MTLTexMapType::Ks;
}
if (parse_keyword(p, end, "map_Ns")) {
return eMTLSyntaxElement::map_Ns;
return MTLTexMapType::Ns;
}
if (parse_keyword(p, end, "map_d")) {
return eMTLSyntaxElement::map_d;
return MTLTexMapType::d;
}
if (parse_keyword(p, end, "refl")) {
return eMTLSyntaxElement::map_refl;
}
if (parse_keyword(p, end, "map_refl")) {
return eMTLSyntaxElement::map_refl;
if (parse_keyword(p, end, "refl") || parse_keyword(p, end, "map_refl")) {
return MTLTexMapType::refl;
}
if (parse_keyword(p, end, "map_Ke")) {
return eMTLSyntaxElement::map_Ke;
return MTLTexMapType::Ke;
}
if (parse_keyword(p, end, "bump")) {
return eMTLSyntaxElement::map_Bump;
if (parse_keyword(p, end, "bump") || parse_keyword(p, end, "map_Bump") ||
parse_keyword(p, end, "map_bump")) {
return MTLTexMapType::bump;
}
if (parse_keyword(p, end, "map_Bump") || parse_keyword(p, end, "map_bump")) {
return eMTLSyntaxElement::map_Bump;
}
return eMTLSyntaxElement::string;
return MTLTexMapType::Count;
}
static const std::pair<StringRef, int> unsupported_texture_options[] = {
@ -639,7 +634,7 @@ static const std::pair<StringRef, int> unsupported_texture_options[] = {
static bool parse_texture_option(const char *&p,
const char *end,
MTLMaterial *material,
tex_map_XX &tex_map)
MTLTexMap &tex_map)
{
p = drop_whitespace(p, end);
if (parse_keyword(p, end, "-o")) {
@ -693,13 +688,13 @@ static void parse_texture_map(const char *p,
if (!is_map && !is_refl && !is_bump) {
return;
}
eMTLSyntaxElement key = mtl_line_start_to_enum(p, end);
if (key == eMTLSyntaxElement::string || !material->texture_maps.contains(key)) {
MTLTexMapType key = mtl_line_start_to_texture_type(p, end);
if (key == MTLTexMapType::Count) {
/* No supported texture map found. */
std::cerr << "OBJ import: MTL texture map type not supported: '" << line << "'" << std::endl;
return;
}
tex_map_XX &tex_map = material->texture_maps.lookup(key);
MTLTexMap &tex_map = material->tex_map_of_type(key);
tex_map.mtl_dir_path = mtl_dir_path;
/* Parse texture map options. */

View File

@ -17,8 +17,6 @@
#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"
@ -29,12 +27,12 @@ namespace blender::io::obj {
* Only float value(s) can be set using this method.
*/
static void set_property_of_socket(eNodeSocketDatatype property_type,
StringRef socket_id,
const char *socket_id,
Span<float> value,
bNode *r_node)
{
BLI_assert(r_node);
bNodeSocket *socket{nodeFindSocket(r_node, SOCK_IN, socket_id.data())};
bNodeSocket *socket{nodeFindSocket(r_node, SOCK_IN, socket_id)};
BLI_assert(socket && socket->type == property_type);
switch (property_type) {
case SOCK_FLOAT: {
@ -95,7 +93,7 @@ static Image *create_placeholder_image(Main *bmain, const std::string &path)
return image;
}
static Image *load_texture_image(Main *bmain, const tex_map_XX &tex_map, bool relative_paths)
static Image *load_texture_image(Main *bmain, const MTLTexMap &tex_map, bool relative_paths)
{
Image *image = nullptr;
@ -206,15 +204,15 @@ std::pair<float, float> ShaderNodetreeWrap::set_node_locations(const int pos_x)
}
void ShaderNodetreeWrap::link_sockets(bNode *from_node,
StringRef from_node_id,
const char *from_node_id,
bNode *to_node,
StringRef to_node_id,
const char *to_node_id,
const int from_node_pos_x)
{
std::tie(from_node->locx, from_node->locy) = set_node_locations(from_node_pos_x);
std::tie(to_node->locx, to_node->locy) = set_node_locations(from_node_pos_x + 1);
bNodeSocket *from_sock{nodeFindSocket(from_node, SOCK_OUT, from_node_id.data())};
bNodeSocket *to_sock{nodeFindSocket(to_node, SOCK_IN, to_node_id.data())};
bNodeSocket *from_sock{nodeFindSocket(from_node, SOCK_OUT, from_node_id)};
bNodeSocket *to_sock{nodeFindSocket(to_node, SOCK_IN, to_node_id)};
BLI_assert(from_sock && to_sock);
nodeAddLink(nodetree_.get(), from_node, from_sock, to_node, to_sock);
}
@ -338,7 +336,7 @@ void ShaderNodetreeWrap::set_bsdf_socket_values(Material *mat)
if (emission_color.x >= 0 && emission_color.y >= 0 && emission_color.z >= 0) {
set_property_of_socket(SOCK_RGBA, "Emission", {emission_color, 3}, bsdf_);
}
if (mtl_mat_.tex_map_of_type(eMTLSyntaxElement::map_Ke).is_valid()) {
if (mtl_mat_.tex_map_of_type(MTLTexMapType::Ke).is_valid()) {
set_property_of_socket(SOCK_FLOAT, "Emission Strength", {1.0f}, bsdf_);
}
set_property_of_socket(SOCK_FLOAT, "Specular", {specular}, bsdf_);
@ -359,38 +357,36 @@ void ShaderNodetreeWrap::set_bsdf_socket_values(Material *mat)
void ShaderNodetreeWrap::add_image_textures(Main *bmain, Material *mat, bool relative_paths)
{
for (const Map<const eMTLSyntaxElement, tex_map_XX>::Item texture_map :
mtl_mat_.texture_maps.items()) {
if (!texture_map.value.is_valid()) {
for (int key = 0; key < (int)MTLTexMapType::Count; ++key) {
const MTLTexMap &value = mtl_mat_.texture_maps[key];
if (!value.is_valid()) {
/* No Image texture node of this map type can be added to this material. */
continue;
}
bNode *image_texture = add_node_to_tree(SH_NODE_TEX_IMAGE);
BLI_assert(image_texture);
Image *image = load_texture_image(bmain, texture_map.value, relative_paths);
Image *image = load_texture_image(bmain, value, relative_paths);
if (image == nullptr) {
continue;
}
image_texture->id = &image->id;
static_cast<NodeTexImage *>(image_texture->storage)->projection =
texture_map.value.projection_type;
static_cast<NodeTexImage *>(image_texture->storage)->projection = value.projection_type;
/* Add normal map node if needed. */
bNode *normal_map = nullptr;
if (texture_map.key == eMTLSyntaxElement::map_Bump) {
if (key == (int)MTLTexMapType::bump) {
normal_map = add_node_to_tree(SH_NODE_NORMAL_MAP);
const float bump = std::max(0.0f, mtl_mat_.map_Bump_strength);
set_property_of_socket(SOCK_FLOAT, "Strength", {bump}, normal_map);
}
/* Add UV mapping & coordinate nodes only if needed. */
if (texture_map.value.translation != float3(0, 0, 0) ||
texture_map.value.scale != float3(1, 1, 1)) {
if (value.translation != float3(0, 0, 0) || value.scale != float3(1, 1, 1)) {
bNode *mapping = add_node_to_tree(SH_NODE_MAPPING);
bNode *texture_coordinate = add_node_to_tree(SH_NODE_TEX_COORD);
set_property_of_socket(SOCK_VECTOR, "Location", {texture_map.value.translation, 3}, mapping);
set_property_of_socket(SOCK_VECTOR, "Scale", {texture_map.value.scale, 3}, mapping);
set_property_of_socket(SOCK_VECTOR, "Location", {value.translation, 3}, mapping);
set_property_of_socket(SOCK_VECTOR, "Scale", {value.scale, 3}, mapping);
link_sockets(texture_coordinate, "UV", mapping, "Vector", 0);
link_sockets(mapping, "Vector", image_texture, "Vector", 1);
@ -400,12 +396,12 @@ void ShaderNodetreeWrap::add_image_textures(Main *bmain, Material *mat, bool rel
link_sockets(image_texture, "Color", normal_map, "Color", 2);
link_sockets(normal_map, "Normal", bsdf_, "Normal", 3);
}
else if (texture_map.key == eMTLSyntaxElement::map_d) {
link_sockets(image_texture, "Alpha", bsdf_, texture_map.value.dest_socket_id, 2);
else if (key == (int)MTLTexMapType::d) {
link_sockets(image_texture, "Alpha", bsdf_, tex_map_type_to_socket_id[key], 2);
mat->blend_method = MA_BM_BLEND;
}
else {
link_sockets(image_texture, "Color", bsdf_, texture_map.value.dest_socket_id, 2);
link_sockets(image_texture, "Color", bsdf_, tex_map_type_to_socket_id[key], 2);
}
}
}

View File

@ -10,7 +10,6 @@
#include "BLI_map.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "DNA_node_types.h"
@ -75,9 +74,9 @@ class ShaderNodetreeWrap {
* \param from_node_pos_x: 0 to 4 value as per nodetree arrangement.
*/
void link_sockets(bNode *from_node,
StringRef from_node_id,
const char *from_node_id,
bNode *to_node,
StringRef to_node_id,
const char *to_node_id,
const int from_node_pos_x);
/**
* Set values of sockets in p-BSDF node of the nodetree.

View File

@ -185,17 +185,17 @@ TEST(obj_exporter_writer, mtllib)
TEST(obj_exporter_writer, format_handler_buffer_chunking)
{
/* Use a tiny buffer chunk size, so that the test below ends up creating several blocks. */
FormatHandler<eFileType::OBJ, 16> h;
h.write<eOBJSyntaxElement::object_name>("abc");
h.write<eOBJSyntaxElement::object_name>("abcd");
h.write<eOBJSyntaxElement::object_name>("abcde");
h.write<eOBJSyntaxElement::object_name>("abcdef");
h.write<eOBJSyntaxElement::object_name>("012345678901234567890123456789abcd");
h.write<eOBJSyntaxElement::object_name>("123");
h.write<eOBJSyntaxElement::curve_element_begin>();
h.write<eOBJSyntaxElement::new_line>();
h.write<eOBJSyntaxElement::nurbs_parameter_begin>();
h.write<eOBJSyntaxElement::new_line>();
FormatHandler h(16);
h.write_obj_object("abc");
h.write_obj_object("abcd");
h.write_obj_object("abcde");
h.write_obj_object("abcdef");
h.write_obj_object("012345678901234567890123456789abcd");
h.write_obj_object("123");
h.write_obj_curve_begin();
h.write_obj_newline();
h.write_obj_nurbs_parm_begin();
h.write_obj_newline();
size_t got_blocks = h.get_block_count();
ASSERT_EQ(got_blocks, 7);

View File

@ -58,9 +58,9 @@ class obj_mtl_parser_test : public testing::Test {
EXPECT_NEAR(exp.d, got.d, tol);
EXPECT_NEAR(exp.map_Bump_strength, got.map_Bump_strength, tol);
EXPECT_EQ(exp.illum, got.illum);
for (const auto &it : exp.texture_maps.items()) {
const tex_map_XX &exp_tex = it.value;
const tex_map_XX &got_tex = got.texture_maps.lookup(it.key);
for (int key = 0; key < (int)MTLTexMapType::Count; key++) {
const MTLTexMap &exp_tex = exp.texture_maps[key];
const MTLTexMap &got_tex = got.texture_maps[key];
EXPECT_STREQ(exp_tex.image_path.c_str(), got_tex.image_path.c_str());
EXPECT_V3_NEAR(exp_tex.translation, got_tex.translation, tol);
EXPECT_V3_NEAR(exp_tex.scale, got_tex.scale, tol);
@ -113,8 +113,8 @@ TEST_F(obj_mtl_parser_test, string_newlines_whitespace)
mat[4].Kd = {0.6f, 0.7f, 0.8f};
mat[5].name = "crlf_ending";
mat[5].Ns = 5.0f;
mat[5].tex_map_of_type(eMTLSyntaxElement::map_Kd).image_path = "sometex_d.png";
mat[5].tex_map_of_type(eMTLSyntaxElement::map_Ks).image_path = "sometex_s_spaces_after_name.png";
mat[5].tex_map_of_type(MTLTexMapType::Kd).image_path = "sometex_d.png";
mat[5].tex_map_of_type(MTLTexMapType::Ks).image_path = "sometex_s_spaces_after_name.png";
check_string(text, mat, ARRAY_SIZE(mat));
}
@ -175,13 +175,13 @@ TEST_F(obj_mtl_parser_test, materials)
mat[1].illum = 2;
mat[1].map_Bump_strength = 1;
{
tex_map_XX &kd = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Kd);
MTLTexMap &kd = mat[1].tex_map_of_type(MTLTexMapType::Kd);
kd.image_path = "texture.png";
tex_map_XX &ns = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Ns);
MTLTexMap &ns = mat[1].tex_map_of_type(MTLTexMapType::Ns);
ns.image_path = "sometexture_Roughness.png";
tex_map_XX &refl = mat[1].tex_map_of_type(eMTLSyntaxElement::map_refl);
MTLTexMap &refl = mat[1].tex_map_of_type(MTLTexMapType::refl);
refl.image_path = "sometexture_Metallic.png";
tex_map_XX &bump = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Bump);
MTLTexMap &bump = mat[1].tex_map_of_type(MTLTexMapType::bump);
bump.image_path = "sometexture_Normal.png";
}
@ -202,13 +202,13 @@ TEST_F(obj_mtl_parser_test, materials)
mat[3].Ns = 800;
mat[3].map_Bump_strength = 0.5f;
{
tex_map_XX &kd = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Kd);
MTLTexMap &kd = mat[3].tex_map_of_type(MTLTexMapType::Kd);
kd.image_path = "someHatTexture_BaseColor.jpg";
tex_map_XX &ns = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Ns);
MTLTexMap &ns = mat[3].tex_map_of_type(MTLTexMapType::Ns);
ns.image_path = "someHatTexture_Roughness.jpg";
tex_map_XX &refl = mat[3].tex_map_of_type(eMTLSyntaxElement::map_refl);
MTLTexMap &refl = mat[3].tex_map_of_type(MTLTexMapType::refl);
refl.image_path = "someHatTexture_Metalness.jpg";
tex_map_XX &bump = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Bump);
MTLTexMap &bump = mat[3].tex_map_of_type(MTLTexMapType::bump);
bump.image_path = "someHatTexture_Normal.jpg";
}
@ -222,30 +222,30 @@ TEST_F(obj_mtl_parser_test, materials)
mat[4].d = 0.5;
mat[4].map_Bump_strength = 0.1f;
{
tex_map_XX &kd = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Kd);
MTLTexMap &kd = mat[4].tex_map_of_type(MTLTexMapType::Kd);
kd.image_path = "sometex_d.png";
tex_map_XX &ns = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Ns);
MTLTexMap &ns = mat[4].tex_map_of_type(MTLTexMapType::Ns);
ns.image_path = "sometex_ns.psd";
tex_map_XX &refl = mat[4].tex_map_of_type(eMTLSyntaxElement::map_refl);
MTLTexMap &refl = mat[4].tex_map_of_type(MTLTexMapType::refl);
refl.image_path = "clouds.tiff";
refl.scale = {1.5f, 2.5f, 3.5f};
refl.translation = {4.5f, 5.5f, 6.5f};
refl.projection_type = SHD_PROJ_SPHERE;
tex_map_XX &bump = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Bump);
MTLTexMap &bump = mat[4].tex_map_of_type(MTLTexMapType::bump);
bump.image_path = "somebump.tga";
bump.scale = {3, 4, 5};
}
mat[5].name = "Parser_ScaleOffset_Test";
{
tex_map_XX &kd = mat[5].tex_map_of_type(eMTLSyntaxElement::map_Kd);
MTLTexMap &kd = mat[5].tex_map_of_type(MTLTexMapType::Kd);
kd.translation = {2.5f, 0.0f, 0.0f};
kd.image_path = "OffsetOneValue.png";
tex_map_XX &ks = mat[5].tex_map_of_type(eMTLSyntaxElement::map_Ks);
MTLTexMap &ks = mat[5].tex_map_of_type(MTLTexMapType::Ks);
ks.scale = {1.5f, 2.5f, 1.0f};
ks.translation = {3.5f, 4.5f, 0.0f};
ks.image_path = "ScaleOffsetBothTwovalues.png";
tex_map_XX &ns = mat[5].tex_map_of_type(eMTLSyntaxElement::map_Ns);
MTLTexMap &ns = mat[5].tex_map_of_type(MTLTexMapType::Ns);
ns.scale = {0.5f, 1.0f, 1.0f};
ns.image_path = "1.Value.png";
}