Files
noggit-red/src/noggit/WMO.cpp

1382 lines
34 KiB
C++

// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <math/frustum.hpp>
#include <noggit/AsyncLoader.h>
#include <noggit/Log.h> // LogDebug
#include <noggit/ModelManager.h> // ModelManager
#include <noggit/TextureManager.h> // TextureManager, Texture
#include <noggit/WMO.h>
#include <noggit/World.h>
#include <opengl/primitives.hpp>
#include <opengl/scoped.hpp>
#include <boost/algorithm/string.hpp>
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>
WMO::WMO(const std::string& filenameArg)
: AsyncObject(filenameArg)
, _finished_upload(false)
{
}
void WMO::finishLoading ()
{
MPQFile f(filename);
if (f.isEof()) {
LogError << "Error loading WMO \"" << filename << "\"." << std::endl;
return;
}
uint32_t fourcc;
uint32_t size;
float ff[3];
char const* ddnames = nullptr;
char const* groupnames = nullptr;
// - MVER ----------------------------------------------
uint32_t version;
f.read (&fourcc, 4);
f.seekRelative (4);
f.read (&version, 4);
assert (fourcc == 'MVER' && version == 17);
// - MOHD ----------------------------------------------
f.read (&fourcc, 4);
f.seekRelative (4);
assert (fourcc == 'MOHD');
CArgb ambient_color;
unsigned int nTextures, nGroups, nP, nLights, nModels, nDoodads, nDoodadSets, nX;
// header
f.read (&nTextures, 4);
f.read (&nGroups, 4);
f.read (&nP, 4);
f.read (&nLights, 4);
f.read (&nModels, 4);
f.read (&nDoodads, 4);
f.read (&nDoodadSets, 4);
f.read (&ambient_color, 4);
f.read (&nX, 4);
f.read (ff, 12);
extents[0] = ::math::vector_3d (ff[0], ff[1], ff[2]);
f.read (ff, 12);
extents[1] = ::math::vector_3d (ff[0], ff[1], ff[2]);
f.read(&flags, 2);
f.seekRelative (2);
ambient_light_color.x = static_cast<float>(ambient_color.r) / 255.f;
ambient_light_color.y = static_cast<float>(ambient_color.g) / 255.f;
ambient_light_color.z = static_cast<float>(ambient_color.b) / 255.f;
ambient_light_color.w = static_cast<float>(ambient_color.a) / 255.f;
// - MOTX ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOTX');
std::vector<char> texbuf (size);
f.read (texbuf.data(), texbuf.size());
// - MOMT ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOMT');
std::size_t const num_materials (size / 0x40);
materials.resize (num_materials);
std::map<std::uint32_t, std::size_t> texture_offset_to_inmem_index;
auto load_texture
( [&] (std::uint32_t ofs)
{
char const* texture
(texbuf[ofs] ? &texbuf[ofs] : "textures/shanecube.blp");
auto const mapping
(texture_offset_to_inmem_index.emplace(ofs, textures.size()));
if (mapping.second)
{
textures.emplace_back(texture);
}
return mapping.first->second;
}
);
for (size_t i(0); i < num_materials; ++i)
{
f.read(&materials[i], sizeof(WMOMaterial));
uint32_t shader = materials[i].shader;
bool use_second_texture = (shader == 6 || shader == 5 || shader == 3);
materials[i].texture1 = load_texture(materials[i].texture_offset_1);
if (use_second_texture)
{
materials[i].texture2 = load_texture(materials[i].texture_offset_2);
}
}
// - MOGN ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOGN');
groupnames = reinterpret_cast<char const*> (f.getPointer ());
f.seekRelative (size);
// - MOGI ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOGI');
for (size_t i (0); i < nGroups; ++i) {
groups.emplace_back (this, &f, i, groupnames);
}
// - MOSB ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOSB');
if (size > 4)
{
std::string path = noggit::mpq::normalized_filename(std::string (reinterpret_cast<char const*>(f.getPointer ())));
boost::replace_all(path, "mdx", "m2");
if (path.length())
{
if (MPQFile::exists(path))
{
skybox = scoped_model_reference(path);
}
}
}
f.seekRelative (size);
// - MOPV ----------------------------------------------
f.read (&fourcc, 4);
f.read(&size, 4);
assert (fourcc == 'MOPV');
std::vector<math::vector_3d> portal_vertices;
for (size_t i (0); i < size / 12; ++i) {
f.read (ff, 12);
portal_vertices.push_back(math::vector_3d(ff[0], ff[2], -ff[1]));
}
// - MOPT ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOPT');
f.seekRelative (size);
// - MOPR ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert(fourcc == 'MOPR');
f.seekRelative (size);
// - MOVV ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOVV');
f.seekRelative (size);
// - MOVB ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOVB');
f.seekRelative (size);
// - MOLT ----------------------------------------------
f.read (&fourcc, 4);
f.seekRelative (4);
assert (fourcc == 'MOLT');
for (size_t i (0); i < nLights; ++i) {
WMOLight l;
l.init (&f);
lights.push_back (l);
}
// - MODS ----------------------------------------------
f.read (&fourcc, 4);
f.seekRelative (4);
assert (fourcc == 'MODS');
for (size_t i (0); i < nDoodadSets; ++i) {
WMODoodadSet dds;
f.read (&dds, 32);
doodadsets.push_back (dds);
}
// - MODN ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MODN');
if (size)
{
ddnames = reinterpret_cast<char const*> (f.getPointer ());
f.seekRelative (size);
}
// - MODD ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MODD');
for (size_t i (0); i < size / 0x28; ++i) {
struct
{
uint32_t name_offset : 24;
uint32_t flag_AcceptProjTex : 1;
uint32_t flag_0x2 : 1;
uint32_t flag_0x4 : 1;
uint32_t flag_0x8 : 1;
uint32_t flags_unused : 4;
} x;
size_t after_entry (f.getPos() + 0x28);
f.read (&x, sizeof (x));
modelis.emplace_back(ddnames + x.name_offset, &f);
model_nearest_light_vector.emplace_back();
f.seek (after_entry);
}
// - MFOG ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MFOG');
int nfogs = size / 0x30;
for (size_t i (0); i < nfogs; ++i) {
WMOFog fog;
fog.init (&f);
fogs.push_back (fog);
}
for (auto& group : groups)
group.load();
finished = true;
_state_changed.notify_all();
}
void WMO::draw ( opengl::scoped::use_program& wmo_shader
, math::matrix_4x4 const& model_view
, math::matrix_4x4 const& projection
, math::matrix_4x4 const& transform_matrix
, math::matrix_4x4 const& transform_matrix_transposed
, bool boundingbox
, math::frustum const& frustum
, const float& cull_distance
, const math::vector_3d& camera
, bool // draw_doodads
, bool draw_fog
, liquid_render& render
, int animtime
, bool world_has_skies
, display_mode display
)
{
wmo_shader.uniform("ambient_color", ambient_light_color.xyz());
for (auto& group : groups)
{
if (!group.is_visible(transform_matrix, frustum, cull_distance, camera, display))
{
continue;
}
group.draw ( wmo_shader
, frustum
, cull_distance
, camera
, draw_fog
, world_has_skies
);
group.drawLiquid ( transform_matrix_transposed
, render
, draw_fog
, animtime
);
}
if (boundingbox)
{
opengl::scoped::bool_setter<GL_BLEND, GL_TRUE> const blend;
gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
for (auto& group : groups)
{
opengl::primitives::wire_box::getInstance().draw( model_view
, projection
, transform_matrix_transposed
, {1.0f, 1.0f, 1.0f, 1.0f}
, group.BoundingBoxMin
, group.BoundingBoxMax
);
}
opengl::primitives::wire_box::getInstance().draw ( model_view
, projection
, transform_matrix_transposed
, {1.0f, 0.0f, 0.0f, 1.0f}
, math::vector_3d(extents[0].x, extents[0].z, -extents[0].y)
, math::vector_3d(extents[1].x, extents[1].z, -extents[1].y)
);
}
}
std::vector<float> WMO::intersect (math::ray const& ray) const
{
std::vector<float> results;
if (!finishedLoading() || loading_failed())
{
return results;
}
for (auto& group : groups)
{
group.intersect (ray, &results);
}
return results;
}
bool WMO::draw_skybox ( math::matrix_4x4 const& model_view
, math::vector_3d const& camera_pos
, opengl::scoped::use_program& m2_shader
, math::frustum const& frustum
, const float& cull_distance
, int animtime
, bool draw_particles
, math::vector_3d aabb_min
, math::vector_3d aabb_max
, std::map<int, std::pair<math::vector_3d, math::vector_3d>> const& group_extents
) const
{
if (!skybox || !camera_pos.is_inside_of(aabb_min, aabb_max))
{
return false;
}
for (int i=0; i<groups.size(); ++i)
{
auto const& g = groups[i];
if (!g.has_skybox())
{
continue;
}
auto& extent(group_extents.at(i));
if (camera_pos.is_inside_of(extent.first, extent.second))
{
ModelInstance sky(skybox.get()->filename);
sky.pos = camera_pos;
sky.scale = 2.f;
sky.recalcExtents();
skybox->get()->draw(model_view, sky, m2_shader, frustum, cull_distance, camera_pos, animtime, draw_particles, false, display_mode::in_3D);
return true;
}
}
return false;
}
std::map<uint32_t, std::vector<wmo_doodad_instance>> WMO::doodads_per_group(uint16_t doodadset) const
{
std::map<uint32_t, std::vector<wmo_doodad_instance>> doodads;
if (doodadset >= doodadsets.size())
{
LogError << "Invalid doodadset for instance of wmo " << filename << std::endl;
return doodads;
}
auto const& dset = doodadsets[doodadset];
uint32_t start = dset.start, end = start + dset.size;
for (int i = 0; i < groups.size(); ++i)
{
for (uint16_t ref : groups[i].doodad_ref())
{
if (ref >= start && ref < end)
{
doodads[i].push_back(modelis[ref]);
}
}
}
return doodads;
}
void WMOLight::init(MPQFile* f)
{
char type[4];
f->read(&type, 4);
f->read(&color, 4);
f->read(pos, 12);
f->read(&intensity, 4);
f->read(unk, 4 * 5);
f->read(&r, 4);
pos = math::vector_3d(pos.x, pos.z, -pos.y);
// rgb? bgr? hm
float fa = ((color & 0xff000000) >> 24) / 255.0f;
float fr = ((color & 0x00ff0000) >> 16) / 255.0f;
float fg = ((color & 0x0000ff00) >> 8) / 255.0f;
float fb = ((color & 0x000000ff)) / 255.0f;
fcolor = math::vector_4d(fr, fg, fb, fa);
fcolor *= intensity;
fcolor.w = 1.0f;
/*
// light logging
gLog("Light %08x @ (%4.2f,%4.2f,%4.2f)\t %4.2f, %4.2f, %4.2f, %4.2f, %4.2f, %4.2f, %4.2f\t(%d,%d,%d,%d)\n",
color, pos.x, pos.y, pos.z, intensity,
unk[0], unk[1], unk[2], unk[3], unk[4], r,
type[0], type[1], type[2], type[3]);
*/
}
void WMOLight::setup(GLint)
{
// not used right now -_-
}
void WMOLight::setupOnce(GLint, math::vector_3d, math::vector_3d)
{
//math::vector_4d position(dir, 0);
//math::vector_4d position(0,1,0,0);
//math::vector_4d ambient = math::vector_4d(light_color * 0.3f, 1);
//math::vector_4d diffuse = math::vector_4d(light_color, 1);
//gl.enable(light);
}
WMOGroup::WMOGroup(WMO *_wmo, MPQFile* f, int _num, char const* names)
: wmo(_wmo)
, num(_num)
{
// extract group info from f
std::uint32_t flags; // not used, the flags are in the group header
f->read(&flags, 4);
float ff[3];
f->read(ff, 12);
VertexBoxMax = math::vector_3d(ff[0], ff[1], ff[2]);
f->read(ff, 12);
VertexBoxMin = math::vector_3d(ff[0], ff[1], ff[2]);
int nameOfs;
f->read(&nameOfs, 4);
//! \todo get proper name from group header and/or dbc?
if (nameOfs > 0) {
name = std::string(names + nameOfs);
}
else name = "(no name)";
}
WMOGroup::WMOGroup(WMOGroup const& other)
: BoundingBoxMin(other.BoundingBoxMin)
, BoundingBoxMax(other.BoundingBoxMax)
, VertexBoxMin(other.VertexBoxMin)
, VertexBoxMax(other.VertexBoxMax)
, use_outdoor_lights(other.use_outdoor_lights)
, name(other.name)
, wmo(other.wmo)
, header(other.header)
, center(other.center)
, rad(other.rad)
, num(other.num)
, fog(other.fog)
, _doodad_ref(other._doodad_ref)
, _batches(other._batches)
, _vertices(other._vertices)
, _normals(other._normals)
, _texcoords(other._texcoords)
, _texcoords_2(other._texcoords_2)
, _vertex_colors(other._vertex_colors)
, _indices(other._indices)
{
if (other.lq)
{
lq = std::make_unique<wmo_liquid>(*other.lq.get());
}
}
namespace
{
math::vector_4d colorFromInt(unsigned int col)
{
GLubyte r, g, b, a;
a = (col & 0xFF000000) >> 24;
r = (col & 0x00FF0000) >> 16;
g = (col & 0x0000FF00) >> 8;
b = (col & 0x000000FF);
return math::vector_4d(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
}
}
void WMOGroup::upload()
{
_vertex_array.upload();
_buffers.upload();
gl.bufferData<GL_ARRAY_BUFFER> ( _vertices_buffer
, _vertices.size() * sizeof (*_vertices.data())
, _vertices.data()
, GL_STATIC_DRAW
);
gl.bufferData<GL_ARRAY_BUFFER> ( _normals_buffer
, _normals.size() * sizeof (*_normals.data())
, _normals.data()
, GL_STATIC_DRAW
);
gl.bufferData<GL_ARRAY_BUFFER> ( _texcoords_buffer
, _texcoords.size() * sizeof (*_texcoords.data())
, _texcoords.data()
, GL_STATIC_DRAW
);
gl.bufferData<GL_ELEMENT_ARRAY_BUFFER, std::uint16_t>(_indices_buffer, _indices, GL_STATIC_DRAW);
if (header.flags.has_two_motv)
{
gl.bufferData<GL_ARRAY_BUFFER, math::vector_2d> ( _texcoords_buffer_2
, _texcoords_2
, GL_STATIC_DRAW
);
}
gl.bufferData<GL_ARRAY_BUFFER> ( _vertex_colors_buffer
, _vertex_colors.size() * sizeof (*_vertex_colors.data())
, _vertex_colors.data()
, GL_STATIC_DRAW
);
_uploaded = true;
}
void WMOGroup::setup_vao(opengl::scoped::use_program& wmo_shader)
{
opengl::scoped::index_buffer_manual_binder indices (_indices_buffer);
{
opengl::scoped::vao_binder const _ (_vao);
wmo_shader.attrib("position", _vertices_buffer, 3, GL_FLOAT, GL_FALSE, 0, 0);
wmo_shader.attrib("normal", _normals_buffer, 3, GL_FLOAT, GL_FALSE, 0, 0);
wmo_shader.attrib("texcoord", _texcoords_buffer, 2, GL_FLOAT, GL_FALSE, 0, 0);
if (header.flags.has_two_motv)
{
wmo_shader.attrib("texcoord_2", _texcoords_buffer_2, 2, GL_FLOAT, GL_FALSE, 0, 0);
}
// even if the 2 flags are set there's only one vertex color vector, the 2nd chunk is used for alpha only
if (header.flags.has_vertex_color || header.flags.use_mocv2_for_texture_blending)
{
wmo_shader.attrib("vertex_color", _vertex_colors_buffer, 4, GL_FLOAT, GL_FALSE, 0, 0);
}
indices.bind();
}
_vao_is_setup = true;
}
void WMOGroup::load()
{
// open group file
std::stringstream curNum;
curNum << "_" << std::setw (3) << std::setfill ('0') << num;
std::string fname = wmo->filename;
fname.insert (fname.find (".wmo"), curNum.str ());
MPQFile f(fname);
if (f.isEof()) {
LogError << "Error loading WMO \"" << fname << "\"." << std::endl;
return;
}
uint32_t fourcc;
uint32_t size;
// - MVER ----------------------------------------------
f.read (&fourcc, 4);
f.seekRelative (4);
uint32_t version;
f.read (&version, 4);
assert (fourcc == 'MVER' && version == 17);
// - MOGP ----------------------------------------------
f.read (&fourcc, 4);
f.seekRelative (4);
assert (fourcc == 'MOGP');
f.read (&header, sizeof (wmo_group_header));
WMOFog &wf = wmo->fogs[header.fogs[0]];
if (wf.r2 <= 0) fog = -1; // default outdoor fog..?
else fog = header.fogs[0];
BoundingBoxMin = ::math::vector_3d (header.box1[0], header.box1[2], -header.box1[1]);
BoundingBoxMax = ::math::vector_3d (header.box2[0], header.box2[2], -header.box2[1]);
// - MOPY ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOPY');
f.seekRelative (size);
// - MOVI ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOVI');
_indices.resize (size / sizeof (uint16_t));
f.read (_indices.data (), size);
// - MOVT ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOVT');
// let's hope it's padded to 12 bytes, not 16...
::math::vector_3d const* vertices = reinterpret_cast< ::math::vector_3d const*>(f.getPointer ());
VertexBoxMin = ::math::vector_3d (9999999.0f, 9999999.0f, 9999999.0f);
VertexBoxMax = ::math::vector_3d (-9999999.0f, -9999999.0f, -9999999.0f);
rad = 0;
for (size_t i = 0; i < size / sizeof (::math::vector_3d); ++i) {
::math::vector_3d v (vertices[i].x, vertices[i].z, -vertices[i].y);
if (v.x < VertexBoxMin.x) VertexBoxMin.x = v.x;
if (v.y < VertexBoxMin.y) VertexBoxMin.y = v.y;
if (v.z < VertexBoxMin.z) VertexBoxMin.z = v.z;
if (v.x > VertexBoxMax.x) VertexBoxMax.x = v.x;
if (v.y > VertexBoxMax.y) VertexBoxMax.y = v.y;
if (v.z > VertexBoxMax.z) VertexBoxMax.z = v.z;
_vertices.push_back (v);
}
center = (VertexBoxMax + VertexBoxMin) * 0.5f;
rad = (VertexBoxMax - center).length () + 300.0f;;
f.seekRelative (size);
// - MONR ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MONR');
_normals.resize (size / sizeof (::math::vector_3d));
f.read (_normals.data (), size);
for (auto& n : _normals)
{
n = {n.x, n.z, -n.y};
}
// - MOTV ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOTV');
_texcoords.resize (size / sizeof (::math::vector_2d));
f.read (_texcoords.data (), size);
// - MOBA ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
assert (fourcc == 'MOBA');
_batches.resize (size / sizeof (wmo_batch));
f.read (_batches.data (), size);
// - MOLR ----------------------------------------------
if (header.flags.has_light)
{
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MOLR')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
f.seekRelative (size);
}
}
// - MODR ----------------------------------------------
if (header.flags.has_doodads)
{
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MODR')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
_doodad_ref.resize (size / sizeof (int16_t));
f.read (_doodad_ref.data (), size);
}
}
// - MOBN ----------------------------------------------
if (header.flags.has_bsp_tree)
{
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MOBN')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
f.seekRelative (size);
}
}
// - MOBR ----------------------------------------------
if (header.flags.has_bsp_tree)
{
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MOBR')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
f.seekRelative (size);
}
}
if (header.flags.flag_0x400)
{
// - MPBV ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MPBV')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
f.seekRelative (size);
}
// - MPBP ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MPBP')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
f.seekRelative (size);
}
// - MPBI ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MPBI')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
f.seekRelative (size);
}
// - MPBG ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MPBG')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
f.seekRelative (size);
}
}
// - MOCV ----------------------------------------------
if (header.flags.has_vertex_color)
{
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MOCV')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
load_mocv(f, size);
}
}
// - MLIQ ----------------------------------------------
if (header.flags.has_water)
{
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MLIQ')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
WMOLiquidHeader hlq;
f.read(&hlq, 0x1E);
lq = std::make_unique<wmo_liquid> ( &f
, hlq
, wmo->materials[hlq.material_id]
, header.group_liquid
, (bool)wmo->flags.use_liquid_type_dbc_id
, (bool)header.flags.ocean
);
// creating the wmo liquid doesn't move the position
f.seekRelative(size - 0x1E);
}
}
if (header.flags.has_mori_morb)
{
// - MORI ----------------------------------------------
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MORI')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
f.seekRelative (size);
}
// - MORB ----------------------------------------------
f.read(&fourcc, 4);
f.read(&size, 4);
if (fourcc != 'MORB')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
f.seekRelative (size);
}
}
// - MOTV ----------------------------------------------
if (header.flags.has_two_motv)
{
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MOTV')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
_texcoords_2.resize(size / sizeof(::math::vector_2d));
f.read(_texcoords_2.data(), size);
}
}
// - MOCV ----------------------------------------------
if (header.flags.use_mocv2_for_texture_blending)
{
f.read (&fourcc, 4);
f.read (&size, 4);
if (fourcc != 'MOCV')
{
LogError << "Broken header in WMO \"" << fname << "\". Trying to continue reading." << std::endl;
f.seek (f.getPos() - 8);
}
else
{
std::vector<CImVector> mocv_2(size / sizeof(CImVector));
f.read(mocv_2.data(), size);
for (int i = 0; i < mocv_2.size(); ++i)
{
float alpha = static_cast<float>(mocv_2[i].a) / 255.f;
// the second mocv is used for texture blending only
if (header.flags.has_vertex_color)
{
_vertex_colors[i].w = alpha;
}
else // no vertex coloring, only texture blending with the alpha
{
_vertex_colors.emplace_back(0.f, 0.f, 0.f, alpha);
}
}
}
}
//dl_light = 0;
// "real" lighting?
if (header.flags.indoor && header.flags.has_vertex_color)
{
::math::vector_3d dirmin(1, 1, 1);
float lenmin;
for (auto doodad : _doodad_ref)
{
lenmin = 999999.0f * 999999.0f;
ModelInstance& mi = wmo->modelis[doodad];
for (unsigned int j = 0; j < wmo->lights.size(); j++)
{
WMOLight& l = wmo->lights[j];
::math::vector_3d dir = l.pos - mi.pos;
float ll = dir.length_squared ();
if (ll < lenmin)
{
lenmin = ll;
dirmin = dir;
}
}
wmo->model_nearest_light_vector[doodad] = dirmin;
}
use_outdoor_lights = false;
}
else
{
use_outdoor_lights = true;
}
}
void WMOGroup::load_mocv(MPQFile& f, uint32_t size)
{
uint32_t const* colors = reinterpret_cast<uint32_t const*> (f.getPointer());
_vertex_colors.resize(size / sizeof(uint32_t));
for (size_t i(0); i < size / sizeof(uint32_t); ++i)
{
_vertex_colors[i] = colorFromInt(colors[i]);
}
if (wmo->flags.do_not_fix_vertex_color_alpha)
{
int interior_batchs_start = 0;
if (header.transparency_batches_count > 0)
{
interior_batchs_start = _batches[header.transparency_batches_count - 1].vertex_end + 1;
}
for (int n = interior_batchs_start; n < _vertex_colors.size(); ++n)
{
_vertex_colors[n].w = header.flags.exterior ? 1.f : 0.f;
}
}
else
{
fix_vertex_color_alpha();
}
// there's no read so this is required
f.seekRelative(size);
}
void WMOGroup::fix_vertex_color_alpha()
{
int interior_batchs_start = 0;
if (header.transparency_batches_count > 0)
{
interior_batchs_start = _batches[header.transparency_batches_count - 1].vertex_end + 1;
}
math::vector_4d wmo_ambient_color;
if (wmo->flags.use_unified_render_path)
{
wmo_ambient_color = {0.f, 0.f, 0.f, 0.f};
}
else
{
wmo_ambient_color = wmo->ambient_light_color;
// w is not used, set it to 0 to avoid changing the vertex color alpha
wmo_ambient_color.w = 0.f;
}
for (int i = 0; i < _vertex_colors.size(); ++i)
{
auto& color = _vertex_colors[i];
float r = color.x;
float g = color.y;
float b = color.z;
float a = color.w;
// I removed the color = color/2 because it's just multiplied by 2 in the shader afterward in blizzard's code
if (i >= interior_batchs_start)
{
r += ((r * a / 64.f) - wmo_ambient_color.x);
g += ((g * a / 64.f) - wmo_ambient_color.y);
r += ((b * a / 64.f) - wmo_ambient_color.z);
}
else
{
r -= wmo_ambient_color.x;
g -= wmo_ambient_color.y;
b -= wmo_ambient_color.z;
r = (r * (1.f - a));
g = (g * (1.f - a));
b = (b * (1.f - a));
}
color.x = std::min(255.f, std::max(0.f, r));
color.y = std::min(255.f, std::max(0.f, g));
color.z = std::min(255.f, std::max(0.f, b));
color.w = 1.f; // default value used in the shader so I simplified it here,
// it can be overriden by the 2nd mocv chunk
}
}
bool WMOGroup::is_visible( math::matrix_4x4 const& transform
, math::frustum const& frustum
, float const& cull_distance
, math::vector_3d const& camera
, display_mode display
) const
{
math::vector_3d pos = transform * center;
if (!frustum.intersectsSphere(pos, rad))
{
return false;
}
float dist = display == display_mode::in_3D
? (pos - camera).length() - rad
: std::abs(pos.y - camera.y) - rad;
return (dist < cull_distance);
}
void WMOGroup::draw( opengl::scoped::use_program& wmo_shader
, math::frustum const& // frustum
, const float& //cull_distance
, const math::vector_3d& //camera
, bool // draw_fog
, bool // world_has_skies
)
{
if (!_uploaded)
{
upload();
}
if (!_vao_is_setup)
{
setup_vao(wmo_shader);
}
bool exterior_lit = header.flags.exterior_lit | header.flags.exterior;
int has_mocv = header.flags.has_vertex_color | header.flags.use_mocv2_for_texture_blending;
wmo_shader.uniform("use_vertex_color", has_mocv);
wmo_shader.uniform("exterior_lit", (int)exterior_lit);
opengl::scoped::vao_binder const _ (_vao);
for (wmo_batch& batch : _batches)
{
WMOMaterial const& mat (wmo->materials.at (batch.texture));
float alpha_test = 0.003921568f; // 1/255
switch (mat.blend_mode)
{
case 1:
gl.disable(GL_BLEND);
alpha_test = 0.878431372f; // 224/255
break;
case 2:
gl.enable(GL_BLEND);
gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
break;
case 3:
gl.enable(GL_BLEND);
gl.blendFunc(GL_SRC_ALPHA, GL_ONE);
break;
case 4:
gl.enable(GL_BLEND);
gl.blendFunc(GL_DST_COLOR, GL_ZERO);
break;
case 5:
gl.enable(GL_BLEND);
gl.blendFunc(GL_DST_COLOR, GL_SRC_COLOR);
break;
case 6:
gl.enable(GL_BLEND);
gl.blendFunc(GL_DST_COLOR, GL_ONE);
break;
case 0:
default:
alpha_test = -1.f;
gl.disable(GL_BLEND);
break;
}
wmo_shader.uniform("shader_id", (int)mat.shader);
wmo_shader.uniform("alpha_test", alpha_test);
wmo_shader.uniform("unfogged", (int)mat.flags.unfogged);
wmo_shader.uniform("unlit", (int)mat.flags.unlit);
if (mat.flags.unculled)
{
gl.disable(GL_CULL_FACE);
}
else
{
gl.enable(GL_CULL_FACE);
}
opengl::texture::set_active_texture(0);
wmo->textures.at(mat.texture1)->bind();
// only shaders using 2 textures in wotlk
if (mat.shader == 6 || mat.shader == 5 || mat.shader == 3)
{
opengl::texture::set_active_texture(1);
wmo->textures.at(mat.texture2)->bind();
}
gl.drawRangeElements (GL_TRIANGLES, batch.vertex_start, batch.vertex_end, batch.index_count, GL_UNSIGNED_SHORT, reinterpret_cast<void*>(sizeof(std::uint16_t)*batch.index_start));
}
}
void WMOGroup::intersect (math::ray const& ray, std::vector<float>* results) const
{
if (!ray.intersect_bounds (VertexBoxMin, VertexBoxMax))
{
return;
}
//! \todo Also allow clicking on doodads and liquids.
for (auto&& batch : _batches)
{
for (size_t i (batch.index_start); i < batch.index_start + batch.index_count; i += 3)
{
if ( auto&& distance
= ray.intersect_triangle ( _vertices[_indices[i + 0]]
, _vertices[_indices[i + 1]]
, _vertices[_indices[i + 2]]
)
)
{
results->emplace_back (*distance);
}
}
}
}
void WMOGroup::drawLiquid ( math::matrix_4x4 const& transform
, liquid_render& render
, bool // draw_fog
, int animtime
)
{
// draw liquid
//! \todo culling for liquid boundingbox or something
if (lq)
{
gl.disable(GL_BLEND);
gl.depthMask(GL_TRUE);
lq->draw ( transform, render, animtime);
}
}
void WMOGroup::setupFog (bool draw_fog, std::function<void (bool)> setup_fog)
{
if (use_outdoor_lights || fog == -1) {
setup_fog (draw_fog);
}
else {
wmo->fogs[fog].setup();
}
}
void WMOFog::init(MPQFile* f)
{
f->read(this, 0x30);
color = math::vector_4d(((color1 & 0x00FF0000) >> 16) / 255.0f, ((color1 & 0x0000FF00) >> 8) / 255.0f,
(color1 & 0x000000FF) / 255.0f, ((color1 & 0xFF000000) >> 24) / 255.0f);
float temp;
temp = pos.y;
pos.y = pos.z;
pos.z = -temp;
fogstart = fogstart * fogend * 1.5f;
fogend *= 1.5;
}
void WMOFog::setup()
{
}
decltype (WMOManager::_) WMOManager::_;
void WMOManager::report()
{
std::string output = "Still in the WMO manager:\n";
_.apply ( [&] (std::string const& key, WMO const&)
{
output += " - " + key + "\n";
}
);
LogDebug << output;
}
void WMOManager::clear_hidden_wmos()
{
_.apply ( [&] (std::string const&, WMO& wmo)
{
wmo.show();
}
);
}