diff options
Diffstat (limited to 'Source/Graphics/model.cpp')
-rw-r--r-- | Source/Graphics/model.cpp | 2504 |
1 files changed, 2504 insertions, 0 deletions
diff --git a/Source/Graphics/model.cpp b/Source/Graphics/model.cpp new file mode 100644 index 00000000..1dbcfeaf --- /dev/null +++ b/Source/Graphics/model.cpp @@ -0,0 +1,2504 @@ +//----------------------------------------------------------------------------- +// Name: model.cpp +// Developer: Wolfire Games LLC +// Author: David Rosen +// Description: This class loads and displays models +// License: Read below +//----------------------------------------------------------------------------- +// +// +// Copyright 2022 Wolfire Games LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- +#include "model.h" + +#include <Graphics/graphics.h> +#include <Graphics/shaders.h> +#include <Graphics/drawbatch.h> + +#include <Internal/collisiondetection.h> +#include <Internal/checksum.h> +#include <Internal/common.h> +#include <Internal/datemodified.h> +#include <Internal/stopwatch.h> +#include <Internal/error.h> +#include <Internal/filesystem.h> +#include <Internal/profiler.h> + +#include <Math/vec2.h> +#include <Math/vec2math.h> +#include <Math/vec3math.h> +#include <Math/overgrowth_geometry.h> + +#include <Compat/filepath.h> +#include <Asset/Asset/image_sampler.h> +#include <Compat/fileio.h> +#include <Logging/logdata.h> +#include <Utility/assert.h> +#include <Main/engine.h> + +#include <Timing/timingevent.h> +#include <Timing/intel_gl_perf.h> + +#include <cmath> +#include <set> + +//----------------------------------------------------------------------------- +//Functions +//----------------------------------------------------------------------------- + +namespace { + void RearrangeVertices(Model& model, const std::vector<int> &new_order){ + std::vector<float> old_vertices = model.vertices; + std::vector<float> old_normals = model.normals; + std::vector<float> old_tex_coords = model.tex_coords; + std::vector<float> old_tex_coords2 = model.tex_coords2; + std::vector<float> old_tangents = model.tangents; + std::vector<float> old_bitangents = model.bitangents; + std::vector<float> old_aux = model.aux; + std::vector<vec4> old_bone_weights = model.bone_weights; + std::vector<vec4> old_bone_ids = model.bone_ids; + + for(unsigned i=0; i<new_order.size(); ++i){ + model.vertices[i*3+0] = old_vertices[new_order[i]*3+0]; + model.vertices[i*3+1] = old_vertices[new_order[i]*3+1]; + model.vertices[i*3+2] = old_vertices[new_order[i]*3+2]; + if(!model.normals.empty()){ + model.normals[i*3+0] = old_normals[new_order[i]*3+0]; + model.normals[i*3+1] = old_normals[new_order[i]*3+1]; + model.normals[i*3+2] = old_normals[new_order[i]*3+2]; + } + if(!model.tex_coords.empty()){ + model.tex_coords[i*2+0] = old_tex_coords[new_order[i]*2+0]; + model.tex_coords[i*2+1] = old_tex_coords[new_order[i]*2+1]; + } + if(!model.tex_coords2.empty()){ + model.tex_coords2[i*2+0] = old_tex_coords2[new_order[i]*2+0]; + model.tex_coords2[i*2+1] = old_tex_coords2[new_order[i]*2+1]; + } + if(!model.tangents.empty()){ + model.tangents[i*3+0] = old_tangents[new_order[i]*3+0]; + model.tangents[i*3+1] = old_tangents[new_order[i]*3+1]; + model.tangents[i*3+2] = old_tangents[new_order[i]*3+2]; + } + if(!model.bitangents.empty()){ + model.bitangents[i*3+0] = old_bitangents[new_order[i]*3+0]; + model.bitangents[i*3+1] = old_bitangents[new_order[i]*3+1]; + model.bitangents[i*3+2] = old_bitangents[new_order[i]*3+2]; + } + if(!model.aux.empty()){ + model.aux[i*3+0] = old_aux[new_order[i]*3+0]; + model.aux[i*3+1] = old_aux[new_order[i]*3+1]; + model.aux[i*3+2] = old_aux[new_order[i]*3+2]; + } + if(!model.bone_weights.empty()){ + model.bone_weights[i] = old_bone_weights[new_order[i]]; + } + if(!model.bone_ids.empty()){ + model.bone_ids[i] = old_bone_ids[new_order[i]]; + } + } + model.ResizeVertices(new_order.size()); + } +} //namespace "" + +//Initialize model +Model::Model() +:vbo_loaded(false), + vbo_enabled(false), + texel_density(1.0f), + average_triangle_edge_length(1.0f), + path("DEFAULT") +{ +} + +Model::Model( const Model © ) +{ + (*this)=copy; +} + +Model& Model::operator=( const Model © ) +{ + vbo_loaded = false; + vbo_enabled = false; + + vertices = copy.vertices; + normals = copy.normals; + tangents = copy.tangents; + bitangents = copy.bitangents; + face_normals = copy.face_normals; + tex_coords = copy.tex_coords; + tex_coords2 = copy.tex_coords2; + aux = copy.aux; + faces = copy.faces; + bone_weights = copy.bone_weights; + bone_ids = copy.bone_ids; + checksum = copy.checksum; + + min_coords = copy.min_coords; + max_coords = copy.max_coords; + center_coords = copy.center_coords; + old_center = copy.old_center; + + bounding_sphere_origin = copy.bounding_sphere_origin; + bounding_sphere_radius = copy.bounding_sphere_radius; + + texel_density = copy.texel_density; + + precollapse_vert_reorder = copy.precollapse_vert_reorder; + optimize_vert_reorder = copy.optimize_vert_reorder; + + path = copy.path; + + return (*this); +} + + +void Model::WriteToFile(FILE *file) { + int count; + count = vertices.size()/3; + fwrite(&count, sizeof(int), 1, file); + fwrite(&vertices.front(), sizeof(GLfloat), count*3, file); + fwrite(&normals.front(), sizeof(GLfloat), count*3, file); + fwrite(&tangents.front(), sizeof(GLfloat), count*3, file); + fwrite(&bitangents.front(), sizeof(GLfloat), count*3, file); + fwrite(&tex_coords.front(), sizeof(GLfloat), count*2, file); + + count = faces.size()/3; + fwrite(&count, sizeof(int), 1, file); + fwrite(&faces.front(), sizeof(GLfloat), count*3, file); + fwrite(&face_normals.front(), sizeof(vec3), count, file); + + fwrite(&precollapse_num_vertices, sizeof(int), 1, file); + count = precollapse_vert_reorder.size(); + fwrite(&count, sizeof(int), 1, file); + fwrite(&precollapse_vert_reorder.front(), sizeof(int), count, file); + count = optimize_vert_reorder.size(); + fwrite(&count, sizeof(int), 1, file); + fwrite(&optimize_vert_reorder.front(), sizeof(int), count, file); + + count = tex_coords2.size()/2; + fwrite(&count, sizeof(int), 1, file); + if (count>0) { + fwrite(&tex_coords2.front(), sizeof(GLfloat), count*2, file); + } + + fwrite(&min_coords, sizeof(vec3), 1, file); + fwrite(&max_coords, sizeof(vec3), 1, file); + fwrite(¢er_coords, sizeof(vec3), 1, file); + fwrite(&old_center, sizeof(vec3), 1, file); + fwrite(&bounding_sphere_origin, sizeof(vec3), 1, file); + fwrite(&bounding_sphere_radius, sizeof(float), 1, file); + + fwrite(&texel_density, sizeof(float), 1, file); + fwrite(&average_triangle_edge_length, sizeof(float), 1, file); +} + +void Model::ReadFromFile(FILE *file) +{ + int count; + fread(&count, sizeof(int), 1, file); + ResizeVertices(count); + fread(&vertices.front(), sizeof(GLfloat), count*3, file); + fread(&normals.front(), sizeof(GLfloat), count*3, file); + fread(&tangents.front(), sizeof(GLfloat), count*3, file); + fread(&bitangents.front(), sizeof(GLfloat), count*3, file); + fread(&tex_coords.front(), sizeof(GLfloat), count*2, file); + fread(&count, sizeof(int), 1, file); + ResizeFaces(count); + fread(&faces.front(), sizeof(GLfloat), count*3, file); + fread(&face_normals.front(), sizeof(vec3), count, file); + + fread(&precollapse_num_vertices, sizeof(int), 1, file); + fread(&count, sizeof(int), 1, file); + precollapse_vert_reorder.resize(count); + fread(&precollapse_vert_reorder.front(), sizeof(int), count, file); + + fread(&count, sizeof(int), 1, file); + optimize_vert_reorder.resize(count); + fread(&optimize_vert_reorder.front(), sizeof(int), count, file); + + fread(&count, sizeof(int), 1, file); + if (count > 0) { + tex_coords2.resize(count*2); + fread(&tex_coords2.front(), sizeof(GLfloat), count*2, file); + } + + fread(&min_coords, sizeof(vec3), 1, file); + fread(&max_coords, sizeof(vec3), 1, file); + fread(¢er_coords, sizeof(vec3), 1, file); + fread(&old_center, sizeof(vec3), 1, file); + fread(&bounding_sphere_origin, sizeof(vec3), 1, file); + fread(&bounding_sphere_radius, sizeof(float), 1, file); + + fread(&texel_density, sizeof(float), 1, file); + fread(&average_triangle_edge_length, sizeof(float), 1, file); +} + +void Model::Dispose() { + vertices.clear(); + normals.clear(); + tangents.clear(); + bitangents.clear(); + face_normals.clear(); + tex_coords.clear(); + tex_coords2.clear(); + aux.clear(); + faces.clear(); + + bone_weights.clear(); + bone_ids.clear(); + transform_vec.clear(); + tex_transform_vec.clear(); + morph_transform_vec.clear(); + + image_samplers.clear(); + + optimize_vert_reorder.clear(); + precollapse_vert_reorder.clear(); + + if(vbo_enabled&&vbo_loaded) + { + VBO_vertices.Dispose(); + VBO_tex_coords.Dispose(); + VBO_normals.Dispose(); + VBO_faces.Dispose(); + VBO_tangents.Dispose(); + VBO_bitangents.Dispose(); + VBO_tex_coords2.Dispose(); + VBO_aux.Dispose(); + } + vbo_loaded = false; +} + +//Dispose of model +Model::~Model() { + Dispose(); +} + +float StaticCalcTexelDensity(const std::vector<float> &vertices, const std::vector<float> &tex_coords, const std::vector<unsigned> &faces){ + float total_tex = 0.0f; + float total_vert = 0.0f; + int next; + for (int i=0, len=faces.size()/3; i<len; i++) { + int face_vertex_index[3]; + const float *vert[3]; + const float *tc[3]; + for(int j=0; j<3; ++j) { + face_vertex_index[j] = faces[i*3+j]; + vert[j] = &vertices[face_vertex_index[j]*3]; + tc[j] = &tex_coords[face_vertex_index[j]*2]; + } + for(int j=0; j<3; j++) { + next = (j+1)%3; + float tex_distance = sqrtf( square(tc[j][0] - tc[next][0]) + + square(tc[j][1] - tc[next][1])); + float vert_distance = sqrtf( square(vert[j][0] - vert[next][0]) + + square(vert[j][1] - vert[next][1]) + + square(vert[j][2] - vert[next][2])); + if(vert_distance != 0.0f){ + total_tex += tex_distance; + total_vert += vert_distance; + } + } + } + return (total_tex / total_vert); +} + +void Model::CalcTexelDensity() { + texel_density = StaticCalcTexelDensity(vertices, tex_coords, faces); +} + +struct ObjFileStats { + int num_normal; + int num_tex; + int num_vert; + int num_face; + int objects; //Number of objects specified in the file, for this game this should only be 1 +}; + +bool GetObjFileStats(std::string rel_path, ObjFileStats &stats, bool file_necessary) { + stats.num_normal = 0; + stats.num_tex = 0; + stats.num_vert = 0; + stats.num_face = 0; + stats.objects = 0; + + char abs_path[kPathSize]; + FindFilePath(rel_path.c_str(), abs_path, kPathSize, kAnyPath, file_necessary); + FILE *fp = my_fopen(abs_path,"r"); + + if(!fp){ + if(file_necessary) { + FatalError("Error", "Could not open file fs \"%s\"", rel_path.c_str()); + } else { + return false; + } + } + + char buffer[256]; + while(!feof(fp)) + { + memset(buffer,0,200); + fgets(buffer,256,fp); + + if( strncmp("vn ",buffer,3) == 0 ) + ++stats.num_normal; + else if( strncmp("vt ",buffer,3) == 0 ) + ++stats.num_tex; + else if( strncmp("v ",buffer,2) == 0 ) + ++stats.num_vert; + else if( strncmp("f ",buffer,2) == 0 ) + ++stats.num_face; + else if( strncmp("o ",buffer,2) == 0 ) + ++stats.objects; + } + + //There is always at least one object in a file. + if( stats.objects == 0 ) + stats.objects = 1; + + fclose(fp); + + return true; +} + +struct TempModel { + std::vector<GLfloat> vertices; + std::vector<GLfloat> tangents; + std::vector<GLfloat> bitangents; + std::vector<GLfloat> normals; + std::vector<GLfloat> tex_coords; + std::vector<GLfloat> tex_coords2; + std::vector<GLuint> vert_indices; + std::vector<GLuint> tex_indices; + std::vector<GLuint> tex_indices2; + std::vector<GLuint> norm_indices; + unsigned int vc,nc,tc,fc; +}; + +void LoadTempModel(std::string rel_path, TempModel &temp, const ObjFileStats &obj_stats) { + temp.vertices.resize(obj_stats.num_vert*3); + temp.normals.resize(obj_stats.num_normal*3); + temp.tangents.resize(obj_stats.num_tex*3); + temp.bitangents.resize(obj_stats.num_tex*3); + temp.tex_coords.resize(obj_stats.num_tex*2); + temp.tex_coords2.resize(0); + temp.vert_indices.resize(obj_stats.num_face*6); + temp.tex_indices.resize(obj_stats.num_face*6); + temp.norm_indices.resize(obj_stats.num_face*6); + + char abs_path[kPathSize]; + FindFilePath(rel_path.c_str(), abs_path, kPathSize, kAnyPath); + FILE *fp = my_fopen(abs_path,"r"); + + int num_read; + + temp.fc = 0; + temp.nc = 0; + temp.tc = 0; + temp.vc = 0; + + char buffer[256]; + while(!feof(fp)) + { + memset(buffer,0,255); + fgets(buffer,256,fp); + + if( strncmp("vn ",buffer,3) == 0 ) + { + sscanf((buffer+2),"%f%f%f", + &temp.normals[temp.nc*3+0], + &temp.normals[temp.nc*3+1], + &temp.normals[temp.nc*3+2]); + ++temp.nc; + } + else if( strncmp("vt ",buffer,3) == 0 ) + { + sscanf((buffer+2),"%f%f", + &temp.tex_coords[temp.tc*2+0], + &temp.tex_coords[temp.tc*2+1]); + ++temp.tc; + } + else if( strncmp("v ",buffer,2) == 0 ) + { + sscanf((buffer+1),"%f%f%f", + &temp.vertices[temp.vc*3+0], + &temp.vertices[temp.vc*3+1], + &temp.vertices[temp.vc*3+2]); + ++temp.vc; + } + else if( strncmp("f ",buffer,2) == 0 ) + { + if(obj_stats.num_tex&&obj_stats.num_normal) { + num_read = sscanf(buffer+1,"%u/%u/%u %u/%u/%u %u/%u/%u %u/%u/%u", + &temp.vert_indices[temp.fc*3+0], + &temp.tex_indices[temp.fc*3+0], + &temp.norm_indices[temp.fc*3+0], + + &temp.vert_indices[temp.fc*3+1], + &temp.tex_indices[temp.fc*3+1], + &temp.norm_indices[temp.fc*3+1], + + &temp.vert_indices[temp.fc*3+2], + &temp.tex_indices[temp.fc*3+2], + &temp.norm_indices[temp.fc*3+2], + + &temp.vert_indices[temp.fc*3+3], + &temp.tex_indices[temp.fc*3+3], + &temp.norm_indices[temp.fc*3+3]); + if(num_read == 12) { + temp.vert_indices[temp.fc*3+4] = temp.vert_indices[temp.fc*3+0]; + temp.tex_indices[temp.fc*3+4] = temp.tex_indices[temp.fc*3+0]; + temp.norm_indices[temp.fc*3+4] = temp.norm_indices[temp.fc*3+0]; + + temp.vert_indices[temp.fc*3+5] = temp.vert_indices[temp.fc*3+2]; + temp.tex_indices[temp.fc*3+5] = temp.tex_indices[temp.fc*3+2]; + temp.norm_indices[temp.fc*3+5] = temp.norm_indices[temp.fc*3+2]; + + temp.fc++; + } + } else if (obj_stats.num_normal) { + num_read = sscanf(buffer+1,"%u//%u %u//%u %u//%u %u//%u", + &temp.vert_indices[temp.fc*3+0], + &temp.norm_indices[temp.fc*3+0], + + &temp.vert_indices[temp.fc*3+1], + &temp.norm_indices[temp.fc*3+1], + + &temp.vert_indices[temp.fc*3+2], + &temp.norm_indices[temp.fc*3+2], + + &temp.vert_indices[temp.fc*3+3], + &temp.norm_indices[temp.fc*3+3]); + + if(num_read == 8) { + temp.vert_indices[temp.fc*3+4] = temp.vert_indices[temp.fc*3+0]; + temp.norm_indices[temp.fc*3+4] = temp.norm_indices[temp.fc*3+0]; + + temp.vert_indices[temp.fc*3+5] = temp.vert_indices[temp.fc*3+2]; + temp.norm_indices[temp.fc*3+5] = temp.norm_indices[temp.fc*3+2]; + + temp.fc++; + } + } else if (obj_stats.num_tex) { + num_read = sscanf(buffer+1,"%u/%u %u/%u %u/%u %u/%u", + &temp.vert_indices[temp.fc*3+0], + &temp.tex_indices[temp.fc*3+0], + + &temp.vert_indices[temp.fc*3+1], + &temp.tex_indices[temp.fc*3+1], + + &temp.vert_indices[temp.fc*3+2], + &temp.tex_indices[temp.fc*3+2], + + &temp.vert_indices[temp.fc*3+3], + &temp.tex_indices[temp.fc*3+3]); + + if(num_read == 8) { + temp.vert_indices[temp.fc*3+4] = temp.vert_indices[temp.fc*3+0]; + temp.tex_indices[temp.fc*3+4] = temp.tex_indices[temp.fc*3+0]; + + temp.vert_indices[temp.fc*3+5] = temp.vert_indices[temp.fc*3+2]; + temp.tex_indices[temp.fc*3+5] = temp.tex_indices[temp.fc*3+2]; + + temp.fc++; + } + } else { + num_read = sscanf(buffer+1,"%u %u %u %u", + &temp.vert_indices[temp.fc*3+0], + &temp.vert_indices[temp.fc*3+1], + &temp.vert_indices[temp.fc*3+2], + &temp.vert_indices[temp.fc*3+3]); + + if(num_read == 4) { + temp.vert_indices[temp.fc*3+4] = temp.vert_indices[temp.fc*3+0]; + temp.vert_indices[temp.fc*3+5] = temp.vert_indices[temp.fc*3+2]; + temp.fc++; + } + } + temp.fc++; + } + } + fclose(fp); + + bool vert_index_error = false; + bool tex_index_error = false; + bool norm_index_error = false; + + for (unsigned j = 0;j<temp.fc*3;j++){ + + if( temp.vert_indices[j] > 0 ) + temp.vert_indices[j]--; + + if( temp.tex_indices[j] > 0 ) + temp.tex_indices[j]--; + + if( temp.norm_indices[j] > 0 ) + temp.norm_indices[j]--; + + + if( temp.vert_indices[j] > temp.vertices.size()/3 && temp.vert_indices[j] != 0) + { + temp.vert_indices[j] = 0; //This is better than NaN data or a segfault. + vert_index_error = true; + } + + if( temp.tex_indices[j] > temp.tex_coords.size()/2 && temp.tex_indices[j] != 0) + { + temp.tex_indices[j] = 0; + tex_index_error = true; + } + + if( temp.norm_indices[j] > temp.normals.size()/3 && temp.norm_indices[j] != 0 ) + { + temp.norm_indices[j] = 0; + norm_index_error = true; + } + } + + if( vert_index_error ) + { + LOGE << "Error when loading: " << rel_path << " face (v) indices are higher than parsed data, file is probably corrupt." << std::endl; + } + + if( tex_index_error ) + { + LOGE << "Error when loading: " << rel_path << " face (vt) indices are higher than parsed data, file is probably corrupt." << std::endl; + } + + if( norm_index_error ) + { + LOGE << "Error when loading: " << rel_path << " face (vn) indices are higher than parsed data, file is probably corrupt." << std::endl; + } +} + +void LoadSecondUVs(std::string name, TempModel &temp, const ObjFileStats &obj_stats) { + temp.tex_coords2.resize(obj_stats.num_tex*2); + temp.tex_indices2.resize(obj_stats.num_face*6); + + char abs_path[kPathSize]; + FindFilePath(name.c_str(), abs_path, kPathSize, kDataPaths|kModPaths); + FILE *fp = my_fopen(abs_path,"r"); + + int tc2 = 0; + int fc2 = 0; + unsigned int trash; + int num_read; + + char buffer[256]; + while(!feof(fp)) + { + memset(buffer,0,255); + fgets(buffer,256,fp); + + if( strncmp("vt ",buffer,3) == 0 ) { + sscanf((buffer+2),"%f%f", + &temp.tex_coords2[tc2*2+0], + &temp.tex_coords2[tc2*2+1]); + ++tc2; + } else if( strncmp("f ",buffer,2) == 0 ) { + if(obj_stats.num_tex&&obj_stats.num_normal) { + num_read = sscanf(buffer+1,"%u/%u/%u %u/%u/%u %u/%u/%u %u/%u/%u", + &trash, + &temp.tex_indices2[fc2*3+0], + &trash, + + &trash, + &temp.tex_indices2[fc2*3+1], + &trash, + + &trash, + &temp.tex_indices2[fc2*3+2], + &trash, + + &trash, + &temp.tex_indices2[fc2*3+3], + &trash); + if(num_read == 12) { + temp.tex_indices2[fc2*3+4] = temp.tex_indices2[fc2*3+0]; + + temp.tex_indices2[fc2*3+5] = temp.tex_indices2[fc2*3+2]; + + ++fc2; + } + } else if (obj_stats.num_tex) { + num_read = sscanf(buffer+1,"%u/%u %u/%u %u/%u %u/%u", + &trash, + &temp.tex_indices2[fc2*3+0], + + &trash, + &temp.tex_indices2[fc2*3+1], + + &trash, + &temp.tex_indices2[fc2*3+2], + + &trash, + &temp.tex_indices2[fc2*3+3]); + + if(num_read == 8) { + temp.tex_indices2[fc2*3+4] = temp.tex_indices2[fc2*3+0]; + + temp.tex_indices2[fc2*3+5] = temp.tex_indices2[fc2*3+2]; + + ++fc2; + } + } + ++fc2; + } + } + + for (int j = 0;j<fc2*3;j++){ + temp.tex_indices2[j]--; + } + + fclose(fp); +} + +void Model::SaveObj(std::string name) { + std::ofstream file; + char abs_path[kPathSize]; + FormatString(abs_path, kPathSize, "%s%s", GetWritePath(modsource_).c_str(), name.c_str()); + my_ofstream_open(file, abs_path); + + unsigned index = 0; + for(int i=0, len=vertices.size()/3; i<len; ++i){ + file << "v "; + file << vertices[index++] << " "; + file << vertices[index++] << " "; + file << vertices[index++] << "\n"; + } + + if(!tex_coords.empty()){ + index = 0; + for(int i=0, len=vertices.size()/3; i<len; ++i){ + file << "vt "; + file << tex_coords[index++] << " "; + file << tex_coords[index++] << "\n"; + } + } + + if(!normals.empty()){ + index = 0; + for(int i=0, len=vertices.size()/3; i<len; ++i){ + file << "vn "; + file << normals[index++] << " "; + file << normals[index++] << " "; + file << normals[index++] << "\n"; + } + } + + index = 0; + + if(tex_coords.empty() && normals.empty()){ + for(int i=0, len=faces.size()/3; i<len; ++i){ + file << "f "; + file << faces[index++]+1 << " "; + file << faces[index++]+1 << " "; + file << faces[index++]+1 << "\n"; + } + } else if(tex_coords.empty() || normals.empty()){ + for(int i=0, len=faces.size()/3; i<len; ++i){ + file << "f "; + file << faces[index]+1 << "/" << faces[index]+1 << " "; + index++; + file << faces[index]+1 << "/" << faces[index]+1 << " "; + index++; + file << faces[index]+1 << "/" << faces[index]+1 << "\n"; + index++; + } + } else { + for(int i=0, len=faces.size()/3; i<len; ++i){ + file << "f "; + file << faces[index]+1 << "/" << faces[index]+1 << "/" << faces[index]+1 << " "; + index++; + file << faces[index]+1 << "/" << faces[index]+1 << "/" << faces[index]+1 << " "; + index++; + file << faces[index]+1 << "/" << faces[index]+1 << "/" << faces[index]+1 << "\n"; + index++; + } + } + + file.close(); +} + +const int Model::ERROR_MORE_THAN_ONE_OBJECT = 1; + +int Model::SimpleLoadTriangleCutObj(const std::string &name_to_load) { + ObjFileStats obj_stats; + GetObjFileStats(name_to_load, obj_stats, true); + + TempModel temp; + LoadTempModel(name_to_load, temp, obj_stats); + + //Check if the obj file has more than one model object packed into it. + if( obj_stats.objects == 1 ) + { + int num_faces = temp.fc; + int num_vertices = num_faces*3; + + vertices.resize(num_vertices*3); + if(obj_stats.num_normal){ + normals.resize(num_vertices*3); + } else { + normals.clear(); + } + tangents.resize(num_vertices*3,0.0f); + bitangents.resize(num_vertices*3,0.0f); + tex_coords.resize(num_vertices*2); + faces.resize(num_faces*3); + face_normals.resize(num_faces); + + for (int i=0, len=faces.size(); i<len; i++){ + faces[i] = i; + + vertices[i*3+0] = temp.vertices[temp.vert_indices[i]*3+0]; + vertices[i*3+1] = temp.vertices[temp.vert_indices[i]*3+1]; + vertices[i*3+2] = temp.vertices[temp.vert_indices[i]*3+2]; + + if(obj_stats.num_normal){ + normals[i*3+0] = temp.normals[temp.norm_indices[i]*3+0]; + normals[i*3+1] = temp.normals[temp.norm_indices[i]*3+1]; + normals[i*3+2] = temp.normals[temp.norm_indices[i]*3+2]; + } + + if(obj_stats.num_tex){ + tangents[i*3+0] = temp.tangents[temp.tex_indices[i]*3+0]; + tangents[i*3+1] = temp.tangents[temp.tex_indices[i]*3+1]; + tangents[i*3+2] = temp.tangents[temp.tex_indices[i]*3+2]; + + bitangents[i*3+0] = temp.bitangents[temp.tex_indices[i]*3+0]; + bitangents[i*3+1] = temp.bitangents[temp.tex_indices[i]*3+1]; + bitangents[i*3+2] = temp.bitangents[temp.tex_indices[i]*3+2]; + + tex_coords[i*2+0] = temp.tex_coords[temp.tex_indices[i]*2+0]; + tex_coords[i*2+1] = temp.tex_coords[temp.tex_indices[i]*2+1]; + } + } + + tex_coords2.resize(num_vertices*2); + for (int i = 0;i<num_vertices;i++){ + tex_coords2[i*2+0] = tex_coords[i*2+0]; + tex_coords2[i*2+1] = tex_coords[i*2+1]; + } + } + else + { + return ERROR_MORE_THAN_ONE_OBJECT; + } + + return 0; +} + +const char* Model::GetLoadErrorString(int err) +{ + switch( err ) + { + case 0: + return "No error"; + case ERROR_MORE_THAN_ONE_OBJECT: + return "More than one object in .obj file, incompatible with loader."; + default: + return "Unknown error"; + } +} + +void CopyTexCoords2( Model &a, const Model& b ) { + a.tex_coords2.resize(a.vertices.size()/3*2); + std::map<float,std::map<float,std::map<float, + std::map<float,std::map<float,std::map<float, + std::vector<int> > > > > > > dup_map; + unsigned vert_index = 0; + unsigned normal_index = 0; + vec3 n_i; + for(int i=0, len=a.vertices.size()/3; i<len; ++i){ + a.tex_coords2[i*2+0] = 0.0f; + a.tex_coords2[i*2+1] = 0.0f; + if(!a.normals.empty() && !b.normals.empty()){ + n_i = vec3(a.normals[normal_index+0], + a.normals[normal_index+1], + a.normals[normal_index+2]); + } + std::vector<int> &indices = dup_map[floorf(a.vertices[vert_index+0]*100.0f)] + [floorf(a.vertices[vert_index+1]*100.0f)] + [floorf(a.vertices[vert_index+2]*100.0f)] + [floorf(n_i[0]*100.0f)] + [floorf(n_i[1]*100.0f)] + [floorf(n_i[2]*100.0f)]; + normal_index += 3; + vert_index += 3; + indices.push_back(i); + } + + vert_index = 0; + normal_index = 0; + for(int i=0, len=b.vertices.size()/3; i<len; ++i){ + if(!a.normals.empty() && !b.normals.empty()){ + n_i = vec3(b.normals[normal_index+0], + b.normals[normal_index+1], + b.normals[normal_index+2]); + } + std::vector<int> &indices = dup_map[floorf(b.vertices[vert_index+0]*100.0f)] + [floorf(b.vertices[vert_index+1]*100.0f)] + [floorf(b.vertices[vert_index+2]*100.0f)] + [floorf(n_i[0]*100.0f)] + [floorf(n_i[1]*100.0f)] + [floorf(n_i[2]*100.0f)]; + normal_index += 3; + vert_index += 3; + for(unsigned j=0; j<indices.size(); ++j){ + a.tex_coords2[indices[j]*2+0] = b.tex_coords[i*2+0]; + a.tex_coords2[indices[j]*2+1] = b.tex_coords[i*2+1]; + } + } + + dup_map.clear(); + vert_index = 0; + normal_index = 0; + for(int i=0, len=a.vertices.size()/3; i<len; ++i){ + if(!a.normals.empty() && !b.normals.empty()){ + n_i = vec3(a.normals[normal_index+0], + a.normals[normal_index+1], + a.normals[normal_index+2]); + } + std::vector<int> &indices = dup_map[floorf(a.vertices[vert_index+0]*10.0f+0.5f)] + [floorf(a.vertices[vert_index+1]*10.0f+0.5f)] + [floorf(a.vertices[vert_index+2]*10.0f+0.5f)] + [floorf(n_i[0]*10.0f+0.5f)] + [floorf(n_i[1]*10.0f+0.5f)] + [floorf(n_i[2]*10.0f+0.5f)]; + normal_index += 3; + vert_index += 3; + indices.push_back(i); + } + + vert_index = 0; + normal_index = 0; + for(int i=0, len=b.vertices.size()/3; i<len; ++i){ + if(!a.normals.empty() && !b.normals.empty()){ + n_i = vec3(b.normals[normal_index+0], + b.normals[normal_index+1], + b.normals[normal_index+2]); + } + std::vector<int> &indices = dup_map[floorf(b.vertices[vert_index+0]*10.0f+0.5f)] + [floorf(b.vertices[vert_index+1]*10.0f+0.5f)] + [floorf(b.vertices[vert_index+2]*10.0f+0.5f)] + [floorf(n_i[0]*10.0f+0.5f)] + [floorf(n_i[1]*10.0f+0.5f)] + [floorf(n_i[2]*10.0f+0.5f)]; + normal_index += 3; + vert_index += 3; + for(unsigned j=0; j<indices.size(); ++j){ + if(a.tex_coords2[indices[j]*2+0] == 0.0f && + a.tex_coords2[indices[j]*2+1] == 0.0f) + { + a.tex_coords2[indices[j]*2+0] = b.tex_coords[i*2+0]; + a.tex_coords2[indices[j]*2+1] = b.tex_coords[i*2+1]; + } + } + } + + dup_map.clear(); + vert_index = 0; + normal_index = 0; + for(int i=0, len=a.vertices.size()/3; i<len; ++i){ + if(!a.normals.empty() && !b.normals.empty()){ + n_i = vec3(a.normals[normal_index+0], + a.normals[normal_index+1], + a.normals[normal_index+2]); + } + std::vector<int> &indices = dup_map[floorf(a.vertices[vert_index+0]*1.0f+0.5f)] + [floorf(a.vertices[vert_index+1]*1.0f+0.5f)] + [floorf(a.vertices[vert_index+2]*1.0f+0.5f)] + [floorf(n_i[0]*1.0f+0.5f)] + [floorf(n_i[1]*1.0f+0.5f)] + [floorf(n_i[2]*1.0f+0.5f)]; + normal_index += 3; + vert_index += 3; + indices.push_back(i); + } + + vert_index = 0; + normal_index = 0; + for(int i=0, len=b.vertices.size()/3; i<len; ++i){ + if(!a.normals.empty() && !b.normals.empty()){ + n_i = vec3(b.normals[normal_index+0], + b.normals[normal_index+1], + b.normals[normal_index+2]); + } + std::vector<int> &indices = dup_map[floorf(b.vertices[vert_index+0]*1.0f+0.5f)] + [floorf(b.vertices[vert_index+1]*1.0f+0.5f)] + [floorf(b.vertices[vert_index+2]*1.0f+0.5f)] + [floorf(n_i[0]*1.0f+0.5f)] + [floorf(n_i[1]*1.0f+0.5f)] + [floorf(n_i[2]*1.0f+0.5f)]; + normal_index += 3; + vert_index += 3; + for(unsigned j=0; j<indices.size(); ++j){ + if(a.tex_coords2[indices[j]*2+0] == 0.0f && + a.tex_coords2[indices[j]*2+1] == 0.0f) + { + a.tex_coords2[indices[j]*2+0] = b.tex_coords[i*2+0]; + a.tex_coords2[indices[j]*2+1] = b.tex_coords[i*2+1]; + } + } + } +} + + +void Model::LoadObj(const std::string &rel_path, char flags, const std::string &alt_name, const PathFlags searchPaths) { + + //LOGI << "Loading model: " << rel_path << std::endl; + const char* fail_whale = "./Data/Models/Default/default_model_2.obj"; + + bool center = (flags & _MDL_CENTER)!=0; + bool simple = (flags & _MDL_SIMPLE)!=0; + use_tangent = (flags & _MDL_USE_TANGENT)!=0; + + bool load_model = true; + + Dispose(); + + path = rel_path; + + std::string name_to_load = rel_path; + const std::string &cache_name_to_load = SanitizePath(alt_name.empty()?rel_path:alt_name); + + char abs_path[kPathSize], abs_cache_path[kPathSize]; + ModID modsource, cache_modsource; + bool found_model = (FindFilePath(name_to_load.c_str(), abs_path, kPathSize, searchPaths, false, NULL, &modsource) != -1); + bool found_cache = (FindFilePath((cache_name_to_load+".cache").c_str(), abs_cache_path, kPathSize, searchPaths, false, NULL, &cache_modsource) != -1); + + if (!found_model && !found_cache) { // To do: fix bug when only cache exists + std::string error_msg = "Whale fail :(\nModel file \"" + std::string(rel_path) + "\" not found. Loading the fail whale instead."; + DisplayError("Error", error_msg.c_str(), _ok, false); + name_to_load = fail_whale; + } + + if(found_model){ + checksum = Checksum(abs_path); + } + + if(found_cache){ + FILE *cache_file = my_fopen(abs_cache_path, "rb"); + if(cache_file){ + unsigned short file_checksum = 0; + fread(&file_checksum, sizeof(unsigned short), 1, cache_file); + unsigned short version; + fread(&version, sizeof(unsigned short), 1, cache_file); + if(checksum == file_checksum && version == _model_cache_version){ + ReadFromFile(cache_file); + load_model = false; + modsource_ = cache_modsource; + } + fclose(cache_file); + } + } + + if (load_model) { + int modelLoadError = SimpleLoadTriangleCutObj(name_to_load); + + if( 0 == modelLoadError ) + { + precollapse_num_vertices = vertices.size()/3; + + ObjFileStats obj_stats2; + bool uv2_file = GetObjFileStats(name_to_load+"_UV2", obj_stats2, false); + if (uv2_file) { + Model temp2; + int uv2_err = temp2.SimpleLoadTriangleCutObj(name_to_load+"_UV2"); + + if( uv2_err == 0 ) + { + //CopyTexCoords2((*this), temp2); + if(temp2.vertices.size() != vertices.size()){ + DisplayError("Error", ("Mismatched number of vertices in: "+ + (name_to_load+"_UV2") + ". Will not use data.").c_str() ); + } + else + { + for(int i=0, len=vertices.size()/3*2; i<len; ++i){ + tex_coords2[i] = temp2.tex_coords[i]; + } + } + } + else + { + DisplayError("Error", ("Malformed data in: "+ + (name_to_load+"_UV2" + ", reason: " + GetLoadErrorString(uv2_err) + " Will not use data.")).c_str() ); + } + } + + + if(flags & _MDL_FLIP_FACES){ + for(unsigned i=0; i<faces.size(); i+=3){ + std::swap(faces[i+0], faces[i+2]); + } + } + + if(!simple){ + if(normals.empty()) { + calcNormals(); + } else { + calcFaceNormals(); + for(int i=0, len=normals.size(); i<len; i+=3){ + float length = sqrtf(square(normals[i]) + square(normals[i+1]) + square(normals[i+2])); + if(length != 0.0f){ + normals[i+0] /= length; + normals[i+1] /= length; + normals[i+2] /= length; + } + } + } + calcTangents(); + } else { + tex_coords.clear(); + tex_coords.resize(vertices.size()/3*2, 0.0f); + normals.resize(vertices.size(), 0.0f); + } + calcBoundingBox(); + old_center = center_coords; + if(center) { + CenterModel(); + } + calcBoundingSphere(); + + CalcTexelDensity(); + CalcAverageTriangleEdge(); + + RemoveDuplicatedVerts(); + RemoveDegenerateTriangles(); + OptimizeTriangleOrder(); + OptimizeVertexOrder(); + + FILE *cache_file = my_fopen((GetWritePath(modsource) + cache_name_to_load + ".cache").c_str(), "wb"); + if(cache_file){ + fwrite(&checksum, sizeof(unsigned short), 1, cache_file); + fwrite(&_model_cache_version, sizeof(unsigned short), 1, cache_file); + WriteToFile(cache_file); + fclose(cache_file); + } + modsource_ = modsource; + } + else + { + if( fail_whale == rel_path ) + { + FatalError( "Error", "Unable to load fail whale, this is bad."); + } + else + { + std::string error_msg = "Whale fail :(\nModel file \"" + std::string(rel_path) + "\" is corrupt because: "+ GetLoadErrorString(modelLoadError) + ". Loading the fail whale instead."; + DisplayError("Error", error_msg.c_str(), _ok, false); + + LoadObj(fail_whale, flags, fail_whale); + return; //Quick out. + } + } + } + vbo_enabled = true; + //PrintACMR(); +} + +void DrawModelVerts(Model& model, const char* vert_attrib_str, Shaders* shaders, Graphics* graphics, int shader) { + if(!model.vbo_loaded){ + model.createVBO(); + } + model.VBO_vertices.Bind(); + model.VBO_faces.Bind(); + int vert_attrib_id = shaders->returnShaderAttrib(vert_attrib_str, shader); + graphics->EnableVertexAttribArray(vert_attrib_id); + glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 3*sizeof(GLfloat), 0); + graphics->DrawElements(GL_TRIANGLES, model.faces.size(), GL_UNSIGNED_INT, 0); + graphics->ResetVertexAttribArrays(); + graphics->BindArrayVBO(0); + graphics->BindElementVBO(0); +} + +//Draw model +void Model::Draw() { + PROFILER_GPU_ZONE(g_profiler_ctx, "Model::Draw"); + Graphics *graphics = Graphics::Instance(); + CHECK_GL_ERROR(); + if(vbo_enabled){ + if(!vbo_loaded){ + createVBO(); + } + CHECK_GL_ERROR(); + int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0; + if(!tangents.empty()){ + flags |= F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2; + } + if (!tex_coords2.empty()) { + flags |= F_TEXTURE_COORD_ARRAY3; + } + if (!aux.empty()) { + flags |= F_COLOR_ARRAY; + } + CHECK_GL_ERROR(); + graphics->SetClientStates(flags); + CHECK_GL_ERROR(); + VBO_vertices.Bind(); + CHECK_GL_ERROR(); + glVertexPointer(3, GL_FLOAT, 0, 0); + CHECK_GL_ERROR(); + VBO_normals.Bind(); + CHECK_GL_ERROR(); + glNormalPointer(GL_FLOAT, 0, 0); + CHECK_GL_ERROR(); + graphics->SetClientActiveTexture(0); + CHECK_GL_ERROR(); + VBO_tex_coords.Bind(); + glTexCoordPointer(2, GL_FLOAT, 0, 0); + CHECK_GL_ERROR(); + if(!tangents.empty()){ + graphics->SetClientActiveTexture(1); + CHECK_GL_ERROR(); + VBO_tangents.Bind(); + glTexCoordPointer(3, GL_FLOAT, 0, 0); + graphics->SetClientActiveTexture(2); + CHECK_GL_ERROR(); + VBO_bitangents.Bind(); + glTexCoordPointer(3, GL_FLOAT, 0, 0); + CHECK_GL_ERROR(); + } + if (!tex_coords2.empty()) { + graphics->SetClientActiveTexture(3); + CHECK_GL_ERROR(); + VBO_tex_coords2.Bind(); + glTexCoordPointer(2, GL_FLOAT, 0, 0); + CHECK_GL_ERROR(); + } + if (!aux.empty()) { + VBO_aux.Bind(); + glColorPointer(3, GL_FLOAT, 0, 0); + CHECK_GL_ERROR(); + } + VBO_faces.Bind(); + graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, 0); + CHECK_GL_ERROR(); + if (!tex_coords2.empty()) { + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY3,false); + } + if(!tangents.empty()){ + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false); + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false); + } + CHECK_GL_ERROR(); + Graphics::Instance()->SetClientActiveTexture(0); + CHECK_GL_ERROR(); + graphics->BindArrayVBO(0); + CHECK_GL_ERROR(); + graphics->BindElementVBO(0); + CHECK_GL_ERROR(); + } + else if (transform_vec.empty()) { + int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0; + if(!tangents.empty()){ + flags |= F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2; + } + if (!tex_coords2.empty()) { + flags |= F_TEXTURE_COORD_ARRAY3; + } + if (!aux.empty()) { + flags |= F_COLOR_ARRAY; + } + if(!bone_weights.empty()){ + flags |= F_TEXTURE_COORD_ARRAY5; + } + if(!bone_ids.empty()){ + flags |= F_TEXTURE_COORD_ARRAY6; + } + graphics->SetClientStates(flags); + graphics->SetClientActiveTexture(0); + glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords[0]); + glNormalPointer(GL_FLOAT, 0, &normals[0]); + glVertexPointer(3, GL_FLOAT, 0, &vertices[0]); + if(!tangents.empty()){ + graphics->SetClientActiveTexture(1); + glTexCoordPointer(3, GL_FLOAT, 0, &tangents[0]); + graphics->SetClientActiveTexture(2); + glTexCoordPointer(3, GL_FLOAT, 0, &bitangents[0]); + } + if(!tex_coords2.empty()){ + graphics->SetClientActiveTexture(3); + glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords2[0]); + } + if(!aux.empty()){ + glColorPointer(3, GL_FLOAT, 0, &aux[0]); + } + if(!bone_weights.empty()){ + graphics->SetClientActiveTexture(5); + glTexCoordPointer(4, GL_FLOAT, 0, &bone_weights[0]); + } + if(!bone_ids.empty()){ + graphics->SetClientActiveTexture(6); + glTexCoordPointer(4, GL_FLOAT, 0, &bone_ids[0]); + } + graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, &faces[0]); + if(!tex_coords2.empty()){ + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY3,false); + } + if(!bone_weights.empty()){ + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY5,false); + } + if(!bone_ids.empty()){ + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY6,false); + } + if(!tangents.empty()){ + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false); + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false); + } + Graphics::Instance()->SetClientActiveTexture(0); + } else { + int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0 | F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2 | F_TEXTURE_COORD_ARRAY4 | F_TEXTURE_COORD_ARRAY5; + if(!tangents.empty()){ + flags |= F_TEXTURE_COORD_ARRAY3; + } + if (!tex_coords2.empty()) { + flags |= F_TEXTURE_COORD_ARRAY3; + } + if (!aux.empty()) { + flags |= F_COLOR_ARRAY; + } + if(!bone_weights.empty()){ + flags |= F_TEXTURE_COORD_ARRAY5; + } + if(!bone_ids.empty()){ + flags |= F_TEXTURE_COORD_ARRAY6; + } + graphics->SetClientStates(flags); + glNormalPointer(GL_FLOAT, 0, &normals[0]); + glVertexPointer(3, GL_FLOAT, 0, &vertices[0]); + graphics->SetClientActiveTexture(0); + glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords[0]); + graphics->SetClientActiveTexture(1); + glTexCoordPointer(4, GL_FLOAT, 0, &transform_vec[0][0]); + graphics->SetClientActiveTexture(2); + glTexCoordPointer(4, GL_FLOAT, 0, &transform_vec[1][0]); + if(!tangents.empty()){ + graphics->SetClientActiveTexture(3); + glTexCoordPointer(3, GL_FLOAT, 0, &tangents[0]); + } + graphics->SetClientActiveTexture(4); + glTexCoordPointer(4, GL_FLOAT, 0, &transform_vec[2][0]); + graphics->SetClientActiveTexture(5); + glTexCoordPointer(4, GL_FLOAT, 0, &transform_vec[3][0]); + graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, &faces[0]); + if(!tangents.empty()){ + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY3,false); + } + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY4,false); + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY5,false); + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false); + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false); + Graphics::Instance()->SetClientActiveTexture(0); + } + + CHECK_GL_ERROR(); +} + +void Model::StartPreset() { + Graphics *graphics = Graphics::Instance(); + if(vbo_enabled){ + if(!vbo_loaded){ + createVBO(); + } + int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0; + if(!tangents.empty()){ + flags |= F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2; + } + if (!tex_coords2.empty()) { + flags |= F_TEXTURE_COORD_ARRAY3; + } + if (!aux.empty()) { + flags |= F_COLOR_ARRAY; + } + graphics->SetClientStates(flags); + VBO_vertices.Bind(); + glVertexPointer(3, GL_FLOAT, 0, 0); + VBO_normals.Bind(); + glNormalPointer(GL_FLOAT, 0, 0); + graphics->SetClientActiveTexture(0); + VBO_tex_coords.Bind(); + glTexCoordPointer(2, GL_FLOAT, 0, 0); + if(!tangents.empty()){ + graphics->SetClientActiveTexture(1); + VBO_tangents.Bind(); + glTexCoordPointer(3, GL_FLOAT, 0, 0); + graphics->SetClientActiveTexture(2); + VBO_bitangents.Bind(); + glTexCoordPointer(3, GL_FLOAT, 0, 0); + } + if (!tex_coords2.empty()) { + graphics->SetClientActiveTexture(3); + VBO_tex_coords2.Bind(); + glTexCoordPointer(2, GL_FLOAT, 0, 0); + } + if (!aux.empty()) { + VBO_aux.Bind(); + glColorPointer(3, GL_FLOAT, 0, 0); + } + VBO_faces.Bind(); + } +} + +void Model::EndPreset() { + if(vbo_enabled){ + if (!aux.empty()) { + //glClientActiveTexture(GL_TEXTURE4); + //glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } + + if (!tex_coords2.empty()) { + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY3,false); + } + + if(!tangents.empty()){ + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false); + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false); + } + Graphics::Instance()->SetClientActiveTexture(0); + Graphics::Instance()->BindArrayVBO(0); + Graphics::Instance()->BindElementVBO(0); + } +} + +void Model::DrawPreset() +{ + if(vbo_enabled){ + Graphics::Instance()->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, 0); + } +} + +struct AuxVertData { + std::vector<int> verts; + vec3 total_aux; +}; + +bool Model::LoadAuxFromImage(const std::string &image_path) { + bool ret = true; + if(aux.empty()) { + ImageSamplerRef image = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(image_path); + + if(image.valid()) { + image_samplers.insert(image); + + aux.resize(vertices.size()); + for(int i=0, len=vertices.size()/3; i<len; i++) { + vec4 color = image->GetInterpolatedColorUV(tex_coords[i*2+0], tex_coords[i*2+1]); + aux[i*3+0] = color.r(); + aux[i*3+1] = color.g(); + aux[i*3+2] = color.b(); + } + + // Duplicate map keeps a list of all vertices that share the same position + std::map<vec3, AuxVertData> dup_map; + unsigned vert_index = 0; + for(int i=0, len=vertices.size()/3; i<len; i++) { + vec3 index(vertices[vert_index+0], + vertices[vert_index+1], + vertices[vert_index+2]); + AuxVertData &avd = dup_map[index]; + avd.verts.push_back(i); + avd.total_aux[0] += aux[vert_index + 0]; + avd.total_aux[1] += aux[vert_index + 1]; + avd.total_aux[2] += aux[vert_index + 2]; + vert_index += 3; + } + std::map<vec3, AuxVertData>::iterator iter; + for(iter = dup_map.begin(); iter != dup_map.end(); ++iter){ + AuxVertData &avd = iter->second; + if(avd.verts.size() > 1){ + avd.total_aux /= (float)avd.verts.size(); + for(unsigned i=0; i<avd.verts.size(); ++i){ + int index = avd.verts[i]*3; + aux[index + 0] = avd.total_aux[0]; + aux[index + 1] = avd.total_aux[2]; + aux[index + 2] = avd.total_aux[1]; + } + } + } + } else { + LOGE << "Failed to load wind map from image: " << image_path << std::endl; + ret = false; + } + } else { + //Nop instruction, this state is ok. + } + return ret; +} + +void Model::DrawAltTexCoords() +{ + PROFILER_GPU_ZONE(g_profiler_ctx, "Model::DrawAltTexCoords"); + CHECK_GL_ERROR(); + + Graphics *graphics = Graphics::Instance(); + int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0; + if(!tangents.empty()){ + flags |= F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2; + } + graphics->SetClientStates(flags); + + if(vbo_enabled){ + if(!vbo_loaded){ + createVBO(); + } + VBO_vertices.Bind(); + glVertexPointer(3, GL_FLOAT, 0, 0); + VBO_normals.Bind(); + glNormalPointer(GL_FLOAT, 0, 0); + graphics->SetClientActiveTexture(0); + VBO_tex_coords.Bind(); + glTexCoordPointer(2, GL_FLOAT, 0, 0); + if (!tex_coords2.empty()) { + VBO_tex_coords2.Bind(); + glTexCoordPointer(2, GL_FLOAT, 0, 0); + } + if(!tangents.empty()){ + graphics->SetClientActiveTexture(1); + VBO_tangents.Bind(); + glTexCoordPointer(3, GL_FLOAT, 0, 0); + graphics->SetClientActiveTexture(2); + VBO_bitangents.Bind(); + glTexCoordPointer(3, GL_FLOAT, 0, 0); + } + + VBO_faces.Bind(); + graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, 0); + + if(!tangents.empty()){ + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false); + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false); + } + Graphics::Instance()->SetClientActiveTexture(0); + Graphics::Instance()->BindArrayVBO(0); + Graphics::Instance()->BindElementVBO(0); + } + else + { + graphics->SetClientActiveTexture(0); + glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords[0]); + glNormalPointer(GL_FLOAT, 0, &normals[0]); + glVertexPointer(3, GL_FLOAT, 0, &vertices[0]); + + if(!tex_coords2.empty()){ + glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords2[0]); + } + if(!tangents.empty()){ + graphics->SetClientActiveTexture(1); + glTexCoordPointer(3, GL_FLOAT, 0, &tangents[0]); + graphics->SetClientActiveTexture(2); + glTexCoordPointer(3, GL_FLOAT, 0, &bitangents[0]); + } + + graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, &faces[0]); + + if(!tangents.empty()){ + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false); + Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false); + } + Graphics::Instance()->SetClientActiveTexture(0); + } + + CHECK_GL_ERROR(); +} + +void Model::DrawToTextureCoords() +{ + PROFILER_GPU_ZONE(g_profiler_ctx, "Model::DrawToTextureCoords"); + CHECK_GL_ERROR(); + + Graphics *graphics = Graphics::Instance(); + int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0; + if(!tangents.empty()){ + flags |= F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2; + } + if (!tex_coords2.empty()) { + flags |= F_TEXTURE_COORD_ARRAY3; + } + CHECK_GL_ERROR(); + graphics->SetClientStates(flags); + CHECK_GL_ERROR(); + + if(vbo_enabled){ + if(!vbo_loaded){ + createVBO(); + } + VBO_tex_coords.Bind(); + glVertexPointer(2, GL_FLOAT, 0, 0); + VBO_normals.Bind(); + glNormalPointer(GL_FLOAT, 0, 0); + graphics->SetClientActiveTexture(0); + VBO_tex_coords.Bind(); + glTexCoordPointer(2, GL_FLOAT, 0, 0); + if(!tangents.empty()){ + graphics->SetClientActiveTexture(1); + VBO_tangents.Bind(); + glTexCoordPointer(3, GL_FLOAT, 0, 0); + graphics->SetClientActiveTexture(2); + VBO_bitangents.Bind(); + glTexCoordPointer(3, GL_FLOAT, 0, 0); + } + if (!tex_coords2.empty()) { + graphics->SetClientActiveTexture(3); + VBO_tex_coords2.Bind(); + glTexCoordPointer(2, GL_FLOAT, 0, 0); + } + VBO_faces.Bind(); + graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, 0); + Graphics::Instance()->SetClientActiveTexture(0); + Graphics::Instance()->BindArrayVBO(0); + Graphics::Instance()->BindElementVBO(0); + } else { + graphics->SetClientActiveTexture(0); + glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords[0]); + glNormalPointer(GL_FLOAT, 0, &normals[0]); + glVertexPointer(2, GL_FLOAT, 0, &tex_coords[0]); + + if(!tangents.empty()){ + graphics->SetClientActiveTexture(1); + glTexCoordPointer(3, GL_FLOAT, 0, &tangents[0]); + graphics->SetClientActiveTexture(2); + glTexCoordPointer(3, GL_FLOAT, 0, &bitangents[0]); + } + if(!tex_coords2.empty()){ + graphics->SetClientActiveTexture(3); + glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords2[0]); + } + graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, &faces[0]); + Graphics::Instance()->SetClientActiveTexture(0); + } + graphics->SetClientStates(F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0); + + CHECK_GL_ERROR(); +} + +void Model::calcBoundingBox() { + min_coords=0; + max_coords=0; + center_coords = 0; + + for(int i=0, len=vertices.size()/3, vert_index=0; i<len; i++, vert_index+=3) { + for(int j=0; j<3; ++j){ + float coord = vertices[vert_index+j]; + if(i==0 || coord > max_coords[j]){ + max_coords[j] = coord; + } + if(i==0 || coord < min_coords[j]){ + min_coords[j] = coord; + } + } + } + + center_coords = (min_coords + max_coords)/2; +} + +//Calculate bounding sphere +void Model::calcBoundingSphere() { + bounding_sphere_origin = center_coords; + + float longest_distance=0; + vec3 vert; + for (int i=0, len=vertices.size()/3; i<len; i++) { + vert.x()=vertices[i*3]; + vert.y()=vertices[i*3+1]; + vert.z()=vertices[i*3+2]; + if(distance_squared(bounding_sphere_origin, vert)>longest_distance) + longest_distance=distance_squared(bounding_sphere_origin, vert); + } + bounding_sphere_radius=sqrtf(longest_distance); +} + +// must be called after center_coords has been set (i.e. after call to calcBoundingSphere +void Model::CenterModel() { + for(int i=0, len=vertices.size()/3; i<len; i++) { + vertices[3*i] -= center_coords.x(); + vertices[3*i+1] -= center_coords.y(); + vertices[3*i+2] -= center_coords.z(); + } + center_coords = vec3(0,0,0); + + calcBoundingBox(); +} + +void Model::ResizeVertices(int size) { + vertices.resize(size*3); + normals.resize(size*3); + tangents.resize(size*3); + bitangents.resize(size*3); + tex_coords.resize(size*2); + if(!bone_weights.empty()){ + bone_weights.resize(size); + }if(!bone_ids.empty()){ + bone_ids.resize(size); + } +} + +void Model::ResizeFaces(int size) { + bool success = false; + while(!success){ + try { + faces.resize(size*3); + face_normals.resize(size); + success = true; + } catch (std::bad_alloc const&) { + success = false; + SDL_Delay(100); + LOGE << "Vector alloc failed, trying again..." << std::endl; + } + } +} + + +void Model::calcFaceNormals() { + face_normals.resize(faces.size() / 3); + #pragma omp parallel for + for(int i=0, len=faces.size()/3; i<len; i++){ + face_normals[i][0] = (vertices[faces[i*3+1]*3+1] - vertices[faces[i*3+0]*3+1])*(vertices[faces[i*3+2]*3+2] - vertices[faces[i*3+0]*3+2]) - (vertices[faces[i*3+1]*3+2] - vertices[faces[i*3+0]*3+2])*(vertices[faces[i*3+2]*3+1] - vertices[faces[i*3+0]*3+1]); + face_normals[i][1] = (vertices[faces[i*3+1]*3+2] - vertices[faces[i*3+0]*3+2])*(vertices[faces[i*3+2]*3+0] - vertices[faces[i*3+0]*3+0]) - (vertices[faces[i*3+1]*3+0] - vertices[faces[i*3+0]*3+0])*(vertices[faces[i*3+2]*3+2] - vertices[faces[i*3+0]*3+2]); + face_normals[i][2] = (vertices[faces[i*3+1]*3+0] - vertices[faces[i*3+0]*3+0])*(vertices[faces[i*3+2]*3+1] - vertices[faces[i*3+0]*3+1]) - (vertices[faces[i*3+1]*3+1] - vertices[faces[i*3+0]*3+1])*(vertices[faces[i*3+2]*3+0] - vertices[faces[i*3+0]*3+0]); + } +} + +void Model::calcNormals() { + normals.resize(vertices.size()); + + #pragma omp parallel for + for(int i=0, len=vertices.size()/3; i<len; i++){ + normals[i*3+0]=0; + normals[i*3+1]=0; + normals[i*3+2]=0; + } + + //vec3 edge1,edge2; + + calcFaceNormals(); + for(int i=0, len=faces.size()/3; i<len; i++){ + normals[faces[i*3+0]*3+0]+=face_normals[i].x(); + normals[faces[i*3+0]*3+1]+=face_normals[i].y(); + normals[faces[i*3+0]*3+2]+=face_normals[i].z(); + + normals[faces[i*3+1]*3+0]+=face_normals[i].x(); + normals[faces[i*3+1]*3+1]+=face_normals[i].y(); + normals[faces[i*3+1]*3+2]+=face_normals[i].z(); + + normals[faces[i*3+2]*3+0]+=face_normals[i].x(); + normals[faces[i*3+2]*3+1]+=face_normals[i].y(); + normals[faces[i*3+2]*3+2]+=face_normals[i].z(); + } + #pragma omp parallel for + for(int i=0, len=vertices.size()/3; i<len; i++){ + float length=sqrtf(square(normals[i*3+0])+square(normals[i*3+1])+square(normals[i*3+2])); + + //For some reasons the normals are sometimes 0,0,0 We have to check for this to prevent NaN. + if( length != 0.0f ) + { + normals[i*3+0]/=length; + normals[i*3+1]/=length; + normals[i*3+2]/=length; + } + } + #pragma omp parallel for + for(int i=0, len=faces.size()/3; i<len; i++){ + face_normals[i] = normalize(face_normals[i]); + } +} + +void Model::calcTangents() { + tangents.resize(vertices.size()); + bitangents.resize(vertices.size()); +#pragma omp parallel for + for(int i=0, len=vertices.size(); i<len; i+=3){ + tangents[i+0]=0; + tangents[i+1]=0; + tangents[i+2]=0; + + bitangents[i+0]=0; + bitangents[i+1]=0; + bitangents[i+2]=0; + } +#pragma omp parallel for + for (long a=0, len=faces.size()/3; a<len; a++) + { + float x1, x2, y1, y2, z1, z2; + float s1, s2, t1, t2; + float r; + + vec3 sdir; + vec3 tdir; + + x1 = vertices[faces[a*3+1]*3+0] - vertices[faces[a*3+0]*3+0]; + x2 = vertices[faces[a*3+2]*3+0] - vertices[faces[a*3+0]*3+0]; + y1 = vertices[faces[a*3+1]*3+1] - vertices[faces[a*3+0]*3+1]; + y2 = vertices[faces[a*3+2]*3+1] - vertices[faces[a*3+0]*3+1]; + z1 = vertices[faces[a*3+1]*3+2] - vertices[faces[a*3+0]*3+2]; + z2 = vertices[faces[a*3+2]*3+2] - vertices[faces[a*3+0]*3+2]; + + s1 = tex_coords[faces[a*3+1]*2+0] - tex_coords[faces[a*3+0]*2+0]; + s2 = tex_coords[faces[a*3+2]*2+0] - tex_coords[faces[a*3+0]*2+0]; + t1 = tex_coords[faces[a*3+1]*2+1] - tex_coords[faces[a*3+0]*2+1]; + t2 = tex_coords[faces[a*3+2]*2+1] - tex_coords[faces[a*3+0]*2+1]; + + float denom = (s1 * t2 - s2 * t1); + if(denom != 0.0f) { + r = 1.0F / denom; + } else { + r = 99999.0f; + } + sdir = vec3((t2 * x1 - t1 * x2) * r, + (t2 * y1 - t1 * y2) * r, + (t2 * z1 - t1 * z2) * r); + + tdir = vec3((s1 * x2 - s2 * x1) * r, + (s1 * y2 - s2 * y1) * r, + (s1 * z2 - s2 * z1) * r); + + #pragma omp critical + { + tangents[faces[a*3+0]*3+0] += sdir.x(); + tangents[faces[a*3+0]*3+1] += sdir.y(); + tangents[faces[a*3+0]*3+2] += sdir.z(); + tangents[faces[a*3+1]*3+0] += sdir.x(); + tangents[faces[a*3+1]*3+1] += sdir.y(); + tangents[faces[a*3+1]*3+2] += sdir.z(); + tangents[faces[a*3+2]*3+0] += sdir.x(); + tangents[faces[a*3+2]*3+1] += sdir.y(); + tangents[faces[a*3+2]*3+2] += sdir.z(); + + bitangents[faces[a*3+0]*3+0] += tdir.x(); + bitangents[faces[a*3+0]*3+1] += tdir.y(); + bitangents[faces[a*3+0]*3+2] += tdir.z(); + bitangents[faces[a*3+1]*3+0] += tdir.x(); + bitangents[faces[a*3+1]*3+1] += tdir.y(); + bitangents[faces[a*3+1]*3+2] += tdir.z(); + bitangents[faces[a*3+2]*3+0] += tdir.x(); + bitangents[faces[a*3+2]*3+1] += tdir.y(); + bitangents[faces[a*3+2]*3+2] += tdir.z(); + } + } + +#pragma omp parallel for + for(int i=0, len=vertices.size(); i<len; i+=3) { + vec3 n(normals[i+0],normals[i+1],normals[i+2]); + vec3 t(tangents[i+0],tangents[i+1],tangents[i+2]); + + // Gram-Schmidt orthogonalize + vec3 tangent = normalize(t - n * dot(n, t)); + + tangents[i+0]=tangent.x(); + tangents[i+1]=tangent.y(); + tangents[i+2]=tangent.z(); + } + +#pragma omp parallel for + for(int i=0, len=vertices.size(); i<len; i+=3){ + vec3 n(normals[i+0],normals[i+1],normals[i+2]); + vec3 t(bitangents[i+0],bitangents[i+1],bitangents[i+2]); + + // Gram-Schmidt orthogonalize + vec3 tangent = normalize(t - n * dot(n, t)); + + bitangents[i+0]=tangent.x(); + bitangents[i+1]=tangent.y(); + bitangents[i+2]=tangent.z(); + } +} + +//Set up simp_vertex buffer object +void Model::createVBO() { + CHECK_GL_ERROR(); + + VBO_vertices.Fill(kVBOFloat | kVBOStatic, vertices.size()*sizeof(float), &vertices[0]); + if(!normals.empty()){ + VBO_normals.Fill(kVBOFloat | kVBOStatic, vertices.size()*sizeof(float), &normals[0]); + } + CHECK_GL_ERROR(); + if(!tangents.empty()){ + VBO_tangents.Fill(kVBOFloat | kVBOStatic, vertices.size()*sizeof(float), &tangents[0]); + } + CHECK_GL_ERROR(); + if(!bitangents.empty()){ + VBO_bitangents.Fill(kVBOFloat | kVBOStatic, vertices.size()*sizeof(float), &bitangents[0]); + } + + CHECK_GL_ERROR(); + VBO_tex_coords.Fill(kVBOFloat | kVBOStatic, vertices.size()/3*2*sizeof(float), &tex_coords[0]); + + CHECK_GL_ERROR(); + if (!tex_coords2.empty()) { + VBO_tex_coords2.Fill(kVBOFloat | kVBOStatic, vertices.size()/3*2*sizeof(float), &tex_coords2[0]); + } + + if (!aux.empty()) { + VBO_aux.Fill(kVBOFloat | kVBOStatic, vertices.size()*sizeof(float), &aux[0]); + } + + CHECK_GL_ERROR(); + VBO_faces.Fill(kVBOElement | kVBOStatic, faces.size()*sizeof(GLuint), &faces[0]); + + CHECK_GL_ERROR(); + Graphics *graphics = Graphics::Instance(); + graphics->BindArrayVBO(0); + graphics->BindElementVBO(0); + vbo_loaded = true; + + CHECK_GL_ERROR(); +} + +struct ChildHit { + float time; + bool hit; + int child; +}; + +class ChildHitCompare { + public: + bool operator()(const ChildHit &a, const ChildHit &b) { + return a.time < b.time; + } +}; + +//Check a line against the model for collision +int Model::lineCheck(const vec3& p1,const vec3& p2, vec3* p, vec3 *normal, bool backface) const { + if(!sphere_line_intersection(p1,p2,bounding_sphere_origin,bounding_sphere_radius))return -1; + + float distance; + float olddistance=0; + int intersecting=0; + int firstintersecting=-1; + vec3 point; + vec3 end = p2; + int face_index, vert_index; + vec3 points[3]; + for (int j=0, len=faces.size()/3;j<len;j++){ + face_index = j*3; + for(unsigned k=0; k<3; ++k){ + vert_index = faces[face_index+k]*3; + points[k][0] = vertices[vert_index+0]; + points[k][1] = vertices[vert_index+1]; + points[k][2] = vertices[vert_index+2]; + } + intersecting=LineFacet(p1,end,points[0],points[1],points[2],&point,face_normals[j]); + if(intersecting){ + distance=distance_squared(p1, point); + if(distance<olddistance||firstintersecting==-1){ + olddistance=distance; firstintersecting=j; if(p)*p=point; end=point; if(normal)*normal = face_normals[j];} + } + } + if(firstintersecting!=-1)return firstintersecting; + + return -1; +} + +//Check a line against the model for collision against front face only +int Model::lineCheckNoBackface(const vec3& p1,const vec3& p2, vec3* p, vec3 *normal) const { + return lineCheck(p1, p2, p, normal, false); +} + +float StaticCalcAverageTriangleEdge(const std::vector<unsigned> &faces, const std::vector<float> &vertices) { + float total_vert = 0.0f; + int num_sample = 0; + for(int i=0, len=faces.size(); i<len; i+=3){ + const float *vert[3]; + for(int j=0; j<3; j++) { + int vert_index = faces[i+j]; + vert[j] = &vertices[vert_index*3]; + } + for(int j=0; j<3; j++) { + int next = (j+1)%3; + float vert_distance = sqrtf( square(vert[j][0]-vert[next][0]) + + square(vert[j][1]-vert[next][1]) + + square(vert[j][2]-vert[next][2])); + if(vert_distance != 0.0f){ + ++num_sample; + total_vert += vert_distance; + } + } + } + return total_vert / ((float)num_sample); +} + +void Model::CalcAverageTriangleEdge() { + average_triangle_edge_length = StaticCalcAverageTriangleEdge(faces, vertices); +} + +void Model::CopyFacesFromModel( const Model &source_model, const std::vector<int> ©_faces ) { + vbo_loaded = false; + vbo_enabled = false; + + int num_faces = copy_faces.size(); + faces.resize(num_faces*3); + face_normals.resize(num_faces); + + for(int i=0; i<num_faces; i++){ + const int index = i*3; + const int copy_index = copy_faces[i]*3; + faces[index+0] = source_model.faces[copy_index+0]; + faces[index+1] = source_model.faces[copy_index+1]; + faces[index+2] = source_model.faces[copy_index+2]; + + face_normals[i] = source_model.face_normals[copy_faces[i]]; + } + + std::vector<bool> vertex_included(source_model.vertices.size()/3, false); + for(int i=0; i<num_faces; i++){ + const int index = i*3; + vertex_included[faces[index+0]] = true; + vertex_included[faces[index+1]] = true; + vertex_included[faces[index+2]] = true; + } + + std::vector<int> vertex_index(source_model.vertices.size()/3); + std::vector<int> reverse_vertex_index; + { + int counter = 0; + for(unsigned i=0; i<vertex_index.size(); i++){ + if(vertex_included[i]){ + reverse_vertex_index.push_back(i); + vertex_index[i] = counter++; + } + } + } + + for(int i=0; i<num_faces; i++){ + const int index = i*3; + faces[index+0] = vertex_index[faces[index+0]]; + faces[index+1] = vertex_index[faces[index+1]]; + faces[index+2] = vertex_index[faces[index+2]]; + } + + int num_vertices = reverse_vertex_index.size(); + + vertices.resize(num_vertices*3); + normals.resize(num_vertices*3); + + if(!source_model.tangents.empty()){ + tangents.resize(num_vertices*3); + bitangents.resize(num_vertices*3); + } + + tex_coords.resize(num_vertices*2); + if(!source_model.tex_coords2.empty()){ + tex_coords2.resize(num_vertices*2); + } + + for(int i=0; i<num_vertices; i++){ + const int index = i*3; + const int copy_index = reverse_vertex_index[i]*3; + vertices[index+0] = source_model.vertices[copy_index+0]; + vertices[index+1] = source_model.vertices[copy_index+1]; + vertices[index+2] = source_model.vertices[copy_index+2]; + + normals[index+0] = source_model.normals[copy_index+0]; + normals[index+1] = source_model.normals[copy_index+1]; + normals[index+2] = source_model.normals[copy_index+2]; + + if(!source_model.tangents.empty()){ + tangents[index+0] = source_model.tangents[copy_index+0]; + tangents[index+1] = source_model.tangents[copy_index+1]; + tangents[index+2] = source_model.tangents[copy_index+2]; + + bitangents[index+0] = source_model.bitangents[copy_index+0]; + bitangents[index+1] = source_model.bitangents[copy_index+1]; + bitangents[index+2] = source_model.bitangents[copy_index+2]; + } + + const int tex_index = i*2; + const int copy_tex_index = reverse_vertex_index[i]*2; + + tex_coords[tex_index+0] = source_model.tex_coords[copy_tex_index+0]; + tex_coords[tex_index+1] = source_model.tex_coords[copy_tex_index+1]; + + if(!source_model.tex_coords2.empty()){ + tex_coords2[tex_index+0] = source_model.tex_coords2[copy_tex_index+0]; + tex_coords2[tex_index+1] = source_model.tex_coords2[copy_tex_index+1]; + } + } + + texel_density = source_model.texel_density; + vbo_enabled = true; +} + +void Model::LoadObjMorph(const std::string &rel_path, const std::string &rel_base_path){ + Dispose(); + char abs_path[kPathSize]; + ModID modsource; + if( FindFilePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths, true, NULL, &modsource) == -1 ){ + FatalError("Error", "Could not find morph file: \"%s\"", rel_path.c_str()); + } + + bool load_model = true; + + size_t read_count = 0; + unsigned short checksum = Checksum(abs_path); + FILE *cache_file = my_fopen((GetWritePath(modsource) + rel_path + ".mcache").c_str(), "rb"); + if(cache_file){ + unsigned short file_checksum = 0; + read_count = fread(&file_checksum, sizeof(unsigned short), 1, cache_file); + LOG_ASSERT(read_count == 1); + unsigned short version; + read_count = fread(&version, sizeof(unsigned short), 1, cache_file); + LOG_ASSERT(read_count == 1); + if(checksum == file_checksum && version == _morph_cache_version){ + size_t count = 0; + read_count = fread(&count, sizeof(int), 1, cache_file); + LOG_ASSERT(read_count == 1); + ResizeVertices(count); + read_count = fread(&vertices[0], sizeof(GLfloat), count*3, cache_file); + LOG_ASSERT_EQ(read_count, count*3); + read_count = fread(&tex_coords[0], sizeof(GLfloat), count*2, cache_file); + LOG_ASSERT_EQ(read_count, count*2); + load_model = false; + } + fclose(cache_file); + } + + if (load_model) { + // Load base model to get bounding box + ObjFileStats obj_stats; + GetObjFileStats(rel_base_path, obj_stats, true); + TempModel base_model; + LoadTempModel(rel_base_path, base_model, obj_stats); + GetObjFileStats(rel_path, obj_stats, true); + TempModel morph_model; + LoadTempModel(rel_path, morph_model, obj_stats); + + for(unsigned i=0; i<base_model.fc*3; ++i){ + vertices.push_back(base_model.vertices[base_model.vert_indices[i]*3+0]); + vertices.push_back(base_model.vertices[base_model.vert_indices[i]*3+1]); + vertices.push_back(base_model.vertices[base_model.vert_indices[i]*3+2]); + } + + std::vector<GLfloat> old_tex_coords; + for(unsigned i=0; i<base_model.fc*3; ++i){ + old_tex_coords.push_back(base_model.tex_coords[base_model.tex_indices[i]*2+0]); + old_tex_coords.push_back(base_model.tex_coords[base_model.tex_indices[i]*2+1]); + } + + std::vector<GLfloat> old_vertices = vertices; + + // Load morph vertices and tex coords + vertices.clear(); + tex_coords.clear(); + for(unsigned i=0; i<base_model.fc*3; ++i){ + vertices.push_back(morph_model.vertices[morph_model.vert_indices[i]*3+0]); + vertices.push_back(morph_model.vertices[morph_model.vert_indices[i]*3+1]); + vertices.push_back(morph_model.vertices[morph_model.vert_indices[i]*3+2]); + } + for(unsigned i=0; i<base_model.fc*3; ++i){ + tex_coords.push_back(morph_model.tex_coords[morph_model.tex_indices[i]*2+0]); + tex_coords.push_back(morph_model.tex_coords[morph_model.tex_indices[i]*2+1]); + } + + for(unsigned i=0, len=vertices.size(); i<len; ++i){ + vertices[i] -= old_vertices[i]; + } + for(unsigned i=0, len=tex_coords.size(); i<len; ++i){ + tex_coords[i] -= old_tex_coords[i]; + } + + FILE *cache_file = my_fopen((GetWritePath(modsource) + rel_path + ".mcache").c_str(), "wb"); + if(cache_file){ + fwrite(&checksum, sizeof(unsigned short), 1, cache_file); + fwrite(&_morph_cache_version, sizeof(unsigned short), 1, cache_file); + size_t count = vertices.size()/3; + fwrite(&count, sizeof(int), 1, cache_file); + fwrite(&vertices[0], sizeof(GLfloat), count*3, cache_file); + fwrite(&tex_coords[0], sizeof(GLfloat), count*2, cache_file); + fclose(cache_file); + } + } +} + +namespace { + struct VertInfo { + static const int NUM_VERT_INFO_ENTRIES = 8; + float entries[NUM_VERT_INFO_ENTRIES]; + int old_id; + }; + + bool operator<(const VertInfo &a, const VertInfo &b){ + static const int LEN = VertInfo::NUM_VERT_INFO_ENTRIES - 1; + for(int i=0; i<LEN; ++i){ + if(a.entries[i] != b.entries[i]){ + return (a.entries[i] < b.entries[i]); + } + } + return (a.entries[LEN] < b.entries[LEN]); + } + + bool operator==(const VertInfo &a, const VertInfo &b){ + for(int i=0; i<VertInfo::NUM_VERT_INFO_ENTRIES; ++i){ + if(a.entries[i] != b.entries[i]){ + return false; + } + } + return true; + } + + bool operator!=(const VertInfo &a, const VertInfo &b){ + return !(a==b); + } +} // namespace "" + +void Model::RemoveDuplicatedVerts() { + if(vertices.empty()){ + DisplayError("Warning", "Calling RemoveDuplicatedVerts on empty mesh."); + return; + } + // Sort vector of vertices so that identical vertices are neighbors + std::vector<VertInfo> vert_info; + for(int i=0, vert_index=0, tc_index=0, len=vertices.size(); vert_index<len; ++i, vert_index += 3, tc_index += 2){ + VertInfo vi; + vi.old_id = i; + for(int i=0; i<3; ++i){ + vi.entries[i] = vertices[vert_index+i]; + } + if(use_tangent && !normals.empty()){ // Only check normal info if the model uses tangent + for(int i=0; i<3; ++i){ + vi.entries[i+3] = normals[vert_index+i]; + } + } else { + for(int i=0; i<3; ++i){ + vi.entries[i+3] = 0.0f; + } + } + if(tex_coords.empty() == false) { + for(int i=0; i<2; ++i){ + vi.entries[i+6] = tex_coords[tc_index+i]; + } + } + vert_info.push_back(vi); + } + std::sort(vert_info.begin(), vert_info.end()); + // Collect a vector of each first unique vertex, and the mapping from each old vertex to new unique vertices + std::vector<int> unique; + std::vector<int> new_vert(vert_info.size(),-1); + for(int i=0, len=vert_info.size(); i<len; ++i){ + if(i==0 || vert_info[i] != vert_info[i-1]){ + unique.push_back(i); + } + new_vert[vert_info[i].old_id] = unique.size()-1; + } + // Invert to precollapse vert reorder + precollapse_vert_reorder.resize(unique.size()); + for(int i=0, len=unique.size(); i<len; ++i){ + precollapse_vert_reorder[i] = vert_info[unique[i]].old_id; + } + for(int i=0, len=faces.size(); i<len; ++i){ + faces[i] = new_vert[faces[i]]; + } + + RearrangeVertices(*this, precollapse_vert_reorder); + + LOGI << vert_info.size() - unique.size() << " of " << vert_info.size() << " vertices are duplicates." << std::endl; + LOGI << "New vertex size: " << vertices.size()/3 << std::endl; +} + + +void Model::RemoveDegenerateTriangles() { + unsigned count = 0; + unsigned index = 0; + unsigned copy_index = 0; + for(int i=0, len=faces.size()/3; i<len; i++){ + if(faces[index] == faces[index+1] || + faces[index+1] == faces[index+2] || + faces[index] == faces[index+2]) + { + count++; + index += 3; + continue; + } + faces[copy_index] = faces[index]; + faces[copy_index+1] = faces[index+1]; + faces[copy_index+2] = faces[index+2]; + copy_index += 3; + index += 3; + } + faces.resize(copy_index); + LOGI << "Removed " << count << " degenerate triangles." << std::endl; +} + +void Model::CopyVertCollapse( const Model &source_model ) { + RearrangeVertices(*this, source_model.precollapse_vert_reorder); + RearrangeVertices(*this, source_model.optimize_vert_reorder); +} + +struct VertData { + int cache_pos; + float score; + unsigned not_added_triangles; +}; + +struct TriData { + bool added; + float score; + unsigned verts[3]; +}; + +const unsigned _vertex_cache_size = 32; +const float _fvs_cache_decay_power = 1.5f; +const float _fvs_last_tri_score = 0.75f; +const float _fvs_valence_boost_scale = 2.0f; +const float _fvs_valence_boost_power = 0.5f; + +// Based on http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html +float FindVertexScore ( VertData *vertex_data ) +{ + if ( vertex_data->not_added_triangles == 0 ) + { + // No tri needs this vertex! + return -1.0f; + } + + float score = 0.0f; + int cache_pos = vertex_data->cache_pos; + if ( cache_pos < 0 ) + { + // Vertex is not in FIFO cache - no score. + } + else + { + if ( cache_pos < 3 ) + { + // This vertex was used in the last triangle, + // so it has a fixed score, whichever of the three + // it's in. Otherwise, you can get very different + // answers depending on whether you add + // the triangle 1,2,3 or 3,1,2 - which is silly. + score = _fvs_last_tri_score; + } + else + { + assert ( cache_pos < (int)_vertex_cache_size ); + // Points for being high in the cache. + const float scaler = 1.0f / ( _vertex_cache_size - 3 ); + score = 1.0f - ( cache_pos - 3 ) * scaler; + score = pow ( score, _fvs_cache_decay_power ); + } + } + + // Bonus points for having a low number of tris still to + // use the vert, so we get rid of lone verts quickly. + float ValenceBoost = pow ( (float)(vertex_data->not_added_triangles), + -_fvs_valence_boost_power ); + score += _fvs_valence_boost_scale * ValenceBoost; + + return score; +} + +void Model::PrintACMR(){ + unsigned cache_hits = 0; + unsigned total = 0; + std::vector<int> fifo(32, -1); + unsigned index = 0; + for(unsigned i=0; i<faces.size(); ++i){ + for(unsigned j=0; j<fifo.size(); ++j){ + if(fifo[j] == (int)faces[i]){ + cache_hits++; + break; + } + } + fifo[index] = faces[i]; + index = (index+1)%fifo.size(); + total++; + } + LOGI << "Opt: " << cache_hits << " cache hits out of " << total << "." << std::endl; + LOGI << "Opt: ACMR: " << ((float)(total-cache_hits))/(float)total*3.0f << std::endl; +} + +void Model::OptimizeTriangleOrder() { + std::vector<TriData> tris(faces.size()/3); + std::vector<VertData> verts(vertices.size()/3); + + unsigned index = 0; + for(int i=0, len=faces.size()/3; i<len; i++){ + tris[i].verts[0] = faces[index++]; + tris[i].verts[1] = faces[index++]; + tris[i].verts[2] = faces[index++]; + for(unsigned j=0; j<3; ++j){ + verts[tris[i].verts[j]].not_added_triangles++; + } + } + + for(int i=0, len=vertices.size()/3; i<len; ++i){ + verts[i].cache_pos = -1; + verts[i].score = FindVertexScore(&verts[i]); + } + + int best_triangle = 0; + float best_score = 0.0f; + for(int i=0, len=faces.size()/3; i<len; i++){ + tris[i].score = verts[tris[i].verts[0]].score + + verts[tris[i].verts[1]].score + + verts[tris[i].verts[2]].score; + if(tris[i].score > best_score){ + best_score = tris[i].score; + best_triangle = i; + } + } + + int num_faces = faces.size()/3;; + std::vector<int> lru(_vertex_cache_size,-1); + std::vector<unsigned> draw_list(num_faces); + int draw_list_index = 0; + while(draw_list_index < num_faces){ + draw_list[draw_list_index++] = best_triangle; + if(draw_list_index == num_faces){ + break; + } + tris[best_triangle].added = true; + // Update not_added_triangles + for(unsigned i=0; i<3; ++i){ + unsigned vert_id = tris[best_triangle].verts[i]; + --verts[vert_id].not_added_triangles; + } + // Update LRU cache + for(unsigned i=0; i<3; ++i){ + unsigned vert_id = tris[best_triangle].verts[i]; + int cp = verts[vert_id].cache_pos; + // If vert is in cache, remove it and slide other verts down + if(cp != -1){ + for(unsigned j=cp; j<_vertex_cache_size; ++j){ + if(j == _vertex_cache_size-1){ + lru[j] = -1; + } else { + lru[j] = lru[j+1]; + if(lru[j] != -1){ + verts[lru[j]].cache_pos--; + } + } + } + } + // Slide other verts up and add vert to cache + for(int j=_vertex_cache_size-1; j>=0; --j){ + if(lru[j] != -1){ + verts[lru[j]].cache_pos++; + if(j >= (int)_vertex_cache_size-1){ + verts[lru[j]].cache_pos = -1; //Out of cache entirely + } + verts[lru[j]].score = FindVertexScore(&verts[lru[j]]); + } + if(j != 0){ + lru[j] = lru[j-1]; + } + } + lru[0] = vert_id; + verts[vert_id].cache_pos = 0; + verts[vert_id].score = FindVertexScore(&verts[vert_id]); + } + // This can be optimized a lot, Forsyth's method just checks triangles + // thave have a vertex in the LRU cache + best_score = 0.0f; + best_triangle = -1; + for(int i=0; i<num_faces; ++i){ + if(tris[i].added){ + continue; + } + tris[i].score = verts[tris[i].verts[0]].score + + verts[tris[i].verts[1]].score + + verts[tris[i].verts[2]].score; + if(best_triangle == -1 || tris[i].score > best_score){ + best_score = tris[i].score; + best_triangle = i; + } + } + } + + { + unsigned index = 0; + for(unsigned i=0; i<draw_list.size(); ++i){ + faces[index++] = tris[draw_list[i]].verts[0]; + faces[index++] = tris[draw_list[i]].verts[1]; + faces[index++] = tris[draw_list[i]].verts[2]; + } + } +} + + +void Model::OptimizeVertexOrder() { + std::vector<int> order(vertices.size()/3, -1); + unsigned index = 0; + for(unsigned i=0; i<faces.size(); ++i){ + if(order[faces[i]] == -1){ + order[faces[i]] = index++; + } + faces[i] = order[faces[i]]; + } + optimize_vert_reorder.clear(); + optimize_vert_reorder.resize(index, -1); + for(int i=0, len=order.size(); i<len; i++) + { + if( order[i] >= 0 && order[i] < (int)optimize_vert_reorder.size() ) + { + optimize_vert_reorder[order[i]] = i; + } + else + { + LOGW << "Oddity when running " << __FUNCTION__ << ", order[i] is out of bounds, value is " << order[i] << " on index " << i << std::endl; + } + } + RearrangeVertices(*this, optimize_vert_reorder); +} + +struct edge { + int vert_id[2]; +}; + +class TriScoreSorter { +public: + bool operator()(const TriData &a, const TriData &b) { + return a.score > b.score; + } +}; + +void Model::SortTrianglesBackToFront( const vec3 &camera ) +{ + std::vector<TriData> tris(faces.size()/3); + + unsigned index = 0; + for(int i=0, len=faces.size()/3; i<len; ++i){ + tris[i].added = false; + tris[i].score = 0.0f; + tris[i].verts[0] = faces[index++]; + tris[i].verts[1] = faces[index++]; + tris[i].verts[2] = faces[index++]; + } + + vec3 center; + for(int i=0, len=faces.size()/3; i<len; ++i){ + center[0] = (vertices[tris[i].verts[0]*3+0] + + vertices[tris[i].verts[1]*3+0] + + vertices[tris[i].verts[2]*3+0])/3.0f; + center[1] = (vertices[tris[i].verts[0]*3+1] + + vertices[tris[i].verts[1]*3+1] + + vertices[tris[i].verts[2]*3+1])/3.0f; + center[2] = (vertices[tris[i].verts[0]*3+2] + + vertices[tris[i].verts[1]*3+2] + + vertices[tris[i].verts[2]*3+2])/3.0f; + tris[i].score = distance_squared(center, camera); + } + + std::sort(tris.begin(), tris.end(), TriScoreSorter()); + + index = 0; + for(unsigned i=0; i<tris.size(); ++i){ + faces[index++] = tris[i].verts[0]; + faces[index++] = tris[i].verts[1]; + faces[index++] = tris[i].verts[2]; + } +} |