#include #include #include #include #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]); } }; struct TexAtlasInfo { unsigned x, y; unsigned alignWidth, alignHeight; float xofs, yofs, xscale, yscale; ObjDecoder::Texture const& tex; }; std::tuple, 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); // If the atlas gets too large we may have problems with texture coordinate precision if (maxTex > 8192) maxTex = 8192; unsigned minSize = std::numeric_limits::max(); unsigned accumWidth = 0, maxHeight = 0; for (auto& tex : texs) { minSize = std::min(minSize, std::min(tex.width, tex.height)); accumWidth += tex.width; maxHeight = std::max(maxHeight, tex.height); } unsigned minLg = ilog2(minSize), minPow2 = (1< ret; unsigned xpos = 0, ypos = 0, lineMaxHeight = 0, maxWidth = 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 > accumWidth2) { ypos += lineMaxHeight; maxWidth = std::max(maxWidth, xpos); lineMaxHeight = 0; xpos = 0; } if (ypos + alignHeight > static_cast(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(lineMaxHeight, alignHeight); xpos += alignWidth; } unsigned atlasWidth = std::max(xpos, maxWidth), atlasHeight = ypos+lineMaxHeight; printf("Texture atlas size: %ux%u\n", atlasWidth, atlasHeight); TextureResource tex = create2DTexture(atlasWidth, atlasHeight, true, minLg+1); std::vector pixels(atlasWidth*atlasHeight*4); // 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]; } } if (col == 0xff) { pixels[(y+info.y)*atlasWidth*4+(info.x+x)*4] = 0u; pixels[(y+info.y)*atlasWidth*4+(info.x+x)*4+1] = 0u; pixels[(y+info.y)*atlasWidth*4+(info.x+x)*4+2] = 0u; pixels[(y+info.y)*atlasWidth*4+(info.x+x)*4+3] = 0u; } else { pixels[(y+info.y)*atlasWidth*4+(info.x+x)*4] = palt[col*3+2]; //vgaPalette[col]>>16; pixels[(y+info.y)*atlasWidth*4+(info.x+x)*4+1] = palt[col*3+1]; //vgaPalette[col]>>8; pixels[(y+info.y)*atlasWidth*4+(info.x+x)*4+2] = palt[col*3]; //vgaPalette[col]; pixels[(y+info.y)*atlasWidth*4+(info.x+x)*4+3] = 255u; } } } } glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, atlasWidth, atlasHeight, GL_BGRA, 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 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, true); 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 pixels(texAnim.frames*width*height*4); 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]; if (col == 0xff) { pixels[f*width*height*4+y*width*4+x*4] = 0u; pixels[f*width*height*4+y*width*4+x*4+1] = 0u; pixels[f*width*height*4+y*width*4+x*4+2] = 0u; pixels[f*width*height*4+y*width*4+x*4+3] = 0u; } else { pixels[f*width*height*4+y*width*4+x*4] = palt[col*3+2]; pixels[f*width*height*4+y*width*4+x*4+1] = palt[col*3+1]; pixels[f*width*height*4+y*width*4+x*4+2] = palt[col*3]; pixels[f*width*height*4+y*width*4+x*4+3] = 255u; } } } } glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, texAnim.frames, GL_BGRA, GL_UNSIGNED_BYTE, pixels.data()); glGenerateMipmap(GL_TEXTURE_2D_ARRAY); return std::make_tuple(animInfo, std::move(tex)); } std::tuple, std::vector > genVertexAttribs(ObjDecoder& obj, std::vector& atlasInfo, AnimTexInfo *animTex = nullptr) { auto tris = obj.getTriangles(); auto quads = obj.getQuads(); // Deduplicate vertex attributes, track indices std::map vertexAttribsMap; std::vector vertexAttribs; std::vector 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) : TransformDrawable(renderer), vbo_(), rot_(0.0f), animFrame_(0) { // Acquire shader program_ = ProgramProvider::getInstance().getProgram("object", "object"); // Generate texture atlas auto& texs = obj.getTextures(); std::vector atlasInfo; if (texs.size() > 0) { std::tie(atlasInfo, tex_) = genTexAtlas(texs, palt.getPalette()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, static_cast(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(GL_LINEAR_MIPMAP_LINEAR)); } // Generate vertex attribute array std::vector vertexAttribs; std::vector 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()); vp_ = glm::perspectiveFov(75.0f, static_cast(renderer.getWidth()), static_cast(renderer.getHeight()), 7500.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(vp_)); glUniformMatrix4fv(1, 1, GL_FALSE, glm::value_ptr(transformMatrix_)); glUniform1i(2, 0); glUniform1i(3, 1); glBindTexture(GL_TEXTURE_2D, tex_); if (texAnim_) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D_ARRAY, texAnim_); glActiveTexture(GL_TEXTURE0); glUniform1ui(4, animFrame_); } glBindVertexArray(vertexArray_); glDrawRangeElements(GL_TRIANGLES, 0, maxIndex_, numIndices_, GL_UNSIGNED_SHORT, vbo_.getOfs(indexOfs_)); } }