Files
wc3re/render/Object.cc

408 lines
16 KiB
C++

#include <glbinding/gl/gl.h>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/packing.hpp>
#include "Object.hh"
#include "ProgramProvider.hh"
#include "Renderer.hh"
#include "ObjDecoder.hh"
#include "PaletteDecoder.hh"
#include "renderutil.hh"
using namespace gl;
namespace render {
namespace {
struct VertexAttribs {
int32_t vertex[3];
uint16_t texCoords[2];
uint8_t useTexAnim;
bool operator<(VertexAttribs const& other) const {
if (vertex[0] == other.vertex[0]) {
if (vertex[1] == other.vertex[1]) {
if (vertex[2] == other.vertex[2]) {
if (texCoords[0] == other.texCoords[0]) {
if (texCoords[1] == other.texCoords[1])
return (useTexAnim < other.useTexAnim);
return (texCoords[1] < other.texCoords[1]);
}
return (texCoords[0] < other.texCoords[0]);
}
return (vertex[2] < other.vertex[2]);
}
return (vertex[1] < other.vertex[1]);
}
return (vertex[0] < other.vertex[0]);
}
};
const std::array<uint32_t, 256> vgaPalette = {
0x000000, 0x0000a8, 0x00a800, 0x00a8a8, 0xa80000, 0xa800a8, 0xa85400, 0xa8a8a8,
0x545454, 0x5454fc, 0x54fc54, 0x54fcfc, 0xfc5454, 0xfc54fc, 0xfcfc54, 0xfcfcfc,
0x000000, 0x141414, 0x202020, 0x2c2c2c, 0x383838, 0x444444, 0x505050, 0x606060,
0x707070, 0x808080, 0x909090, 0xa0a0a0, 0xb4b4b4, 0xc8c8c8, 0xe0e0e0, 0xfcfcfc,
0x0000fc, 0x4000fc, 0x7c00fc, 0xbc00fc, 0xfc00fc, 0xfc00bc, 0xfc007c, 0xfc0040,
0xfc0000, 0xfc4000, 0xfc7c00, 0xfcbc00, 0xfcfc00, 0xbcfc00, 0x7cfc00, 0x40fc00,
0x00fc00, 0x00fc40, 0x00fc7c, 0x00fcbc, 0x00fcfc, 0x00bcfc, 0x007cfc, 0x0040fc,
0x7c7cfc, 0x9c7cfc, 0xbc7cfc, 0xdc7cfc, 0xfc7cfc, 0xfc7cdc, 0xfc7cbc, 0xfc7c9c,
0xfc7c7c, 0xfc9c7c, 0xfcbc7c, 0xfcdc7c, 0xfcfc7c, 0xdcfc7c, 0xbcfc7c, 0x9cfc7c,
0x7cfc7c, 0x7cfc9c, 0x7cfcbc, 0x7cfcdc, 0x7cfcfc, 0x7cdcfc, 0x7cbcfc, 0x7c9cfc,
0xb4b4fc, 0xc4b4fc, 0xd8b4fc, 0xe8b4fc, 0xfcb4fc, 0xfcb4e8, 0xfcb4d8, 0xfcb4c4,
0xfcb4b4, 0xfcc4b4, 0xfcd8b4, 0xfce8b4, 0xfcfcb4, 0xe8fcb4, 0xd8fcb4, 0xc4fcb4,
0xb4fcb4, 0xb4fcc4, 0xb4fcd8, 0xb4fce8, 0xb4fcfc, 0xb4e8fc, 0xb4d8fc, 0xb4c4fc,
0x000070, 0x1c0070, 0x380070, 0x540070, 0x700070, 0x700054, 0x700038, 0x70001c,
0x700000, 0x701c00, 0x703800, 0x705400, 0x707000, 0x547000, 0x387000, 0x1c7000,
0x007000, 0x00701c, 0x007038, 0x007054, 0x007070, 0x005470, 0x003870, 0x001c70,
0x383870, 0x443870, 0x543870, 0x603870, 0x703870, 0x703860, 0x703854, 0x703844,
0x703838, 0x704438, 0x705438, 0x706038, 0x707038, 0x607038, 0x547038, 0x447038,
0x387038, 0x387044, 0x387054, 0x387060, 0x387070, 0x386070, 0x385470, 0x384470,
0x505070, 0x585070, 0x605070, 0x685070, 0x705070, 0x705068, 0x705060, 0x705058,
0x705050, 0x705850, 0x706050, 0x706850, 0x707050, 0x687050, 0x607050, 0x587050,
0x507050, 0x507058, 0x507060, 0x507068, 0x507070, 0x506870, 0x506070, 0x505870,
0x000040, 0x100040, 0x200040, 0x300040, 0x400040, 0x400030, 0x400020, 0x400010,
0x400000, 0x401000, 0x402000, 0x403000, 0x404000, 0x304000, 0x204000, 0x104000,
0x004000, 0x004010, 0x004020, 0x004030, 0x004040, 0x003040, 0x002040, 0x001040,
0x202040, 0x282040, 0x302040, 0x382040, 0x402040, 0x402038, 0x402030, 0x402028,
0x402020, 0x402820, 0x403020, 0x403820, 0x404020, 0x384020, 0x304020, 0x284020,
0x204020, 0x204028, 0x204030, 0x204038, 0x204040, 0x203840, 0x203040, 0x202840,
0x2c2c40, 0x302c40, 0x342c40, 0x3c2c40, 0x402c40, 0x402c3c, 0x402c34, 0x402c30,
0x402c2c, 0x40302c, 0x40342c, 0x403c2c, 0x40402c, 0x3c402c, 0x34402c, 0x30402c,
0x2c402c, 0x2c4030, 0x2c4034, 0x2c403c, 0x2c4040, 0x2c3c40, 0x2c3440, 0x2c3040,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000};
struct TexAtlasInfo {
unsigned x, y;
unsigned alignWidth, alignHeight;
float xofs, yofs, xscale, yscale;
ObjDecoder::Texture const& tex;
};
std::tuple<std::vector<TexAtlasInfo>, TextureResource>
genTexAtlas(ObjDecoder::Textures const& texs,
PaletteDecoder::Palette const& palt)
{
// Build texture atlas for object
unsigned maxTex, maxLayers;
std::tie(maxTex, maxLayers) = getGLTextureMaximums();
printf("Maximum texture size: %d, maximum array texture layers: %d\n", maxTex, maxLayers);
unsigned minSize = std::numeric_limits<unsigned>::max();
for (auto& tex : texs) {
minSize = std::min<unsigned>(minSize, std::min<unsigned>(tex.width, tex.height));
}
unsigned minLg = ilog2(minSize), minPow2 = (1<<minLg);
if (minPow2 < minSize) {
++minLg;
minPow2 <<= 1;
}
printf("Minimum texture size %u, using %u alignment and %u mipmap levels\n",
minSize, minPow2, minLg);
std::vector<TexAtlasInfo> ret;
unsigned xpos = 0, ypos = 0, lineMaxHeight = 0;
for (auto& tex : texs) {
unsigned alignWidth = (tex.width%minPow2 == 0)?tex.width:tex.width+(minPow2-tex.width%minPow2);
unsigned alignHeight = (tex.height%minPow2 == 0)?tex.height:tex.height+(minPow2-tex.height%minPow2);
if (xpos + alignWidth > static_cast<unsigned>(maxTex)) {
ypos += lineMaxHeight;
lineMaxHeight = 0;
xpos = 0;
}
if (ypos + alignHeight > static_cast<unsigned>(maxTex))
throw Exception{"Cannot fit obj textures into GL texture"};
TexAtlasInfo info = {xpos, ypos, alignWidth, alignHeight, 0.0f, 0.0f, 0.0f, 0.0f, tex};
ret.push_back(info);
lineMaxHeight = std::max<unsigned>(lineMaxHeight, alignHeight);
xpos += alignWidth;
}
unsigned atlasWidth = xpos, atlasHeight = ypos+lineMaxHeight;
printf("Texture atlas size: %ux%u\n", atlasWidth, atlasHeight);
TextureResource tex = create2DTexture(atlasWidth, atlasHeight, false, minLg+1);
std::vector<uint8_t> pixels(atlasWidth*atlasHeight*3);
// Copy textures into atlas
for (auto& info : ret) {
info.xofs = (info.x+0.5f)/atlasWidth;
info.yofs = (info.y+0.5f)/atlasHeight;
info.xscale = (info.tex.width-1.0f)/(atlasWidth*info.tex.width);
info.yscale = (info.tex.height-1.0f)/(atlasHeight*info.tex.height);
printf("Texture coordinate factors: %f, %f; %f, %f\n",
info.xofs, info.yofs, info.xscale, info.yscale);
for (unsigned y = 0;y < info.alignHeight;++y) {
for (unsigned x = 0;x < info.alignWidth;++x) {
unsigned col;
if (y < info.tex.height) {
if (x < info.tex.width) {
col = info.tex.pixels[y*info.tex.width + x];
} else {
col = info.tex.pixels[y*info.tex.width + info.tex.width-1];
}
} else {
if (x < info.tex.width) {
col = info.tex.pixels[(info.tex.height-1)*info.tex.width + x];
} else {
col = info.tex.pixels[info.tex.height*info.tex.width - 1];
}
}
pixels[(y+info.y)*atlasWidth*3+(info.x+x)*3] = palt[col*3]; //vgaPalette[col]>>16;
pixels[(y+info.y)*atlasWidth*3+(info.x+x)*3+1] = palt[col*3+1]; //vgaPalette[col]>>8;
pixels[(y+info.y)*atlasWidth*3+(info.x+x)*3+2] = palt[col*3+2]; //vgaPalette[col];
}
}
}
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, atlasWidth, atlasHeight, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
glGenerateMipmap(GL_TEXTURE_2D);
return std::make_tuple(std::move(ret), std::move(tex));
}
struct AnimTexInfo {
float xofs, yofs, xscale, yscale;
};
std::tuple<AnimTexInfo, TextureResource>
genTexAnim(ObjDecoder::TextureAnimation const& texAnim,
PaletteDecoder::Palette const& palt)
{
unsigned width = texAnim.width;
if (width%4 != 0)
width += 4 - (width%4);
unsigned height = texAnim.height;
TextureResource tex = create2DArrayTexture(width, height, texAnim.frames, false);
AnimTexInfo animInfo;
animInfo.xofs = 0.5f/width;
animInfo.yofs = 0.5f/height;
animInfo.xscale = (texAnim.width-1.0f)/(width*texAnim.width);
animInfo.yscale = (texAnim.height-1.0f)/(height*texAnim.height);
std::vector<uint8_t> pixels(texAnim.frames*width*height*3);
for (unsigned f = 0;f < texAnim.frames;++f) {
for (unsigned y = 0;y < texAnim.height;++y) {
for (unsigned x = 0;x < texAnim.width;++x) {
unsigned col = texAnim.pixels[f][y*texAnim.width+x];
pixels[f*width*height*3+y*width*3+x*3] = palt[col*3];
pixels[f*width*height*3+y*width*3+x*3+1] = palt[col*3+1];
pixels[f*width*height*3+y*width*3+x*3+2] = palt[col*3+2];
}
}
}
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, texAnim.frames,
GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
return std::make_tuple(animInfo, std::move(tex));
}
std::tuple<std::vector<VertexAttribs>, std::vector<uint16_t> >
genVertexAttribs(ObjDecoder& obj, std::vector<TexAtlasInfo>& atlasInfo,
AnimTexInfo *animTex = nullptr)
{
auto& tris = obj.getTriangles();
auto& quads = obj.getQuads();
// Deduplicate vertex attributes, track indices
std::map<VertexAttribs, size_t> vertexAttribsMap;
std::vector<VertexAttribs> vertexAttribs;
std::vector<uint16_t> indices;
auto& vertices = obj.getVertices();
for (auto& tri : tris) {
for (unsigned i = 0;i < 3;++i) {
VertexAttribs attrs;
std::copy(vertices.at(tri.vertIdx[i]).begin(), vertices.at(tri.vertIdx[i]).end(),
attrs.vertex);
if (tri.tex < atlasInfo.size()) {
auto& tex = atlasInfo[tri.tex];
attrs.texCoords[0] = glm::packUnorm1x16(tex.xofs + tri.texCoords[i][0]*tex.xscale);
attrs.texCoords[1] = glm::packUnorm1x16(tex.yofs + tri.texCoords[i][1]*tex.yscale);
attrs.useTexAnim = 0u;
} else if ((tri.tex == atlasInfo.size()) && animTex) {
attrs.texCoords[0] = glm::packUnorm1x16(animTex->xofs + tri.texCoords[i][0]*animTex->xscale);
attrs.texCoords[1] = glm::packUnorm1x16(animTex->yofs + tri.texCoords[i][1]*animTex->yscale);
attrs.useTexAnim = 1u;
} else
throw Exception{"Texture index out of range"};
auto ins = vertexAttribsMap.insert(std::make_pair(attrs, vertexAttribs.size()));
if (ins.second) {
indices.push_back(vertexAttribs.size());
vertexAttribs.push_back(attrs);
} else
indices.push_back(ins.first->second);
}
}
for (auto& quad : quads) {
for (unsigned i = 0;i < 3;++i) {
VertexAttribs attrs;
std::copy(vertices.at(quad.vertIdx[i]).begin(), vertices.at(quad.vertIdx[i]).end(),
attrs.vertex);
if (quad.tex < atlasInfo.size()) {
auto& tex = atlasInfo[quad.tex];
attrs.texCoords[0] = glm::packUnorm1x16(tex.xofs + quad.texCoords[i][0]*tex.xscale);
attrs.texCoords[1] = glm::packUnorm1x16(tex.yofs + quad.texCoords[i][1]*tex.yscale);
attrs.useTexAnim = 0u;
} else if ((quad.tex == atlasInfo.size()) && animTex) {
attrs.texCoords[0] = glm::packUnorm1x16(animTex->xofs + quad.texCoords[i][0]*animTex->xscale);
attrs.texCoords[1] = glm::packUnorm1x16(animTex->yofs + quad.texCoords[i][1]*animTex->yscale);
attrs.useTexAnim = 1u;
} else
throw Exception{"Texture index out of range"};
auto ins = vertexAttribsMap.insert(std::make_pair(attrs, vertexAttribs.size()));
if (ins.second) {
indices.push_back(vertexAttribs.size());
vertexAttribs.push_back(attrs);
} else
indices.push_back(ins.first->second);
}
for (unsigned i = 0;i < 3;++i) {
VertexAttribs attrs;
std::copy(vertices.at(quad.vertIdx[i?(i+1):i]).begin(), vertices.at(quad.vertIdx[i?(i+1):i]).end(),
attrs.vertex);
if (quad.tex < atlasInfo.size()) {
auto& tex = atlasInfo[quad.tex];
attrs.texCoords[0] = glm::packUnorm1x16(tex.xofs + quad.texCoords[i?(i+1):i][0]*tex.xscale);
attrs.texCoords[1] = glm::packUnorm1x16(tex.yofs + quad.texCoords[i?(i+1):i][1]*tex.yscale);
attrs.useTexAnim = 0u;
} else if ((quad.tex == atlasInfo.size()) && animTex) {
attrs.texCoords[0] = glm::packUnorm1x16(animTex->xofs + quad.texCoords[i?(i+1):i][0]*animTex->xscale);
attrs.texCoords[1] = glm::packUnorm1x16(animTex->yofs + quad.texCoords[i?(i+1):i][1]*animTex->yscale);
attrs.useTexAnim = 1u;
} else
throw Exception{"Texture index out of range"};
auto ins = vertexAttribsMap.insert(std::make_pair(attrs, vertexAttribs.size()));
if (ins.second) {
indices.push_back(vertexAttribs.size());
vertexAttribs.push_back(attrs);
} else
indices.push_back(ins.first->second);
}
}
printf("%lu indices, %lu attributes\n",
indices.size(), vertexAttribs.size());
return std::make_tuple(std::move(vertexAttribs), std::move(indices));
}
}
Object::Object(Renderer& renderer, ObjDecoder& obj, PaletteDecoder& palt)
: Drawable(renderer),
vbo_(), rot_(0.0f), animFrame_(0)
{
// Acquire shader
program_ = ProgramProvider::getInstance().getProgram("object", "object");
// Generate texture atlas
auto& texs = obj.getTextures();
std::vector<TexAtlasInfo> atlasInfo;
if (texs.size() > 0) {
std::tie(atlasInfo, tex_) = genTexAtlas(texs, palt.getPalette());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, static_cast<int>(GL_LINEAR_MIPMAP_LINEAR));
}
auto& texAnims = obj.getTextureAnimations();
AnimTexInfo animInfo;
if (texAnims.size() > 0) {
std::tie(animInfo, texAnim_) = genTexAnim(texAnims[0], palt.getPalette());
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, static_cast<int>(GL_LINEAR_MIPMAP_LINEAR));
}
// Generate vertex attribute array
std::vector<VertexAttribs> vertexAttribs;
std::vector<uint16_t> indices;
if (texAnims.size() > 0)
std::tie(vertexAttribs, indices) = genVertexAttribs(obj, atlasInfo, &animInfo);
else
std::tie(vertexAttribs, indices) = genVertexAttribs(obj, atlasInfo);
// Setup GL vertex buffer and vertex array
glGenVertexArrays(1, &vertexArray_.get());
vbo_ = VBOManager::getInstance().alloc(sizeof(VertexAttribs)*vertexAttribs.size()+
2*indices.size());
glBindBuffer(GL_ARRAY_BUFFER, vbo_.getVBOId());
glBindVertexArray(vertexArray_);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_.getVBOId());
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_INT, GL_FALSE, sizeof(VertexAttribs),
vbo_.getOfs(offsetof(VertexAttribs, vertex)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(VertexAttribs),
vbo_.getOfs(offsetof(VertexAttribs, texCoords)));
glEnableVertexAttribArray(2);
glVertexAttribIPointer(2, 1, GL_UNSIGNED_BYTE, sizeof(VertexAttribs),
vbo_.getOfs(offsetof(VertexAttribs, useTexAnim)));
glBufferSubData(GL_ARRAY_BUFFER, vbo_.getBase(),
sizeof(VertexAttribs)*vertexAttribs.size(),
vertexAttribs.data());
maxIndex_ = vertexAttribs.size()-1;
indexOfs_ = sizeof(VertexAttribs)*vertexAttribs.size();
numIndices_ = indices.size();
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, vbo_.getBase()+indexOfs_,
2*numIndices_,
indices.data());
mvp_ = glm::perspectiveFov(75.0f, static_cast<float>(renderer.getWidth()),
static_cast<float>(renderer.getHeight()),
1000.f, 800000.0f) *
glm::lookAt(glm::vec3(0.0f, 0.0f, 200000),
glm::vec3(0.0f, 0.0f, 0),
glm::vec3(0, 1, 0));
int maxVertices, maxIndices;
glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &maxIndices);
glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &maxVertices);
printf("Max recommened vertices: %d, indices: %d\n",
maxVertices, maxIndices);
}
void Object::draw()
{
glDisable(GL_CULL_FACE);
//glFrontFace(GL_CW);
glEnable(GL_DEPTH_TEST);
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
useProgram(program_);
glUniformMatrix4fv(0, 1, GL_FALSE,
glm::value_ptr(mvp_*glm::rotate(rot_, glm::vec3(1.0f, 0.0f, 0.0f))));
glUniform1i(1, 0);
glUniform1i(2, 1);
glBindTexture(GL_TEXTURE_2D, tex_);
if (texAnim_) {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D_ARRAY, texAnim_);
glActiveTexture(GL_TEXTURE0);
glUniform1ui(3, animFrame_);
}
glBindVertexArray(vertexArray_);
glDrawRangeElements(GL_TRIANGLES, 0, maxIndex_,
numIndices_, GL_UNSIGNED_SHORT, reinterpret_cast<void*>(indexOfs_));
}
}