Files
noggit-red/src/noggit/Model.cpp
2020-10-09 22:27:50 +03:00

1648 lines
46 KiB
C++

// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <math/bounding_box.hpp>
#include <noggit/AsyncLoader.h>
#include <noggit/Log.h>
#include <noggit/Model.h>
#include <noggit/ModelInstance.h>
#include <noggit/TextureManager.h> // TextureManager, Texture
#include <noggit/World.h>
#include <opengl/scoped.hpp>
#include <opengl/shader.hpp>
#include <algorithm>
#include <cassert>
#include <map>
#include <sstream>
#include <string>
Model::Model(const std::string& filename)
: AsyncObject(filename)
, _finished_upload(false)
{
memset(&header, 0, sizeof(ModelHeader));
}
void Model::finishLoading()
{
MPQFile f(filename);
if (f.isEof())
{
LogError << "Error loading file \"" << filename << "\". Aborting to load model." << std::endl;
finished = true;
return;
}
memcpy(&header, f.getBuffer(), sizeof(ModelHeader));
// blend mode override
if (header.Flags & 8)
{
// go to the end of the header (where the blend override data is)
uint32_t const* blend_override_info = reinterpret_cast<uint32_t const*>(f.getBuffer() + sizeof(ModelHeader));
uint32_t n_blend_override = *blend_override_info++;
uint32_t ofs_blend_override = *blend_override_info;
blend_override = M2Array<uint16_t>(f, ofs_blend_override, n_blend_override);
}
_vertex_box_points = math::box_points ( misc::transform_model_box_coords(header.bounding_box_min)
, misc::transform_model_box_coords(header.bounding_box_max)
);
animated = isAnimated(f); // isAnimated will set animGeometry and animTextures
trans = 1.0f;
_current_anim_seq = 0;
rad = header.bounding_box_radius;
if (header.nGlobalSequences)
{
_global_sequences = M2Array<int>(f, header.ofsGlobalSequences, header.nGlobalSequences);
}
//! \todo This takes a biiiiiit long. Have a look at this.
initCommon(f);
if (animated)
{
initAnimated(f);
}
f.close();
finished = true;
_state_changed.notify_all();
}
bool Model::isAnimated(const MPQFile& f)
{
// see if we have any animated bones
ModelBoneDef const* bo = reinterpret_cast<ModelBoneDef const*>(f.getBuffer() + header.ofsBones);
animGeometry = false;
animBones = false;
_per_instance_animation = false;
ModelVertex const* verts = reinterpret_cast<ModelVertex const*>(f.getBuffer() + header.ofsVertices);
for (size_t i = 0; i<header.nVertices && !animGeometry; ++i)
{
for (size_t b = 0; b<4; b++)
{
if (verts[i].weights[b]>0)
{
ModelBoneDef const& bb = bo[verts[i].bones[b]];
bool billboard = (bb.flags & (0x78)); // billboard | billboard_lock_[xyz]
if ((bb.flags & 0x200) || billboard)
{
if (billboard)
{
// if we have billboarding, the model will need per-instance animation
_per_instance_animation = true;
}
animGeometry = true;
break;
}
}
}
}
if (animGeometry || header.nParticleEmitters || header.nRibbonEmitters || header.nLights)
{
animBones = true;
}
else
{
for (size_t i = 0; i<header.nBones; ++i)
{
ModelBoneDef const& bb = bo[i];
if (bb.translation.type || bb.rotation.type || bb.scaling.type)
{
animBones = true;
break;
}
}
}
animTextures = header.nTexAnims > 0;
// animated colors
if (header.nColors)
{
ModelColorDef const* cols = reinterpret_cast<ModelColorDef const*>(f.getBuffer() + header.ofsColors);
for (size_t i = 0; i<header.nColors; ++i)
{
if (cols[i].color.type != 0 || cols[i].opacity.type != 0)
{
return true;
}
}
}
// animated opacity
if (header.nTransparency)
{
ModelTransDef const* trs = reinterpret_cast<ModelTransDef const*>(f.getBuffer() + header.ofsTransparency);
for (size_t i = 0; i<header.nTransparency; ++i)
{
if (trs[i].trans.type != 0)
{
return true;
}
}
}
// guess not...
return animGeometry || animTextures || animBones;
}
math::vector_3d fixCoordSystem(math::vector_3d v)
{
return math::vector_3d(v.x, v.z, -v.y);
}
namespace
{
math::vector_3d fixCoordSystem2(math::vector_3d v)
{
return math::vector_3d(v.x, v.z, v.y);
}
math::quaternion fixCoordSystemQuat(math::quaternion v)
{
return math::quaternion(-v.x, -v.z, v.y, v.w);
}
}
void Model::initCommon(const MPQFile& f)
{
// vertices, normals, texcoords
_vertices = M2Array<ModelVertex>(f, header.ofsVertices, header.nVertices);
for (auto& v : _vertices)
{
v.position = fixCoordSystem(v.position);
v.normal = fixCoordSystem(v.normal);
}
if (!animGeometry)
{
_current_vertices.swap(_vertices);
}
// textures
ModelTextureDef const* texdef = reinterpret_cast<ModelTextureDef const*>(f.getBuffer() + header.ofsTextures);
_textureFilenames.resize(header.nTextures);
_specialTextures.resize(header.nTextures);
for (size_t i = 0; i < header.nTextures; ++i)
{
if (texdef[i].type == 0)
{
if (texdef[i].nameLen == 0)
{
LogDebug << "Texture " << i << " has a lenght of 0 for '" << filename << std::endl;
continue;
}
_specialTextures[i] = -1;
const char* blp_ptr = f.getBuffer() + texdef[i].nameOfs;
// some tools export the size without accounting for the \0
bool invalid_size = *(blp_ptr + texdef[i].nameLen-1) != '\0';
_textureFilenames[i] = std::string(blp_ptr, texdef[i].nameLen - (invalid_size ? 0 : 1));
}
else
{
#ifndef NO_REPLACIBLE_TEXTURES_HACK
_specialTextures[i] = -1;
_textureFilenames[i] = "tileset/generic/black.blp";
#else
//! \note special texture - only on characters and such... Noggit should not even render these.
//! \todo Check if this is actually correct. Or just remove it.
_specialTextures[i] = texdef[i].type;
if (texdef[i].type == 3)
{
_textureFilenames[i] = "Item\\ObjectComponents\\Weapon\\ArmorReflect4.BLP";
// a fix for weapons with type-3 textures.
_replaceTextures.emplace (texdef[i].type, _textureFilenames[i]);
}
#endif
}
}
// init colors
if (header.nColors) {
ModelColorDef const* colorDefs = reinterpret_cast<ModelColorDef const*>(f.getBuffer() + header.ofsColors);
for (size_t i = 0; i < header.nColors; ++i)
{
_colors.emplace_back (f, colorDefs[i], _global_sequences.data());
}
}
// init transparency
_transparency_lookup = M2Array<int16_t>(f, header.ofsTransparencyLookup, header.nTransparencyLookup);
if (header.nTransparency) {
ModelTransDef const* trDefs = reinterpret_cast<ModelTransDef const*>(f.getBuffer() + header.ofsTransparency);
for (size_t i = 0; i < header.nTransparency; ++i)
{
_transparency.emplace_back (f, trDefs[i], _global_sequences.data());
}
}
// just use the first LOD/view
if (header.nViews > 0) {
// indices - allocate space, too
std::string lodname = filename.substr(0, filename.length() - 3);
lodname.append("00.skin");
MPQFile g(lodname.c_str());
if (g.isEof()) {
LogError << "loading skinfile " << lodname << std::endl;
g.close();
return;
}
ModelView const* view = reinterpret_cast<ModelView const*>(g.getBuffer());
uint16_t const* indexLookup = reinterpret_cast<uint16_t const*>(g.getBuffer() + view->ofs_index);
uint16_t const* triangles = reinterpret_cast<uint16_t const*>(g.getBuffer() + view->ofs_triangle);
_indices.resize (view->n_triangle);
for (size_t i (0); i < _indices.size(); ++i) {
_indices[i] = indexLookup[triangles[i]];
}
// render ops
ModelGeoset const* model_geosets = reinterpret_cast<ModelGeoset const*>(g.getBuffer() + view->ofs_submesh);
ModelTexUnit const* texture_unit = reinterpret_cast<ModelTexUnit const*>(g.getBuffer() + view->ofs_texture_unit);
_texture_lookup = M2Array<uint16_t>(f, header.ofsTexLookup, header.nTexLookup);
_texture_animation_lookups = M2Array<int16_t>(f, header.ofsTexAnimLookup, header.nTexAnimLookup);
_texture_unit_lookup = M2Array<int16_t>(f, header.ofsTexUnitLookup, header.nTexUnitLookup);
showGeosets.resize (view->n_submesh);
for (size_t i = 0; i<view->n_submesh; ++i)
{
showGeosets[i] = true;
}
_render_flags = M2Array<ModelRenderFlags>(f, header.ofsRenderFlags, header.nRenderFlags);
for (size_t j = 0; j<view->n_texture_unit; j++)
{
size_t geoset = texture_unit[j].submesh;
ModelRenderPass pass(texture_unit[j], this);
pass.ordering_thingy = model_geosets[geoset].BoundingBox[0].x;
pass.index_start = model_geosets[geoset].istart;
pass.index_count = model_geosets[geoset].icount;
pass.vertex_start = model_geosets[geoset].vstart;
pass.vertex_end = pass.vertex_start + model_geosets[geoset].vcount;
_render_passes.push_back(pass);
}
g.close();
fix_shader_id_blend_override();
fix_shader_id_layer();
compute_pixel_shader_ids();
for (auto& pass : _render_passes)
{
pass.init_uv_types(this);
}
// transparent parts come later
std::sort(_render_passes.begin(), _render_passes.end());
// add fake geometry for selection
if (_render_passes.empty())
{
_fake_geometry.emplace(this);
}
}
}
void Model::fix_shader_id_blend_override()
{
for (auto& pass : _render_passes)
{
if (pass.shader_id & 0x8000)
{
continue;
}
int shader = 0;
bool blend_mode_override = (header.Flags & 8);
// fuckporting check
if (pass.texture_coord_combo_index + pass.texture_count - 1 >= _texture_unit_lookup.size())
{
LogDebug << "wrong texture coord combo index on fuckported model: " << filename << std::endl;
// use default stuff
pass.shader_id = 0;
pass.texture_count = 1;
continue;
}
if (!blend_mode_override)
{
uint16_t texture_unit_lookup = _texture_unit_lookup[pass.texture_coord_combo_index];
if (_render_flags[pass.renderflag_index].blend)
{
shader = 1;
if (texture_unit_lookup == 0xFFFF)
{
shader |= 0x8;
}
}
shader <<= 4;
if (texture_unit_lookup == 1)
{
shader |= 0x4000;
}
}
else
{
uint16_t runtime_shader_val[2] = { 0, 0 };
for (int i = 0; i < pass.texture_count; ++i)
{
uint16_t override_blend = blend_override[pass.shader_id + i];
uint16_t texture_unit_lookup = _texture_unit_lookup[pass.texture_coord_combo_index + i];
if (i == 0 && _render_flags[pass.renderflag_index].blend == 0)
{
override_blend = 0;
}
runtime_shader_val[i] = override_blend;
if (texture_unit_lookup == 0xFFFF)
{
runtime_shader_val[i] |= 0x8;
}
if (texture_unit_lookup == 1 && i + 1 == pass.texture_count)
{
shader |= 0x4000;
}
}
shader |= (runtime_shader_val[1] & 0xFFFF) | ((runtime_shader_val[0] << 4) & 0xFFFF);
}
pass.shader_id = shader;
}
}
void Model::fix_shader_id_layer()
{
int non_layered_count = 0;
for (auto const& pass : _render_passes)
{
if (pass.material_layer <= 0)
{
non_layered_count++;
}
}
if (non_layered_count < _render_passes.size())
{
std::vector<ModelRenderPass> passes;
ModelRenderPass* first_pass = nullptr;
bool need_reducing = false;
uint16_t previous_render_flag = -1, some_flags = 0;
for (auto& pass : _render_passes)
{
if (pass.renderflag_index == previous_render_flag)
{
need_reducing = true;
continue;
}
previous_render_flag = pass.renderflag_index;
uint8_t lower_bits = pass.shader_id & 0x7;
if (pass.material_layer == 0)
{
if (pass.texture_count >= 1 && _render_flags[pass.renderflag_index].blend == 0)
{
pass.shader_id &= 0xFF8F;
}
first_pass = &pass;
}
bool xor_unlit = ((_render_flags[pass.renderflag_index].flags.unlit ^ _render_flags[first_pass->renderflag_index].flags.unlit) & 1) == 0;
if ((some_flags & 0xFF) == 1)
{
if ((_render_flags[pass.renderflag_index].blend == 1 || _render_flags[pass.renderflag_index].blend == 2)
&& pass.texture_count == 1
&& xor_unlit
&& pass.texture_combo_index == first_pass->texture_combo_index
)
{
if (_transparency_lookup[pass.transparency_combo_index] == _transparency_lookup[first_pass->transparency_combo_index])
{
pass.shader_id = 0x8000;
first_pass->shader_id = 0x8001;
some_flags = (some_flags & 0xFF00) | 3;
// current pass removed (not needed)
continue;
}
}
some_flags = (some_flags & 0xFF00);
}
int16_t texture_unit_lookup = _texture_unit_lookup[pass.texture_coord_combo_index];
if ((some_flags & 0xFF) < 2)
{
if ((_render_flags[pass.renderflag_index].blend == 0) && (pass.texture_count == 2) && ((lower_bits == 4) || (lower_bits == 6)))
{
if (texture_unit_lookup == 0 && (_texture_unit_lookup[pass.texture_coord_combo_index + 1] == -1))
{
some_flags = (some_flags & 0xFF00) | 1;
}
}
}
if ((some_flags >> 8) != 0)
{
if ((some_flags >> 8) == 1)
{
if (((_render_flags[pass.renderflag_index].blend != 4) && (_render_flags[pass.renderflag_index].blend != 6)) || (pass.texture_count != 1) || (texture_unit_lookup >= 0))
{
some_flags &= 0xFF00;
}
else if (_transparency_lookup[pass.transparency_combo_index] == _transparency_lookup[first_pass->transparency_combo_index])
{
pass.shader_id = 0x8000;
first_pass->shader_id = _render_flags[pass.renderflag_index].blend != 4 ? 0xE : 0x8002;
some_flags = (some_flags & 0xFF) | (2 << 8);
first_pass->texture_count = 2;
first_pass->textures[1] = pass.texture_combo_index;
first_pass->uv_animations[1] = pass.animation_combo_index;
// current pass removed (merged with the previous one)
continue;
}
}
else
{
if ((some_flags >> 8) != 2)
{
continue;
}
if ( ((_render_flags[pass.renderflag_index].blend != 2) && (_render_flags[pass.renderflag_index].blend != 1))
|| (pass.texture_count != 1)
|| xor_unlit
|| ((pass.texture_combo_index & 0xff) != (first_pass->texture_combo_index & 0xff))
)
{
some_flags &= 0xFF00;
}
else if (_transparency_lookup[pass.transparency_combo_index] == _transparency_lookup[first_pass->transparency_combo_index])
{
// current pass ignored/removed
pass.shader_id = 0x8000;
first_pass->shader_id = ((first_pass->shader_id == 0x8002 ? 2 : 0) - 0x7FFF) & 0xFFFF;
some_flags = (some_flags & 0xFF) | (3 << 8);
continue;
}
}
some_flags = (some_flags & 0xFF);
}
if ((_render_flags[pass.renderflag_index].blend == 0) && (pass.texture_count == 1) && (texture_unit_lookup == 0))
{
some_flags = (some_flags & 0xFF) | (1 << 8);
}
// setup texture and anim lookup indices
pass.textures[0] = pass.texture_combo_index;
pass.textures[1] = pass.texture_count > 1 ? pass.texture_combo_index + 1 : 0;
pass.uv_animations[0] = pass.animation_combo_index;
pass.uv_animations[1] = pass.texture_count > 1 ? pass.animation_combo_index + 1 : 0;
passes.push_back(pass);
}
if (need_reducing)
{
previous_render_flag = -1;
for (int i = 0; i < passes.size(); ++i)
{
auto& pass = _render_passes[i];
uint16_t renderflag_index = pass.renderflag_index;
if (renderflag_index == previous_render_flag)
{
pass.shader_id = _render_passes[i - 1].shader_id;
pass.texture_count = _render_passes[i - 1].texture_count;
pass.texture_combo_index = _render_passes[i - 1].texture_combo_index;
pass.texture_coord_combo_index = _render_passes[i - 1].texture_coord_combo_index;
}
else
{
previous_render_flag = renderflag_index;
}
}
}
_render_passes = passes;
}
// no layering, just setting some infos
else
{
for (auto& pass : _render_passes)
{
pass.textures[0] = pass.texture_combo_index;
pass.textures[1] = pass.texture_count > 1 ? pass.texture_combo_index + 1 : 0;
pass.uv_animations[0] = pass.animation_combo_index;
pass.uv_animations[1] = pass.texture_count > 1 ? pass.animation_combo_index + 1 : 0;
}
}
}
ModelRenderPass::ModelRenderPass(ModelTexUnit const& tex_unit, Model* m)
: ModelTexUnit(tex_unit)
, blend_mode(m->_render_flags[renderflag_index].blend)
{
}
bool ModelRenderPass::prepare_draw(opengl::scoped::use_program& m2_shader, Model *m)
{
if (!m->showGeosets[submesh] || !pixel_shader)
{
return false;
}
// COLOUR
// Get the colour and transparency and check that we should even render
math::vector_4d mesh_color = math::vector_4d(1.0f, 1.0f, 1.0f, m->trans); // ??
math::vector_4d emissive_color = math::vector_4d(0.0f, 0.0f, 0.0f, 0.0f);
auto const& renderflag(m->_render_flags[renderflag_index]);
// emissive colors
if (color_index != -1 && m->_colors[color_index].color.uses(0))
{
::math::vector_3d c (m->_colors[color_index].color.getValue (0, m->_anim_time, m->_global_animtime));
if (m->_colors[color_index].opacity.uses (m->_current_anim_seq))
{
mesh_color.w = m->_colors[color_index].opacity.getValue (m->_current_anim_seq, m->_anim_time, m->_global_animtime);
}
if (renderflag.flags.unlit)
{
mesh_color.x = c.x; mesh_color.y = c.y; mesh_color.z = c.z;
}
else
{
mesh_color.x = mesh_color.y = mesh_color.z = 0;
}
emissive_color = math::vector_4d(c, mesh_color.w);
}
// opacity
if (transparency_combo_index != 0xFFFF)
{
auto& transparency (m->_transparency[m->_transparency_lookup[transparency_combo_index]].trans);
if (transparency.uses (0))
{
mesh_color.w = mesh_color.w * transparency.getValue(0, m->_anim_time, m->_global_animtime);
}
}
// exit and return false before affecting the opengl render state
if (!((mesh_color.w > 0) && (color_index == -1 || emissive_color.w > 0)))
{
return false;
}
switch (static_cast<M2Blend>(renderflag.blend))
{
default:
case M2Blend::Opaque:
gl.disable(GL_BLEND);
m2_shader.uniform("alpha_test", -1.f);
m2_shader.uniform("fog_mode", 1);
break;
case M2Blend::Alpha_Key:
gl.disable(GL_BLEND);
m2_shader.uniform("alpha_test", (224.f / 255.f) * mesh_color.w);
m2_shader.uniform("fog_mode", 1);
break;
case M2Blend::Alpha:
gl.enable(GL_BLEND);
gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
m2_shader.uniform("alpha_test", (1.f / 255.f) * mesh_color.w);
m2_shader.uniform("fog_mode", 1);
break;
case M2Blend::No_Add_Alpha:
gl.enable(GL_BLEND);
gl.blendFunc(GL_ONE, GL_ONE);
m2_shader.uniform("alpha_test", (1.f / 255.f) * mesh_color.w);
m2_shader.uniform("fog_mode", 2); // Warning: wiki is unsure on that
break;
case M2Blend::Add:
gl.enable(GL_BLEND);
gl.blendFunc(GL_SRC_ALPHA, GL_ONE);
m2_shader.uniform("alpha_test", (1.f / 255.f) * mesh_color.w);
m2_shader.uniform("fog_mode", 2);
break;
case M2Blend::Mod:
gl.enable(GL_BLEND);
gl.blendFunc(GL_DST_COLOR, GL_ZERO);
m2_shader.uniform("alpha_test", (1.f / 255.f) * mesh_color.w);
m2_shader.uniform("fog_mode", 3);
break;
case M2Blend::Mod2x:
gl.enable(GL_BLEND);
gl.blendFunc(GL_DST_COLOR, GL_SRC_COLOR);
m2_shader.uniform("alpha_test", (1.f / 255.f) * mesh_color.w);
m2_shader.uniform("fog_mode", 4);
break;
}
if (renderflag.flags.two_sided)
{
gl.disable(GL_CULL_FACE);
}
else
{
gl.enable(GL_CULL_FACE);
}
if (renderflag.flags.z_buffered)
{
gl.depthMask(GL_FALSE);
}
else
{
gl.depthMask(GL_TRUE);
}
m2_shader.uniform("unfogged", (int)renderflag.flags.unfogged);
m2_shader.uniform("unlit", (int)renderflag.flags.unlit);
if (texture_count > 1)
{
bind_texture(1, m);
}
bind_texture(0, m);
GLint tu1 = static_cast<GLint>(tu_lookups[0]), tu2 = static_cast<GLint>(tu_lookups[1]);
m2_shader.uniform("tex_unit_lookup_1", tu1);
m2_shader.uniform("tex_unit_lookup_2", tu2);
int16_t tex_anim_lookup = m->_texture_animation_lookups[uv_animations[0]];
math::matrix_4x4 unit(math::matrix_4x4::unit);
if (tex_anim_lookup != -1)
{
m2_shader.uniform("tex_matrix_1", m->_texture_animations[tex_anim_lookup].mat);
if (texture_count > 1)
{
tex_anim_lookup = m->_texture_animation_lookups[uv_animations[1]];
if (tex_anim_lookup != -1)
{
m2_shader.uniform("tex_matrix_2", m->_texture_animations[tex_anim_lookup].mat);
}
else
{
m2_shader.uniform("tex_matrix_2", unit);
}
}
}
else
{
m2_shader.uniform("tex_matrix_1", unit);
m2_shader.uniform("tex_matrix_2", unit);
}
m2_shader.uniform("pixel_shader", static_cast<GLint>(pixel_shader.get()));
m2_shader.uniform("mesh_color", mesh_color);
return true;
}
void ModelRenderPass::after_draw()
{
gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void ModelRenderPass::bind_texture(size_t index, Model* m)
{
opengl::texture::set_active_texture(index);
uint16_t tex = m->_texture_lookup[textures[index]];
if (m->_specialTextures[tex] == -1)
{
m->_textures[tex]->bind();
}
else
{
m->_replaceTextures.at (m->_specialTextures[tex])->bind();
}
}
void ModelRenderPass::init_uv_types(Model* m)
{
tu_lookups[0] = texture_unit_lookup::none;
tu_lookups[1] = texture_unit_lookup::none;
if (m->_texture_unit_lookup.size() < texture_coord_combo_index + texture_count)
{
throw std::out_of_range("model: texture_coord_combo_index out of range " + m->filename);
}
for (int i = 0; i < texture_count; ++i)
{
switch (m->_texture_unit_lookup[texture_coord_combo_index + i])
{
case (int16_t)(-1): tu_lookups[i] = texture_unit_lookup::environment; break;
case 0: tu_lookups[i] = texture_unit_lookup::t1; break;
case 1: tu_lookups[i] = texture_unit_lookup::t2; break;
}
}
}
FakeGeometry::FakeGeometry(Model* m)
{
math::vector_3d min = m->header.bounding_box_min, max = m->header.bounding_box_max;
vertices.emplace_back(min.x, max.y, min.z);
vertices.emplace_back(min.x, max.y, max.z);
vertices.emplace_back(max.x, max.y, max.z);
vertices.emplace_back(max.x, max.y, min.z);
vertices.emplace_back(min.x, min.y, min.z);
vertices.emplace_back(min.x, min.y, max.z);
vertices.emplace_back(max.x, min.y, max.z);
vertices.emplace_back(max.x, min.y, min.z);
indices =
{
0,1,2, 2,3,0,
0,4,5, 5,1,0,
0,3,7, 7,4,0,
1,5,6, 6,2,1,
2,6,7, 7,3,2,
5,6,7, 7,4,5
};
}
namespace
{
// https://wowdev.wiki/M2/.skin/WotLK_shader_selection
boost::optional<ModelPixelShader> GetPixelShader(uint16_t texture_count, uint16_t shader_id)
{
uint16_t texture1_fragment_mode = (shader_id >> 4) & 7;
uint16_t texture2_fragment_mode = shader_id & 7;
// uint16_t texture1_env_map = (shader_id >> 4) & 8;
// uint16_t texture2_env_map = shader_id & 8;
boost::optional<ModelPixelShader> pixel_shader;
if (texture_count == 1)
{
switch (texture1_fragment_mode)
{
case 0:
pixel_shader = ModelPixelShader::Combiners_Opaque;
break;
case 2:
pixel_shader = ModelPixelShader::Combiners_Decal;
break;
case 3:
pixel_shader = ModelPixelShader::Combiners_Add;
break;
case 4:
pixel_shader = ModelPixelShader::Combiners_Mod2x;
break;
case 5:
pixel_shader = ModelPixelShader::Combiners_Fade;
break;
default:
pixel_shader = ModelPixelShader::Combiners_Mod;
break;
}
}
else
{
if (!texture1_fragment_mode)
{
switch (texture2_fragment_mode)
{
case 0:
pixel_shader = ModelPixelShader::Combiners_Opaque_Opaque;
break;
case 3:
pixel_shader = ModelPixelShader::Combiners_Opaque_Add;
break;
case 4:
pixel_shader = ModelPixelShader::Combiners_Opaque_Mod2x;
break;
case 6:
pixel_shader = ModelPixelShader::Combiners_Opaque_Mod2xNA;
break;
case 7:
pixel_shader = ModelPixelShader::Combiners_Opaque_AddNA;
break;
default:
pixel_shader = ModelPixelShader::Combiners_Opaque_Mod;
break;
}
}
else if (texture1_fragment_mode == 1)
{
switch (texture2_fragment_mode)
{
case 0:
pixel_shader = ModelPixelShader::Combiners_Mod_Opaque;
break;
case 3:
pixel_shader = ModelPixelShader::Combiners_Mod_Add;
break;
case 4:
pixel_shader = ModelPixelShader::Combiners_Mod_Mod2x;
break;
case 6:
pixel_shader = ModelPixelShader::Combiners_Mod_Mod2xNA;
break;
case 7:
pixel_shader = ModelPixelShader::Combiners_Mod_AddNA;
break;
default:
pixel_shader = ModelPixelShader::Combiners_Mod_Mod;
break;
}
}
else if (texture1_fragment_mode == 3)
{
if (texture2_fragment_mode == 1)
{
pixel_shader = ModelPixelShader::Combiners_Add_Mod;
}
}
else if (texture1_fragment_mode == 4 && texture2_fragment_mode == 4)
{
pixel_shader = ModelPixelShader::Combiners_Mod2x_Mod2x;
}
else if (texture2_fragment_mode == 1)
{
pixel_shader = ModelPixelShader::Combiners_Mod_Mod2x;
}
}
return pixel_shader;
}
boost::optional<ModelPixelShader> M2GetPixelShaderID (uint16_t texture_count, uint16_t shader_id)
{
boost::optional<ModelPixelShader> pixel_shader;
if (!(shader_id & 0x8000))
{
pixel_shader = GetPixelShader(texture_count, shader_id);
if (!pixel_shader)
{
pixel_shader = GetPixelShader(texture_count, 0x11);
}
}
else
{
switch (shader_id & 0x7FFF)
{
case 1:
pixel_shader = ModelPixelShader::Combiners_Opaque_Mod2xNA_Alpha;
break;
case 2:
pixel_shader = ModelPixelShader::Combiners_Opaque_AddAlpha;
break;
case 3:
pixel_shader = ModelPixelShader::Combiners_Opaque_AddAlpha_Alpha;
break;
}
}
return pixel_shader;
}
}
void Model::compute_pixel_shader_ids()
{
for (auto& pass : _render_passes)
{
pass.pixel_shader = M2GetPixelShaderID(pass.texture_count, pass.shader_id);
}
}
void Model::initAnimated(const MPQFile& f)
{
std::vector<std::unique_ptr<MPQFile>> animation_files;
if (header.nAnimations > 0)
{
std::vector<ModelAnimation> animations(header.nAnimations);
memcpy(animations.data(), f.getBuffer() + header.ofsAnimations, header.nAnimations * sizeof(ModelAnimation));
for (auto& anim : animations)
{
anim.length = std::max(anim.length, 1U);
_animation_length[anim.animID] += anim.length;
_animations_seq_per_id[anim.animID][anim.subAnimID] = anim;
std::string lodname = filename.substr(0, filename.length() - 3);
std::stringstream tempname;
tempname << lodname << anim.animID << "-" << anim.subAnimID << ".anim";
if (MPQFile::exists(tempname.str()))
{
animation_files.push_back(std::make_unique<MPQFile>(tempname.str()));
}
}
}
if (animBones)
{
ModelBoneDef const* mb = reinterpret_cast<ModelBoneDef const*>(f.getBuffer() + header.ofsBones);
for (size_t i = 0; i<header.nBones; ++i)
{
bones.emplace_back(f, mb[i], _global_sequences.data(), animation_files);
}
}
if (animTextures)
{
ModelTexAnimDef const* ta = reinterpret_cast<ModelTexAnimDef const*>(f.getBuffer() + header.ofsTexAnims);
for (size_t i=0; i<header.nTexAnims; ++i) {
_texture_animations.emplace_back (f, ta[i], _global_sequences.data());
}
}
// particle systems
if (header.nParticleEmitters) {
ModelParticleEmitterDef const* pdefs = reinterpret_cast<ModelParticleEmitterDef const*>(f.getBuffer() + header.ofsParticleEmitters);
for (size_t i = 0; i<header.nParticleEmitters; ++i)
{
try
{
_particles.emplace_back (this, f, pdefs[i], _global_sequences.data());
}
catch (std::logic_error error)
{
LogError << "Loading particles for '" << filename << "' " << error.what() << std::endl;
}
}
}
// ribbons
if (header.nRibbonEmitters) {
ModelRibbonEmitterDef const* rdefs = reinterpret_cast<ModelRibbonEmitterDef const*>(f.getBuffer() + header.ofsRibbonEmitters);
for (size_t i = 0; i<header.nRibbonEmitters; ++i) {
_ribbons.emplace_back(this, f, rdefs[i], _global_sequences.data());
}
}
// init lights
if (header.nLights) {
ModelLightDef const* lDefs = reinterpret_cast<ModelLightDef const*>(f.getBuffer() + header.ofsLights);
for (size_t i=0; i<header.nLights; ++i)
_lights.emplace_back (f, lDefs[i], _global_sequences.data());
}
animcalc = false;
}
void Model::calcBones( math::matrix_4x4 const& model_view
, int _anim
, int time
, int animation_time
)
{
for (size_t i = 0; i<header.nBones; ++i) {
bones[i].calc = false;
}
for (size_t i = 0; i<header.nBones; ++i) {
bones[i].calcMatrix(model_view, bones.data(), _anim, time, animation_time);
}
}
void Model::animate(math::matrix_4x4 const& model_view, int anim_id, int anim_time)
{
if (_animations_seq_per_id.empty() || _animations_seq_per_id[anim_id].empty())
{
// use "default" vertices if the animation hasn't been found
if (_current_vertices.empty())
{
_current_vertices = _vertices;
opengl::scoped::buffer_binder<GL_ARRAY_BUFFER> const binder(_vertices_buffer);
gl.bufferData(GL_ARRAY_BUFFER, _current_vertices.size() * sizeof(ModelVertex), _current_vertices.data(), GL_STATIC_DRAW);
}
return;
}
int tmax = _animation_length[anim_id];
int t = anim_time % tmax;
int current_sub_anim = 0;
int time_for_anim = t;
for (auto const& sub_animation : _animations_seq_per_id[anim_id])
{
if (sub_animation.second.length > time_for_anim)
{
current_sub_anim = sub_animation.first;
break;
}
time_for_anim -= sub_animation.second.length;
}
ModelAnimation const& a = _animations_seq_per_id[anim_id][current_sub_anim];
_current_anim_seq = a.Index;//_animations_seq_lookup[anim_id][current_sub_anim];
_anim_time = t;
_global_animtime = anim_time;
if (animBones)
{
calcBones(model_view, _current_anim_seq, t, _global_animtime);
}
if (animGeometry)
{
// transform vertices
_current_vertices = _vertices;
for (auto& vertex : _current_vertices)
{
::math::vector_3d v(0, 0, 0), n(0, 0, 0);
for (size_t b (0); b < 4; ++b)
{
if (vertex.weights[b] <= 0)
continue;
::math::vector_3d tv = bones[vertex.bones[b]].mat * vertex.position;
::math::vector_3d tn = bones[vertex.bones[b]].mrot * vertex.normal;
v += tv * (static_cast<float> (vertex.weights[b]) / 255.0f);
n += tn * (static_cast<float> (vertex.weights[b]) / 255.0f);
}
vertex.position = v;
vertex.normal = n.normalized();
}
opengl::scoped::buffer_binder<GL_ARRAY_BUFFER> const binder (_vertices_buffer);
gl.bufferData (GL_ARRAY_BUFFER, _current_vertices.size() * sizeof (ModelVertex), _current_vertices.data(), GL_STREAM_DRAW);
}
for (size_t i=0; i<header.nLights; ++i)
{
if (_lights[i].parent >= 0)
{
_lights[i].tpos = bones[_lights[i].parent].mat * _lights[i].pos;
_lights[i].tdir = bones[_lights[i].parent].mrot * _lights[i].dir;
}
}
for (auto& particle : _particles)
{
// random time distribution for teh win ..?
int pt = (t + static_cast<int>(tmax*particle.tofs)) % tmax;
particle.setup(_current_anim_seq, pt, _global_animtime);
}
for (size_t i = 0; i<header.nRibbonEmitters; ++i)
{
_ribbons[i].setup(_current_anim_seq, t, _global_animtime);
}
for (auto& tex_anim : _texture_animations)
{
tex_anim.calc(_current_anim_seq, t, _anim_time);
}
}
void TextureAnim::calc(int anim, int time, int animtime)
{
mat = math::matrix_4x4::unit;
if (trans.uses(anim))
{
mat *= math::matrix_4x4 (math::matrix_4x4::translation, trans.getValue(anim, time, animtime));
}
if (rot.uses(anim))
{
mat *= math::matrix_4x4 (math::matrix_4x4::rotation, rot.getValue(anim, time, animtime));
}
if (scale.uses(anim))
{
mat *= math::matrix_4x4 (math::matrix_4x4::scale, scale.getValue(anim, time, animtime));
}
}
ModelColor::ModelColor(const MPQFile& f, const ModelColorDef &mcd, int *global)
: color (mcd.color, f, global)
, opacity(mcd.opacity, f, global)
{}
ModelTransparency::ModelTransparency(const MPQFile& f, const ModelTransDef &mcd, int *global)
: trans (mcd.trans, f, global)
{}
ModelLight::ModelLight(const MPQFile& f, const ModelLightDef &mld, int *global)
: type (mld.type)
, parent (mld.bone)
, pos (fixCoordSystem(mld.pos))
, tpos (fixCoordSystem(mld.pos))
, dir (::math::vector_3d(0,1,0))
, tdir (::math::vector_3d(0,1,0)) // obviously wrong
, diffColor (mld.color, f, global)
, ambColor (mld.ambColor, f, global)
, diffIntensity (mld.intensity, f, global)
, ambIntensity (mld.ambIntensity, f, global)
{}
void ModelLight::setup(int time, opengl::light, int animtime)
{
math::vector_4d ambcol(ambColor.getValue(0, time, animtime) * ambIntensity.getValue(0, time, animtime), 1.0f);
math::vector_4d diffcol(diffColor.getValue(0, time, animtime) * diffIntensity.getValue(0, time, animtime), 1.0f);
math::vector_4d p;
enum ModelLightTypes {
MODELLIGHT_DIRECTIONAL = 0,
MODELLIGHT_POINT
};
if (type == MODELLIGHT_DIRECTIONAL) {
// directional
p = math::vector_4d(tdir, 0.0f);
}
else if (type == MODELLIGHT_POINT) {
// point
p = math::vector_4d(tpos, 1.0f);
}
else {
p = math::vector_4d(tpos, 1.0f);
LogError << "Light type " << type << " is unknown." << std::endl;
}
// todo: use models' light
}
TextureAnim::TextureAnim (const MPQFile& f, const ModelTexAnimDef &mta, int *global)
: trans (mta.trans, f, global)
, rot (mta.rot, f, global)
, scale (mta.scale, f, global)
, mat (math::matrix_4x4::uninitialized)
{}
Bone::Bone( const MPQFile& f,
const ModelBoneDef &b,
int *global,
const std::vector<std::unique_ptr<MPQFile>>& animation_files)
: trans (b.translation, f, global, animation_files)
, rot (b.rotation, f, global, animation_files)
, scale (b.scaling, f, global, animation_files)
, pivot (fixCoordSystem (b.pivot))
, parent (b.parent)
{
memcpy(&flags, &b.flags, sizeof(uint32_t));
trans.apply(fixCoordSystem);
rot.apply(fixCoordSystemQuat);
scale.apply(fixCoordSystem2);
}
void Bone::calcMatrix( math::matrix_4x4 const& model_view
, Bone *allbones
, int anim
, int time
, int animtime
)
{
if (calc) return;
math::matrix_4x4 m {math::matrix_4x4::unit};
math::quaternion q;
if ( flags.transformed
|| flags.billboard
|| flags.cylindrical_billboard_lock_x
|| flags.cylindrical_billboard_lock_y
|| flags.cylindrical_billboard_lock_z
)
{
m = {math::matrix_4x4::translation, pivot};
if (trans.uses(anim))
{
m *= math::matrix_4x4 (math::matrix_4x4::translation, trans.getValue (anim, time, animtime));
}
if (rot.uses(anim))
{
m *= math::matrix_4x4 (math::matrix_4x4::rotation, q = rot.getValue (anim, time, animtime));
}
if (scale.uses(anim))
{
m *= math::matrix_4x4 (math::matrix_4x4::scale, scale.getValue (anim, time, animtime));
}
if (flags.billboard)
{
math::vector_3d vRight (model_view[0], model_view[4], model_view[8]);
math::vector_3d vUp (model_view[1], model_view[5], model_view[9]); // Spherical billboarding
//math::vector_3d vUp = math::vector_3d(0,1,0); // Cylindrical billboarding
vRight = vRight * -1;
m (0, 2, vRight.x);
m (1, 2, vRight.y);
m (2, 2, vRight.z);
m (0, 1, vUp.x);
m (1, 1, vUp.y);
m (2, 1, vUp.z);
}
m *= math::matrix_4x4 (math::matrix_4x4::translation, -pivot);
}
if (parent >= 0)
{
allbones[parent].calcMatrix (model_view, allbones, anim, time, animtime);
mat = allbones[parent].mat * m;
}
else
{
mat = m;
}
// transform matrix for normal vectors ... ??
if (rot.uses(anim))
{
if (parent >= 0)
{
mrot = allbones[parent].mrot * math::matrix_4x4 (math::matrix_4x4::rotation, q);
}
else
{
mrot = math::matrix_4x4 (math::matrix_4x4::rotation, q);
}
}
else
{
mrot = math::matrix_4x4::unit;
}
calc = true;
}
void Model::draw( math::matrix_4x4 const& model_view
, ModelInstance& instance
, opengl::scoped::use_program& m2_shader
, math::frustum const& frustum
, const float& cull_distance
, const math::vector_3d& camera
, int animtime
, bool // draw_particles
, bool // all_boxes
, display_mode display
)
{
if (!finishedLoading() || loading_failed())
{
return;
}
if (!instance.is_visible(frustum, cull_distance, camera, display))
{
return;
}
if (!_finished_upload)
{
upload();
}
if (animated && (!animcalc || _per_instance_animation))
{
animate(model_view, 0, animtime);
animcalc = true;
}
opengl::scoped::vao_binder const _(_vao);
m2_shader.uniform("transform", instance.transform_matrix_transposed());
{
opengl::scoped::buffer_binder<GL_ARRAY_BUFFER> const binder(_vertices_buffer);
m2_shader.attrib("pos", 3, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), 0);
//m2_shader.attrib("bones_weight", 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (ModelVertex), reinterpret_cast<void*> (sizeof (::math::vector_3d)));
//m2_shader.attrib("bones_indices", 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (ModelVertex), reinterpret_cast<void*> (sizeof (::math::vector_3d) + 4));
m2_shader.attrib("normal", 3, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), reinterpret_cast<void*> (sizeof(::math::vector_3d) + 8));
m2_shader.attrib("texcoord1", 2, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), reinterpret_cast<void*> (sizeof(::math::vector_3d) * 2 + 8));
m2_shader.attrib("texcoord2", 2, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), reinterpret_cast<void*> (sizeof(::math::vector_3d) * 2 + 8 + sizeof(::math::vector_2d)));
}
opengl::scoped::buffer_binder<GL_ELEMENT_ARRAY_BUFFER> indices_binder(_indices_buffer);
for (ModelRenderPass& p : _render_passes)
{
if (p.prepare_draw(m2_shader, this))
{
gl.drawElements(GL_TRIANGLES, p.index_count, GL_UNSIGNED_SHORT, reinterpret_cast<void*>(p.index_start * sizeof(GLushort)));
p.after_draw();
}
}
gl.disable(GL_BLEND);
gl.enable(GL_CULL_FACE);
gl.depthMask(GL_TRUE);
}
void Model::draw ( math::matrix_4x4 const& model_view
, std::vector<ModelInstance*> instances
, opengl::scoped::use_program& m2_shader
, math::frustum const& frustum
, const float& cull_distance
, const math::vector_3d& camera
, bool // draw_fog
, int animtime
, bool draw_particles
, bool all_boxes
, std::unordered_map<Model*, std::size_t>& models_with_particles
, std::unordered_map<Model*, std::size_t>& model_boxes_to_draw
, display_mode display
)
{
if (!finishedLoading() || loading_failed())
{
return;
}
if (!_finished_upload)
{
upload();
}
if (animated && (!animcalc || _per_instance_animation))
{
animate(model_view, 0, animtime);
animcalc = true;
}
std::vector<math::matrix_4x4> transform_matrix;
for (ModelInstance* mi : instances)
{
if (mi->is_visible(frustum, cull_distance, camera, display))
{
transform_matrix.push_back(mi->transform_matrix_transposed());
}
}
if (transform_matrix.empty())
{
return;
}
// store the model count to draw the bounding boxes later
if (all_boxes || _hidden)
{
model_boxes_to_draw.emplace(this, transform_matrix.size());
}
if (draw_particles && (!_particles.empty() || !_ribbons.empty()))
{
models_with_particles.emplace(this, transform_matrix.size());
}
opengl::scoped::vao_binder const _ (_vao);
{
opengl::scoped::buffer_binder<GL_ARRAY_BUFFER> const transform_binder (_transform_buffer);
gl.bufferData(GL_ARRAY_BUFFER, transform_matrix.size() * sizeof(::math::matrix_4x4), transform_matrix.data(), GL_DYNAMIC_DRAW);
m2_shader.attrib("transform", 0, 1);
}
{
opengl::scoped::buffer_binder<GL_ARRAY_BUFFER> const binder (_vertices_buffer);
m2_shader.attrib("pos", 3, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), 0);
//m2_shader.attrib("bones_weight", 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (ModelVertex), reinterpret_cast<void*> (sizeof (::math::vector_3d)));
//m2_shader.attrib("bones_indices", 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (ModelVertex), reinterpret_cast<void*> (sizeof (::math::vector_3d) + 4));
m2_shader.attrib("normal", 3, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), reinterpret_cast<void*> (sizeof (::math::vector_3d) + 8));
m2_shader.attrib("texcoord1", 2, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), reinterpret_cast<void*> (sizeof (::math::vector_3d) * 2 + 8));
m2_shader.attrib("texcoord2", 2, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), reinterpret_cast<void*> (sizeof (::math::vector_3d) * 2 + 8 + sizeof(::math::vector_2d)));
}
opengl::scoped::buffer_binder<GL_ELEMENT_ARRAY_BUFFER> indices_binder(_indices_buffer);
for (ModelRenderPass& p : _render_passes)
{
if (p.prepare_draw(m2_shader, this))
{
gl.drawElementsInstanced(GL_TRIANGLES, p.index_count, GL_UNSIGNED_SHORT, reinterpret_cast<void*>(p.index_start * sizeof(GLushort)), transform_matrix.size());
p.after_draw();
}
}
gl.disable(GL_BLEND);
gl.enable(GL_CULL_FACE);
gl.depthMask(GL_TRUE);
}
void Model::draw_particles( math::matrix_4x4 const& model_view
, opengl::scoped::use_program& particles_shader
, std::size_t instance_count
)
{
for (auto& p : _particles)
{
p.draw(model_view, particles_shader, _transform_buffer, instance_count);
}
}
void Model::draw_ribbons( opengl::scoped::use_program& ribbons_shader
, std::size_t instance_count
)
{
for (auto& r : _ribbons)
{
r.draw(ribbons_shader, _transform_buffer, instance_count);
}
}
void Model::draw_box (opengl::scoped::use_program& m2_box_shader, std::size_t box_count)
{
opengl::scoped::vao_binder const _ (_box_vao);
{
opengl::scoped::buffer_binder<GL_ARRAY_BUFFER> const transform_binder (_transform_buffer);
m2_box_shader.attrib("transform", 0, 1);
}
{
opengl::scoped::buffer_binder<GL_ARRAY_BUFFER> const binder (_box_vbo);
m2_box_shader.attrib("position", 3, GL_FLOAT, GL_FALSE, 0, 0);
}
opengl::scoped::buffer_binder<GL_ELEMENT_ARRAY_BUFFER> indices_binder(_box_indices_buffer);
gl.drawElementsInstanced (GL_LINE_STRIP, _box_indices.size(), GL_UNSIGNED_SHORT, nullptr, box_count);
}
std::vector<float> Model::intersect (math::matrix_4x4 const& model_view, math::ray const& ray, int animtime)
{
std::vector<float> results;
if (!finishedLoading() || loading_failed())
{
return results;
}
if (animated && (!animcalc || _per_instance_animation))
{
animate (model_view, 0, animtime);
animcalc = true;
}
if (use_fake_geometry())
{
auto& fake_geom = _fake_geometry.get();
for (size_t i = 0; i < fake_geom.indices.size(); i += 3)
{
if (auto distance
= ray.intersect_triangle(fake_geom.vertices[fake_geom.indices[i + 0]],
fake_geom.vertices[fake_geom.indices[i + 1]],
fake_geom.vertices[fake_geom.indices[i + 2]])
)
{
results.emplace_back (*distance);
}
}
return results;
}
for (auto&& pass : _render_passes)
{
for (size_t i (pass.index_start); i < pass.index_start + pass.index_count; i += 3)
{
if ( auto distance
= ray.intersect_triangle( _current_vertices[_indices[i + 0]].position,
_current_vertices[_indices[i + 1]].position,
_current_vertices[_indices[i + 2]].position)
)
{
results.emplace_back (*distance);
}
}
}
return results;
}
void Model::lightsOn(opengl::light lbase)
{
// setup lights
for (unsigned int i=0, l=lbase; i<header.nLights; ++i) _lights[i].setup(_anim_time, l++, _global_animtime);
}
void Model::lightsOff(opengl::light lbase)
{
for (unsigned int i = 0, l = lbase; i<header.nLights; ++i) gl.disable(l++);
}
void Model::upload()
{
for (std::string texture : _textureFilenames)
_textures.emplace_back(texture);
_buffers.upload();
_vertex_arrays.upload();
if (!animGeometry)
{
opengl::scoped::buffer_binder<GL_ARRAY_BUFFER> const binder (_vertices_buffer);
gl.bufferData (GL_ARRAY_BUFFER, _current_vertices.size() * sizeof (ModelVertex), _current_vertices.data(), GL_STATIC_DRAW);
}
{
opengl::scoped::buffer_binder<GL_ARRAY_BUFFER> const binder (_box_vbo);
gl.bufferData (GL_ARRAY_BUFFER, _vertex_box_points.size() * sizeof (math::vector_3d), _vertex_box_points.data(), GL_STATIC_DRAW);
}
opengl::scoped::buffer_binder<GL_ELEMENT_ARRAY_BUFFER> indices_binder(_indices_buffer);
gl.bufferData (GL_ELEMENT_ARRAY_BUFFER, _indices.size() * sizeof(uint16_t), _indices.data(), GL_STATIC_DRAW);
opengl::scoped::buffer_binder<GL_ELEMENT_ARRAY_BUFFER> box_indices_binder(_box_indices_buffer);
gl.bufferData (GL_ELEMENT_ARRAY_BUFFER, _box_indices.size() * sizeof(uint16_t), _box_indices.data(), GL_STATIC_DRAW);
_finished_upload = true;
}
void Model::updateEmitters(float dt)
{
if (finished)
{
for (auto& particle : _particles)
{
particle.update (dt);
}
}
}