#include #include #include #include #include #include #include #include #define bytes(value) reinterpret_cast(value) #pragma pack(push, 1) struct BMPFileHeader { uint16_t bfType; uint32_t bfSize; uint16_t bfReserved1; uint16_t bfReserved2; uint32_t bfOffBits; }; struct BMPInfoHeader { uint32_t biSize; int32_t biWidth; int32_t biHeight; uint16_t biPlanes; uint16_t biBitCount; uint32_t biCompression; uint32_t biSizeImage; int32_t biXPelsPerMeter; int32_t biYPelsPerMeter; uint32_t biClrUsed; uint32_t biClrImportant; }; struct BGR { uint8_t b,g,r; }; #pragma pack(pop) struct vertex { float x,y; }; struct BGRHash { size_t operator()(const BGR& c) const noexcept { return static_cast(c.r) << 16 | static_cast(c.g) << 8 | static_cast(c.b); } }; bool operator==(const BGR&a,const BGR&b){ return a.r==b.r && a.g==b.g && a.b==b.b; } BGR EMPTY_COLOR{255,255,255}; uint8_t* imageData; uint8_t* tempImageData; size_t width,height,rowSize; struct prov { std::vector vertices; std::vector indices; }; std::unordered_map,BGRHash> pixelsPerProvincesPerState; inline BGR getPixelSafe(size_t r,size_t c){ if(r>=height||c>=width) return EMPTY_COLOR; return *reinterpret_cast( imageData + r*rowSize + c*3 ); } inline BGR getPixelSafeIndex(size_t i){ return getPixelSafe(i/width,i%width); } /* ================= FLOOD FILL ================= */ void FloodFill(){ size_t size=width*height; for(size_t i=0;i(tempImageData + (i/width)*rowSize + (i%width)*3)==EMPTY_COLOR) continue; BGR color=*reinterpret_cast(tempImageData + (i/width)*rowSize + (i%width)*3); std::vector prov; std::queue q; q.push(i); while(!q.empty()){ size_t n=q.front(); q.pop(); BGR& px=*reinterpret_cast(tempImageData + (n/width)*rowSize + (n%width)*3); if(px!=color) continue; px=EMPTY_COLOR; prov.push_back(n); if(n>=width) q.push(n-width); if(n+width&prov,BGR id){ for(size_t i:prov){ size_t r=i/width,c=i%width; if(getPixelSafe(r-1,c)!=id|| getPixelSafe(r+1,c)!=id|| getPixelSafe(r,c-1)!=id|| getPixelSafe(r,c+1)!=id) return i; } return std::numeric_limits::max(); } void FindContour(){ for(auto&[state,provs]:pixelsPerProvincesPerState){ for(auto&prov:provs){ if(prov.vertices.empty()) continue; BGR id=getPixelSafeIndex(prov.vertices[0]); size_t start=FindStart(prov.vertices,id); if(start==std::numeric_limits::max()) continue; std::vector contour; contour.push_back(start); size_t cur=start; size_t prev = (start % width > 0) ? start - 1 : start + 1; const size_t MAX=prov.vertices.size()*8; for(size_t i=0;i keep_point(n, true); std::stack> st; // в стек кладем первый и последний индекс st.push({0, n - 1}); while(!st.empty()) { auto [startIndex, endIndex] = st.top(); st.pop(); double dMax = 0.0; size_t index = startIndex; for(size_t i = startIndex + 1; i < endIndex; ++i) { if(keep_point[i]) { double d = PerpendicularDistance( prov.vertices[i], prov.vertices[startIndex], prov.vertices[endIndex], width ); if(d > dMax) { dMax = d; index = i; } } } if(dMax >= epsilon) { st.push({startIndex, index}); st.push({index, endIndex}); } else { for(size_t j = startIndex + 1; j < endIndex; ++j) keep_point[j] = false; } } // формируем итоговый список std::vector result; result.reserve(n); for(size_t i = 0; i < n; ++i) { if(keep_point[i]) result.push_back(prov.vertices[i]); } prov.vertices.swap(result); } } } struct Pt { int x, y; size_t id; }; inline Pt make_pt(size_t i, size_t width) { return { int(i % width), int(i / width), i }; } inline long long cross(const Pt& a, const Pt& b, const Pt& c) { return 1LL * (b.x - a.x) * (c.y - a.y) - 1LL * (b.y - a.y) * (c.x - a.x); } long long signed_area2(const std::vector& p) { long long a = 0; for (size_t i = 0; i < p.size(); ++i) { const auto& p0 = p[i]; const auto& p1 = p[(i + 1) % p.size()]; a += 1LL * p0.x * p1.y - 1LL * p1.x * p0.y; } return a; } bool point_in_triangle(const Pt& p, const Pt& a, const Pt& b, const Pt& c) { long long c1 = cross(a, b, p); long long c2 = cross(b, c, p); long long c3 = cross(c, a, p); return (c1 >= 0 && c2 >= 0 && c3 >= 0); } std::vector triangulate_prov(const std::vector& prov, size_t width) { std::vector poly; poly.reserve(prov.size()); for (size_t i : prov) poly.push_back(make_pt(i, width)); // CCW if (signed_area2(poly) < 0) std::reverse(poly.begin(), poly.end()); std::vector out; // a,b,c,a,b,c,... while (poly.size() > 3) { bool ear_found = false; for (size_t i = 0; i < poly.size(); ++i) { size_t i0 = (i + poly.size() - 1) % poly.size(); size_t i1 = i; size_t i2 = (i + 1) % poly.size(); const Pt& a = poly[i0]; const Pt& b = poly[i1]; const Pt& c = poly[i2]; if (cross(a, b, c) <= 0) continue; // не выпуклая bool inside = false; for (size_t j = 0; j < poly.size(); ++j) { if (j == i0 || j == i1 || j == i2) continue; if (point_in_triangle(poly[j], a, b, c)) { inside = true; break; } } if (!inside) { out.push_back(a.id); out.push_back(b.id); out.push_back(c.id); poly.erase(poly.begin() + i1); ear_found = true; break; } } if (!ear_found) { // плохой контур (самопересечение / коллинеарность) break; } } if (poly.size() == 3) { out.push_back(poly[0].id); out.push_back(poly[1].id); out.push_back(poly[2].id); } return out; } void Triangulate() { for(auto& [state, provs] : pixelsPerProvincesPerState) { for(prov& prov : provs) { if (prov.vertices.size() < 3) continue; prov.vertices = triangulate_prov(prov.vertices, width); } } } void Optimize() { for(auto& [state, provs] : pixelsPerProvincesPerState) { for(prov& prov : provs) { std::unordered_map vertexToIndex; vertexToIndex.reserve(prov.vertices.size()); std::vector uniqueVertices; uniqueVertices.reserve(prov.vertices.size()); prov.indices.clear(); for (size_t v : prov.vertices) { auto it = vertexToIndex.find(v); if (it != vertexToIndex.end()) { // вершина уже есть prov.indices.push_back(it->second); } else { // новая вершина uint16_t newIndex = static_cast(uniqueVertices.size()); uniqueVertices.push_back(v); vertexToIndex[v] = newIndex; prov.indices.push_back(newIndex); } } prov.vertices.swap(uniqueVertices); } } } /* ================= OUTPUT ================= */ void GenVertices(std::ofstream&out){ uint32_t statesCount = static_cast(pixelsPerProvincesPerState.size()); out.write(bytes(&statesCount),4); for(auto&[color, provs]:pixelsPerProvincesPerState) { out.write(bytes(&color), sizeof(BGR)); uint32_t provsCount = static_cast(provs.size()); out.write(bytes(&provsCount), sizeof(uint32_t)); for(prov& prov:provs) { //Skip invalid meshes if (prov.indices.size() % 3 != 0) { prov.vertices.clear(); prov.indices.clear(); } uint32_t verticesCount = static_cast(prov.vertices.size()); out.write(bytes(&verticesCount), sizeof(uint32_t)); for(size_t i: prov.vertices) { vertex v{ static_cast(i%width), static_cast(i/width) }; out.write(bytes(&v),sizeof(v)); } uint32_t indicesCount = static_cast(prov.indices.size()); out.write(bytes(&indicesCount), sizeof(uint32_t)); out.write(reinterpret_cast(prov.indices.data()), prov.indices.size() * sizeof(uint16_t)); } } } /* ================= MAIN ================= */ int main(int argc,char**argv){ std::ifstream fin(argv[1],std::ios::binary); std::ofstream fout(argv[2],std::ios::binary); BMPFileHeader fh; BMPInfoHeader ih; fin.read((char*)&fh,sizeof(fh)); fin.read((char*)&ih,sizeof(ih)); width=ih.biWidth; height=ih.biHeight; rowSize=(width*3+3)&~3; std::vector img(rowSize*height); std::vector tmp(rowSize*height); fin.seekg(fh.bfOffBits); fin.read((char*)img.data(),img.size()); fin.seekg(fh.bfOffBits); fin.read((char*)tmp.data(),tmp.size()); imageData=img.data(); tempImageData=tmp.data(); FloodFill(); FindContour(); Aproximate(1); Triangulate(); Optimize(); GenVertices(fout); }