// Model - 3D objects
//
// Copyright (C) 2015 Sam Varner
//
// This file is part of Vamos Automotive Simulator.
//
// Vamos is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vamos is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vamos. If not, see <http://www.gnu.org/licenses/>.
#include "Model.h"
#include "../geometry/Conversions.h"
#include "../geometry/Three_Vector.h"
#include <assimp/material.h>
#include <assimp/color4.h>
#include <assimp/postprocess.h>
#include <IL/il.h>
using namespace Vamos_Geometry;
using namespace Vamos_Media;
namespace Vamos_Media
{
Model::Model (const std::string& file,
double scale,
const Three_Vector& translation,
const Three_Vector& rotation,
bool two_sided)
: m_file (file),
mp_texture_ids (NULL),
m_scale (scale),
m_translation (translation),
m_rotation (rad_to_deg (1) * rotation),
m_two_sided (two_sided)
{
mp_object = m_importer.ReadFile (file.c_str(),
aiProcessPreset_TargetRealtime_MaxQuality
| aiProcess_GenSmoothNormals);
if (!mp_object)
throw Model_Exception (m_importer.GetErrorString ());
load_textures ();
}
Model::~Model ()
{
m_texture_id_map.clear ();
delete [] mp_texture_ids;
mp_texture_ids = NULL;
}
GLuint Model::build ()
{
GLuint id = glGenLists (1);
glNewList (id, GL_COMPILE);
glDisable (GL_COLOR_MATERIAL);
glEnable (GL_LIGHTING);
glEnable (GL_TEXTURE_2D);
glPushMatrix ();
glTranslated (m_translation.x, m_translation.y, m_translation.z);
glRotated (m_rotation.x, 1, 0, 0);
glRotated (m_rotation.y, 0, 1, 0);
glRotated (m_rotation.z, 0, 0, 1);
glScaled (m_scale, m_scale, m_scale);
render (mp_object->mRootNode);
glPopMatrix ();
glEndList ();
return id;
}
// Most of the rest of this file was taken from the Assimp example programs
// Sample_SimpleOpenGL.c and model_loading.cpp
void Model::load_textures ()
{
ilInit ();
if (mp_object->HasTextures ())
throw Model_Exception ("Can't use meshes with embedded textures");
for (unsigned int m = 0; m < mp_object->mNumMaterials; ++m)
{
for (int tex_index = 0; ; ++tex_index)
{
aiString path;
if (mp_object->mMaterials[m]->GetTexture (aiTextureType_DIFFUSE, tex_index, &path)
!= AI_SUCCESS)
break;
m_texture_id_map [path.data] = NULL;
}
}
const int num_textures = m_texture_id_map.size ();
if (num_textures == 0)
return;
ILuint *image_ids = NULL;
image_ids = new ILuint [num_textures];
ilGenImages (num_textures, image_ids);
mp_texture_ids = new GLuint [num_textures];
glGenTextures (num_textures, mp_texture_ids);
std::map <std::string, GLuint*>::iterator tex_it = m_texture_id_map.begin ();
for (int i = 0; i < num_textures; ++i)
{
std::string file = tex_it->first;
tex_it->second = &mp_texture_ids[i];
++tex_it;
ilBindImage (image_ids[i]);
fs::path path = m_file.parent_path () / file;
if (ilLoadImage (path.c_str ()))
{
if (!ilConvertImage (IL_RGB, IL_UNSIGNED_BYTE))
throw Model_Cant_Convert_Image (path.c_str ());
glBindTexture (GL_TEXTURE_2D, mp_texture_ids[i]);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D (GL_TEXTURE_2D,
0,
ilGetInteger (IL_IMAGE_BPP),
ilGetInteger (IL_IMAGE_WIDTH),
ilGetInteger (IL_IMAGE_HEIGHT),
0,
ilGetInteger (IL_IMAGE_FORMAT),
GL_UNSIGNED_BYTE,
ilGetData ());
glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei (GL_UNPACK_SKIP_ROWS, 0);
}
// else
// throw Model_Cant_Load_Image (path.c_str ());
}
ilDeleteImages (num_textures, image_ids);
delete [] image_ids;
}
void Model::render (const aiNode* nd)
{
{ // Update the transform
aiMatrix4x4 m = nd->mTransformation;
m.Transpose();
glPushMatrix ();
glMultMatrixf ((float*)&m);
}
// Draw the meshes assigned to this node.
for (unsigned n = 0; n < nd->mNumMeshes; ++n)
{
const aiMesh* mesh = mp_object->mMeshes[nd->mMeshes[n]];
apply_material (*mp_object->mMaterials[mesh->mMaterialIndex]);
for (unsigned t = 0; t < mesh->mNumFaces; ++t)
{
const aiFace* face = &mesh->mFaces[t];
GLenum face_mode;
switch (face->mNumIndices)
{
case 1:
face_mode = GL_POINTS;
break;
case 2:
face_mode = GL_LINES;
break;
case 3:
face_mode = GL_TRIANGLES;
break;
default:
face_mode = GL_POLYGON;
break;
}
glBegin (face_mode);
for (unsigned i = 0; i < face->mNumIndices; i++)
{
int index = face->mIndices[i];
if (mesh->mColors[0] != NULL)
glColor4fv ((GLfloat*)&mesh->mColors[0][index]);
if (mesh->mNormals != NULL)
{
if (mesh->HasTextureCoords (0))
{
glTexCoord2f (mesh->mTextureCoords[0][index].x,
1 - mesh->mTextureCoords[0][index].y);
}
glNormal3fv (&mesh->mNormals[index].x);
glVertex3fv (&mesh->mVertices[index].x);
}
}
glEnd();
}
}
// Draw the children
for (unsigned n = 0; n < nd->mNumChildren; ++n)
{
render (nd->mChildren[n]);
}
glPopMatrix();
}
void color4_to_float4 (const aiColor4D* c, float f[4])
{
f[0] = c->r;
f[1] = c->g;
f[2] = c->b;
f[3] = c->a;
}
void set_float4 (float f[4], float a, float b, float c, float d)
{
f[0] = a;
f[1] = b;
f[2] = c;
f[3] = d;
}
void Model::apply_material (const aiMaterial& material)
{
float c[4];
{
int tex_index = 0;
aiString tex_path;
if (material.GetTexture (aiTextureType_DIFFUSE, tex_index, &tex_path) == AI_SUCCESS)
{
unsigned int tex_id = *m_texture_id_map [tex_path.data];
glBindTexture (GL_TEXTURE_2D, tex_id);
}
}
{
aiColor4D diffuse;
set_float4 (c, 0.8f, 0.8f, 0.8f, 1.0f);
if (material.Get (AI_MATKEY_COLOR_DIFFUSE, diffuse) == AI_SUCCESS)
color4_to_float4 (&diffuse, c);
glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, c);
}
{
aiColor4D specular;
set_float4 (c, 0.0f, 0.0f, 0.0f, 1.0f);
if (material.Get (AI_MATKEY_COLOR_SPECULAR, specular) == AI_SUCCESS)
color4_to_float4 (&specular, c);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, c);
}
{
aiColor4D ambient;
set_float4 (c, 0.2f, 0.2f, 0.2f, 1.0f);
if (material.Get (AI_MATKEY_COLOR_AMBIENT, ambient) == AI_SUCCESS)
color4_to_float4 (&ambient, c);
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, c);
}
{
aiColor4D emission;
set_float4(c, 0.0f, 0.0f, 0.0f, 1.0f);
if (material.Get (AI_MATKEY_COLOR_EMISSIVE, emission) == AI_SUCCESS)
color4_to_float4 (&emission, c);
glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION, c);
}
{
unsigned max = 1;
float shininess;
if (material.Get (AI_MATKEY_SHININESS, &shininess, &max) == AI_SUCCESS)
{
max = 1;
float strength;
if (material.Get (AI_MATKEY_SHININESS_STRENGTH, &strength, &max) == AI_SUCCESS)
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shininess * strength);
else
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shininess);
}
else
{
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 0.0f);
set_float4 (c, 0.0f, 0.0f, 0.0f, 0.0f);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, c);
}
}
{
unsigned max = 1;
int wireframe;
GLenum fill_mode;
if (material.Get (AI_MATKEY_ENABLE_WIREFRAME, &wireframe, &max) == AI_SUCCESS)
fill_mode = wireframe ? GL_LINE : GL_FILL;
else
fill_mode = GL_FILL;
glPolygonMode (GL_FRONT_AND_BACK, fill_mode);
}
{
unsigned max = 1;
int two_sided;
if (m_two_sided
|| ((material.Get (AI_MATKEY_TWOSIDED, &two_sided, &max) == AI_SUCCESS) && two_sided))
glDisable (GL_CULL_FACE);
else
glEnable (GL_CULL_FACE);
}
}
}