459 lines
15 KiB
C++
459 lines
15 KiB
C++
|
#include "obj-loader.h"
|
||
|
#include "nanort.h" // for float3
|
||
|
|
||
|
#define TINYOBJLOADER_IMPLEMENTATION
|
||
|
#include "tiny_obj_loader.h"
|
||
|
|
||
|
#ifdef __clang__
|
||
|
#pragma clang diagnostic push
|
||
|
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||
|
#pragma clang diagnostic ignored "-Wreserved-id-macro"
|
||
|
#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
|
||
|
#pragma clang diagnostic ignored "-Wcast-align"
|
||
|
#pragma clang diagnostic ignored "-Wpadded"
|
||
|
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||
|
#pragma clang diagnostic ignored "-Wsign-conversion"
|
||
|
#pragma clang diagnostic ignored "-Wvariadic-macros"
|
||
|
#pragma clang diagnostic ignored "-Wc++11-extensions"
|
||
|
#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
|
||
|
#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
|
||
|
#if __has_warning("-Wdouble-promotion")
|
||
|
#pragma clang diagnostic ignored "-Wdouble-promotion"
|
||
|
#endif
|
||
|
#if __has_warning("-Wcomma")
|
||
|
#pragma clang diagnostic ignored "-Wcomma"
|
||
|
#endif
|
||
|
#if __has_warning("-Wcast-qual")
|
||
|
#pragma clang diagnostic ignored "-Wcast-qual"
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#include "stb_image.h"
|
||
|
|
||
|
#ifdef __clang__
|
||
|
#pragma clang diagnostic pop
|
||
|
#endif
|
||
|
|
||
|
#include <iostream>
|
||
|
|
||
|
#ifdef NANOSG_USE_CXX11
|
||
|
#include <unordered_map>
|
||
|
#else
|
||
|
#include <map>
|
||
|
#endif
|
||
|
|
||
|
#define USE_TEX_CACHE 1
|
||
|
|
||
|
namespace example {
|
||
|
|
||
|
typedef nanort::real3<float> float3;
|
||
|
|
||
|
#ifdef __clang__
|
||
|
#pragma clang diagnostic push
|
||
|
#pragma clang diagnostic ignored "-Wexit-time-destructors"
|
||
|
#pragma clang diagnostic ignored "-Wglobal-constructors"
|
||
|
#endif
|
||
|
|
||
|
// TODO(LTE): Remove global static definition.
|
||
|
#ifdef NANOSG_USE_CXX11
|
||
|
static std::unordered_map<std::string, int> hashed_tex;
|
||
|
#else
|
||
|
static std::map<std::string, int> hashed_tex;
|
||
|
#endif
|
||
|
|
||
|
#ifdef __clang__
|
||
|
#pragma clang diagnostic pop
|
||
|
#endif
|
||
|
|
||
|
inline void CalcNormal(float3 &N, float3 v0, float3 v1, float3 v2) {
|
||
|
float3 v10 = v1 - v0;
|
||
|
float3 v20 = v2 - v0;
|
||
|
|
||
|
N = vcross(v20, v10);
|
||
|
N = vnormalize(N);
|
||
|
}
|
||
|
|
||
|
static std::string GetBaseDir(const std::string &filepath) {
|
||
|
if (filepath.find_last_of("/\\") != std::string::npos)
|
||
|
return filepath.substr(0, filepath.find_last_of("/\\"));
|
||
|
return "";
|
||
|
}
|
||
|
|
||
|
static int LoadTexture(const std::string &filename,
|
||
|
std::vector<Texture> *textures) {
|
||
|
int idx;
|
||
|
|
||
|
if (filename.empty()) return -1;
|
||
|
|
||
|
std::cout << " Loading texture : " << filename << std::endl;
|
||
|
Texture texture;
|
||
|
|
||
|
// tigra: find in cache. get index
|
||
|
if (USE_TEX_CACHE) {
|
||
|
if (hashed_tex.find(filename) != hashed_tex.end()) {
|
||
|
puts("from cache");
|
||
|
return hashed_tex[filename];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int w, h, n;
|
||
|
unsigned char *data = stbi_load(filename.c_str(), &w, &h, &n, 0);
|
||
|
if (data) {
|
||
|
texture.width = w;
|
||
|
texture.height = h;
|
||
|
texture.components = n;
|
||
|
|
||
|
size_t n_elem = size_t(w * h * n);
|
||
|
texture.image = new unsigned char[n_elem];
|
||
|
for (size_t i = 0; i < n_elem; i++) {
|
||
|
texture.image[i] = data[i];
|
||
|
}
|
||
|
|
||
|
free(data);
|
||
|
|
||
|
textures->push_back(texture);
|
||
|
|
||
|
idx = int(textures->size()) - 1;
|
||
|
|
||
|
// tigra: store index to cache
|
||
|
if (USE_TEX_CACHE) {
|
||
|
hashed_tex[filename] = idx;
|
||
|
}
|
||
|
|
||
|
return idx;
|
||
|
}
|
||
|
|
||
|
std::cout << " Failed to load : " << filename << std::endl;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static void ComputeBoundingBoxOfMesh(float bmin[3], float bmax[3],
|
||
|
const example::Mesh<float> &mesh) {
|
||
|
bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max();
|
||
|
bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
|
||
|
|
||
|
for (size_t i = 0; i < mesh.vertices.size() / 3; i++) {
|
||
|
bmin[0] = std::min(bmin[0], mesh.vertices[3 * i + 0]);
|
||
|
bmin[1] = std::min(bmin[1], mesh.vertices[3 * i + 1]);
|
||
|
bmin[2] = std::min(bmin[1], mesh.vertices[3 * i + 2]);
|
||
|
|
||
|
bmax[0] = std::max(bmax[0], mesh.vertices[3 * i + 0]);
|
||
|
bmax[1] = std::max(bmax[1], mesh.vertices[3 * i + 1]);
|
||
|
bmax[2] = std::max(bmax[2], mesh.vertices[3 * i + 2]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool LoadObj(const std::string &filename, float scale,
|
||
|
std::vector<Mesh<float> > *meshes,
|
||
|
std::vector<Material> *out_materials,
|
||
|
std::vector<Texture> *out_textures) {
|
||
|
tinyobj::attrib_t attrib;
|
||
|
std::vector<tinyobj::shape_t> shapes;
|
||
|
std::vector<tinyobj::material_t> materials;
|
||
|
std::string err;
|
||
|
|
||
|
std::string basedir = GetBaseDir(filename) + "/";
|
||
|
const char *basepath = (basedir.compare("/") == 0) ? NULL : basedir.c_str();
|
||
|
|
||
|
// auto t_start = std::chrono::system_clock::now();
|
||
|
|
||
|
bool ret =
|
||
|
tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename.c_str(),
|
||
|
basepath, /* triangulate */ true);
|
||
|
|
||
|
// auto t_end = std::chrono::system_clock::now();
|
||
|
// std::chrono::duration<double, std::milli> ms = t_end - t_start;
|
||
|
|
||
|
if (!err.empty()) {
|
||
|
std::cerr << err << std::endl;
|
||
|
}
|
||
|
|
||
|
if (!ret) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// std::cout << "[LoadOBJ] Parse time : " << ms.count() << " [msecs]"
|
||
|
// << std::endl;
|
||
|
|
||
|
std::cout << "[LoadOBJ] # of shapes in .obj : " << shapes.size() << std::endl;
|
||
|
std::cout << "[LoadOBJ] # of materials in .obj : " << materials.size()
|
||
|
<< std::endl;
|
||
|
|
||
|
{
|
||
|
size_t total_num_vertices = 0;
|
||
|
size_t total_num_faces = 0;
|
||
|
|
||
|
total_num_vertices = attrib.vertices.size() / 3;
|
||
|
std::cout << " vertices : " << attrib.vertices.size() / 3 << std::endl;
|
||
|
|
||
|
for (size_t i = 0; i < shapes.size(); i++) {
|
||
|
std::cout << " shape[" << i << "].name : " << shapes[i].name
|
||
|
<< std::endl;
|
||
|
std::cout << " shape[" << i
|
||
|
<< "].indices : " << shapes[i].mesh.indices.size() << std::endl;
|
||
|
assert((shapes[i].mesh.indices.size() % 3) == 0);
|
||
|
|
||
|
total_num_faces += shapes[i].mesh.indices.size() / 3;
|
||
|
|
||
|
// tigra: empty name convert to _id
|
||
|
if (shapes[i].name.length() == 0) {
|
||
|
#ifdef NANOSG_USE_CXX11
|
||
|
shapes[i].name = "_" + std::to_string(i);
|
||
|
#else
|
||
|
std::stringstream ss;
|
||
|
ss << i;
|
||
|
shapes[i].name = "_" + ss.str();
|
||
|
#endif
|
||
|
std::cout << " EMPTY shape[" << i << "].name, new : " << shapes[i].name
|
||
|
<< std::endl;
|
||
|
}
|
||
|
}
|
||
|
std::cout << "[LoadOBJ] # of faces: " << total_num_faces << std::endl;
|
||
|
std::cout << "[LoadOBJ] # of vertices: " << total_num_vertices << std::endl;
|
||
|
}
|
||
|
|
||
|
// TODO(LTE): Implement tangents and binormals
|
||
|
|
||
|
for (size_t i = 0; i < shapes.size(); i++) {
|
||
|
Mesh<float> mesh(/* stride */ sizeof(float) * 3);
|
||
|
|
||
|
mesh.name = shapes[i].name;
|
||
|
|
||
|
const size_t num_faces = shapes[i].mesh.indices.size() / 3;
|
||
|
mesh.faces.resize(num_faces * 3);
|
||
|
mesh.material_ids.resize(num_faces);
|
||
|
mesh.facevarying_normals.resize(num_faces * 3 * 3);
|
||
|
mesh.facevarying_uvs.resize(num_faces * 3 * 2);
|
||
|
mesh.vertices.resize(num_faces * 3 * 3);
|
||
|
|
||
|
for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) {
|
||
|
// reorder vertices. may create duplicated vertices.
|
||
|
size_t f0 = size_t(shapes[i].mesh.indices[3 * f + 0].vertex_index);
|
||
|
size_t f1 = size_t(shapes[i].mesh.indices[3 * f + 1].vertex_index);
|
||
|
size_t f2 = size_t(shapes[i].mesh.indices[3 * f + 2].vertex_index);
|
||
|
|
||
|
mesh.vertices[9 * f + 0] = scale * attrib.vertices[3 * f0 + 0];
|
||
|
mesh.vertices[9 * f + 1] = scale * attrib.vertices[3 * f0 + 1];
|
||
|
mesh.vertices[9 * f + 2] = scale * attrib.vertices[3 * f0 + 2];
|
||
|
|
||
|
mesh.vertices[9 * f + 3] = scale * attrib.vertices[3 * f1 + 0];
|
||
|
mesh.vertices[9 * f + 4] = scale * attrib.vertices[3 * f1 + 1];
|
||
|
mesh.vertices[9 * f + 5] = scale * attrib.vertices[3 * f1 + 2];
|
||
|
|
||
|
mesh.vertices[9 * f + 6] = scale * attrib.vertices[3 * f2 + 0];
|
||
|
mesh.vertices[9 * f + 7] = scale * attrib.vertices[3 * f2 + 1];
|
||
|
mesh.vertices[9 * f + 8] = scale * attrib.vertices[3 * f2 + 2];
|
||
|
|
||
|
mesh.faces[3 * f + 0] = static_cast<unsigned int>(3 * f + 0);
|
||
|
mesh.faces[3 * f + 1] = static_cast<unsigned int>(3 * f + 1);
|
||
|
mesh.faces[3 * f + 2] = static_cast<unsigned int>(3 * f + 2);
|
||
|
|
||
|
mesh.material_ids[f] =
|
||
|
static_cast<unsigned int>(shapes[i].mesh.material_ids[f]);
|
||
|
}
|
||
|
|
||
|
if (attrib.normals.size() > 0) {
|
||
|
for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) {
|
||
|
size_t f0, f1, f2;
|
||
|
|
||
|
f0 = size_t(shapes[i].mesh.indices[3 * f + 0].normal_index);
|
||
|
f1 = size_t(shapes[i].mesh.indices[3 * f + 1].normal_index);
|
||
|
f2 = size_t(shapes[i].mesh.indices[3 * f + 2].normal_index);
|
||
|
|
||
|
if (f0 > 0 && f1 > 0 && f2 > 0) {
|
||
|
float n0[3], n1[3], n2[3];
|
||
|
|
||
|
n0[0] = attrib.normals[3 * f0 + 0];
|
||
|
n0[1] = attrib.normals[3 * f0 + 1];
|
||
|
n0[2] = attrib.normals[3 * f0 + 2];
|
||
|
|
||
|
n1[0] = attrib.normals[3 * f1 + 0];
|
||
|
n1[1] = attrib.normals[3 * f1 + 1];
|
||
|
n1[2] = attrib.normals[3 * f1 + 2];
|
||
|
|
||
|
n2[0] = attrib.normals[3 * f2 + 0];
|
||
|
n2[1] = attrib.normals[3 * f2 + 1];
|
||
|
n2[2] = attrib.normals[3 * f2 + 2];
|
||
|
|
||
|
mesh.facevarying_normals[3 * (3 * f + 0) + 0] = n0[0];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 0) + 1] = n0[1];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 0) + 2] = n0[2];
|
||
|
|
||
|
mesh.facevarying_normals[3 * (3 * f + 1) + 0] = n1[0];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 1) + 1] = n1[1];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 1) + 2] = n1[2];
|
||
|
|
||
|
mesh.facevarying_normals[3 * (3 * f + 2) + 0] = n2[0];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 2) + 1] = n2[1];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 2) + 2] = n2[2];
|
||
|
} else { // face contains invalid normal index. calc geometric normal.
|
||
|
f0 = size_t(shapes[i].mesh.indices[3 * f + 0].vertex_index);
|
||
|
f1 = size_t(shapes[i].mesh.indices[3 * f + 1].vertex_index);
|
||
|
f2 = size_t(shapes[i].mesh.indices[3 * f + 2].vertex_index);
|
||
|
|
||
|
float3 v0, v1, v2;
|
||
|
|
||
|
v0[0] = attrib.vertices[3 * f0 + 0];
|
||
|
v0[1] = attrib.vertices[3 * f0 + 1];
|
||
|
v0[2] = attrib.vertices[3 * f0 + 2];
|
||
|
|
||
|
v1[0] = attrib.vertices[3 * f1 + 0];
|
||
|
v1[1] = attrib.vertices[3 * f1 + 1];
|
||
|
v1[2] = attrib.vertices[3 * f1 + 2];
|
||
|
|
||
|
v2[0] = attrib.vertices[3 * f2 + 0];
|
||
|
v2[1] = attrib.vertices[3 * f2 + 1];
|
||
|
v2[2] = attrib.vertices[3 * f2 + 2];
|
||
|
|
||
|
float3 N;
|
||
|
CalcNormal(N, v0, v1, v2);
|
||
|
|
||
|
mesh.facevarying_normals[3 * (3 * f + 0) + 0] = N[0];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 0) + 1] = N[1];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 0) + 2] = N[2];
|
||
|
|
||
|
mesh.facevarying_normals[3 * (3 * f + 1) + 0] = N[0];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 1) + 1] = N[1];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 1) + 2] = N[2];
|
||
|
|
||
|
mesh.facevarying_normals[3 * (3 * f + 2) + 0] = N[0];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 2) + 1] = N[1];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 2) + 2] = N[2];
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// calc geometric normal
|
||
|
for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) {
|
||
|
size_t f0, f1, f2;
|
||
|
|
||
|
f0 = size_t(shapes[i].mesh.indices[3 * f + 0].vertex_index);
|
||
|
f1 = size_t(shapes[i].mesh.indices[3 * f + 1].vertex_index);
|
||
|
f2 = size_t(shapes[i].mesh.indices[3 * f + 2].vertex_index);
|
||
|
|
||
|
float3 v0, v1, v2;
|
||
|
|
||
|
v0[0] = attrib.vertices[3 * f0 + 0];
|
||
|
v0[1] = attrib.vertices[3 * f0 + 1];
|
||
|
v0[2] = attrib.vertices[3 * f0 + 2];
|
||
|
|
||
|
v1[0] = attrib.vertices[3 * f1 + 0];
|
||
|
v1[1] = attrib.vertices[3 * f1 + 1];
|
||
|
v1[2] = attrib.vertices[3 * f1 + 2];
|
||
|
|
||
|
v2[0] = attrib.vertices[3 * f2 + 0];
|
||
|
v2[1] = attrib.vertices[3 * f2 + 1];
|
||
|
v2[2] = attrib.vertices[3 * f2 + 2];
|
||
|
|
||
|
float3 N;
|
||
|
CalcNormal(N, v0, v1, v2);
|
||
|
|
||
|
mesh.facevarying_normals[3 * (3 * f + 0) + 0] = N[0];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 0) + 1] = N[1];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 0) + 2] = N[2];
|
||
|
|
||
|
mesh.facevarying_normals[3 * (3 * f + 1) + 0] = N[0];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 1) + 1] = N[1];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 1) + 2] = N[2];
|
||
|
|
||
|
mesh.facevarying_normals[3 * (3 * f + 2) + 0] = N[0];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 2) + 1] = N[1];
|
||
|
mesh.facevarying_normals[3 * (3 * f + 2) + 2] = N[2];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (attrib.texcoords.size() > 0) {
|
||
|
for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) {
|
||
|
size_t f0, f1, f2;
|
||
|
|
||
|
f0 = size_t(shapes[i].mesh.indices[3 * f + 0].texcoord_index);
|
||
|
f1 = size_t(shapes[i].mesh.indices[3 * f + 1].texcoord_index);
|
||
|
f2 = size_t(shapes[i].mesh.indices[3 * f + 2].texcoord_index);
|
||
|
|
||
|
if (f0 > 0 && f1 > 0 && f2 > 0) {
|
||
|
float3 n0, n1, n2;
|
||
|
|
||
|
n0[0] = attrib.texcoords[2 * f0 + 0];
|
||
|
n0[1] = attrib.texcoords[2 * f0 + 1];
|
||
|
|
||
|
n1[0] = attrib.texcoords[2 * f1 + 0];
|
||
|
n1[1] = attrib.texcoords[2 * f1 + 1];
|
||
|
|
||
|
n2[0] = attrib.texcoords[2 * f2 + 0];
|
||
|
n2[1] = attrib.texcoords[2 * f2 + 1];
|
||
|
|
||
|
mesh.facevarying_uvs[2 * (3 * f + 0) + 0] = n0[0];
|
||
|
mesh.facevarying_uvs[2 * (3 * f + 0) + 1] = n0[1];
|
||
|
|
||
|
mesh.facevarying_uvs[2 * (3 * f + 1) + 0] = n1[0];
|
||
|
mesh.facevarying_uvs[2 * (3 * f + 1) + 1] = n1[1];
|
||
|
|
||
|
mesh.facevarying_uvs[2 * (3 * f + 2) + 0] = n2[0];
|
||
|
mesh.facevarying_uvs[2 * (3 * f + 2) + 1] = n2[1];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Compute pivot translation and add offset to the vertices.
|
||
|
float bmin[3], bmax[3];
|
||
|
ComputeBoundingBoxOfMesh(bmin, bmax, mesh);
|
||
|
|
||
|
float bcenter[3];
|
||
|
bcenter[0] = 0.5f * (bmax[0] - bmin[0]) + bmin[0];
|
||
|
bcenter[1] = 0.5f * (bmax[1] - bmin[1]) + bmin[1];
|
||
|
bcenter[2] = 0.5f * (bmax[2] - bmin[2]) + bmin[2];
|
||
|
|
||
|
for (size_t v = 0; v < mesh.vertices.size() / 3; v++) {
|
||
|
mesh.vertices[3 * v + 0] -= bcenter[0];
|
||
|
mesh.vertices[3 * v + 1] -= bcenter[1];
|
||
|
mesh.vertices[3 * v + 2] -= bcenter[2];
|
||
|
}
|
||
|
|
||
|
mesh.pivot_xform[0][0] = 1.0f;
|
||
|
mesh.pivot_xform[0][1] = 0.0f;
|
||
|
mesh.pivot_xform[0][2] = 0.0f;
|
||
|
mesh.pivot_xform[0][3] = 0.0f;
|
||
|
|
||
|
mesh.pivot_xform[1][0] = 0.0f;
|
||
|
mesh.pivot_xform[1][1] = 1.0f;
|
||
|
mesh.pivot_xform[1][2] = 0.0f;
|
||
|
mesh.pivot_xform[1][3] = 0.0f;
|
||
|
|
||
|
mesh.pivot_xform[2][0] = 0.0f;
|
||
|
mesh.pivot_xform[2][1] = 0.0f;
|
||
|
mesh.pivot_xform[2][2] = 1.0f;
|
||
|
mesh.pivot_xform[2][3] = 0.0f;
|
||
|
|
||
|
mesh.pivot_xform[3][0] = bcenter[0];
|
||
|
mesh.pivot_xform[3][1] = bcenter[1];
|
||
|
mesh.pivot_xform[3][2] = bcenter[2];
|
||
|
mesh.pivot_xform[3][3] = 1.0f;
|
||
|
|
||
|
meshes->push_back(mesh);
|
||
|
}
|
||
|
|
||
|
// material_t -> Material and Texture
|
||
|
out_materials->resize(materials.size());
|
||
|
out_textures->resize(0);
|
||
|
for (size_t i = 0; i < materials.size(); i++) {
|
||
|
(*out_materials)[i].diffuse[0] = materials[i].diffuse[0];
|
||
|
(*out_materials)[i].diffuse[1] = materials[i].diffuse[1];
|
||
|
(*out_materials)[i].diffuse[2] = materials[i].diffuse[2];
|
||
|
(*out_materials)[i].specular[0] = materials[i].specular[0];
|
||
|
(*out_materials)[i].specular[1] = materials[i].specular[1];
|
||
|
(*out_materials)[i].specular[2] = materials[i].specular[2];
|
||
|
|
||
|
(*out_materials)[i].id = int(i);
|
||
|
|
||
|
// map_Kd
|
||
|
(*out_materials)[i].diffuse_texid =
|
||
|
LoadTexture(materials[i].diffuse_texname, out_textures);
|
||
|
// map_Ks
|
||
|
(*out_materials)[i].specular_texid =
|
||
|
LoadTexture(materials[i].specular_texname, out_textures);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
} // namespace example
|