diff --git a/bsputil/bsputil.cc b/bsputil/bsputil.cc index 9b5a78b3..258592c6 100644 --- a/bsputil/bsputil.cc +++ b/bsputil/bsputil.cc @@ -40,6 +40,7 @@ #include // std::sort #include #include +#include // TODO settings::common_settings bsputil_options; @@ -709,7 +710,8 @@ int bsputil_main(int argc, char **argv) "[--decompile] [--decompile-geomonly] [--decompile-hull n]\n" "[--extract-bspx-lump lump_name output_file_name]\n" "[--insert-bspx-lump lump_name input_file_name]\n" - "[--remove-bspx-lump lump_name] bspfile/mapfile\n"); + "[--remove-bspx-lump lump_name]\n" + "[--svg] bspfile/mapfile\n"); exit(1); } @@ -735,7 +737,205 @@ int bsputil_main(int argc, char **argv) } for (int32_t i = 1; i < argc - 1; i++) { - if (!strcmp(argv[i], "--scale")) { + if (!strcmp(argv[i], "--svg")) { + + fs::path svg = fs::path(source).replace_extension(".svg"); + std::ofstream f(svg, std::ios_base::out); + + f << R"()" << std::endl; + f << R"()" << std::endl; + + auto &bsp = std::get(bspdata.bsp); + + img::load_textures(&bsp, {}); + + struct rendered_faces_t + { + std::vector faces; + qvec3f origin; + aabb3f bounds; + }; + + std::vector faces; + aabb3f total_bounds; + size_t total_faces = 0; + auto ents = EntData_Parse(bsp); + + auto addSubModel = [&bsp, &faces, &total_bounds, &total_faces](int32_t index, qvec3f origin) { + auto &model = bsp.dmodels[index]; + rendered_faces_t f { + {}, + origin + }; + + std::vector face_ids; + face_ids.reserve(model.numfaces); + + for (size_t i = model.firstface; i < model.firstface + model.numfaces; i++) + { + auto &face = bsp.dfaces[i]; + + if (face.texinfo == -1) + continue; + + auto &texinfo = bsp.texinfo[face.texinfo]; + + if (texinfo.flags.is_nodraw) + continue; + // TODO + //else if (texinfo.flags.native & Q2_SURF_SKY) + // continue; + else if (!Q_strcasecmp(Face_TextureName(&bsp, &face), "trigger")) + continue; + + auto norm = Face_Normal(&bsp, &face); + + if (qv::dot(qvec3d(0, 0, 1), norm) <= DEFAULT_ON_EPSILON) + continue; + + face_ids.push_back(i); + } + + std::sort(face_ids.begin(), face_ids.end(), [&bsp](size_t a, size_t b) { + float za = std::numeric_limits::lowest(); + float zb = za; + auto &facea = bsp.dfaces[a]; + auto &faceb = bsp.dfaces[b]; + + for (size_t e = 0; e < facea.numedges; e++) + za = std::max(za, Face_PointAtIndex(&bsp, &facea, e)[2]); + + for (size_t e = 0; e < faceb.numedges; e++) + zb = std::max(zb, Face_PointAtIndex(&bsp, &faceb, e)[2]); + + return za < zb; + }); + + for (auto &face_index : face_ids) + { + const auto &face = bsp.dfaces[face_index]; + f.faces.push_back(&face); + + for (auto pt : Face_Points(&bsp, &face)) + f.bounds += f.origin + pt; + } + + if (f.faces.empty()) + return; + + total_bounds += f.bounds; + total_faces += f.faces.size(); + faces.emplace_back(std::move(f)); + }; + + addSubModel(0, {}); + + for (auto &entity : ents) + { + if (!entity.has("model")) + continue; + + qvec3f origin {}; + int32_t model = atoi(entity.get("model").substr(1).c_str()); + + if (entity.has("origin")) + entity.get_vector("origin", origin); + + addSubModel(model, origin); + } + + total_bounds = total_bounds.grow(32); + + float xo = total_bounds.mins()[0]; + float yo = total_bounds.mins()[1]; + float zo = total_bounds.mins()[2]; + + float xs = total_bounds.maxs()[0] - xo; + float ys = total_bounds.maxs()[1] - yo; + float zs = total_bounds.maxs()[2] - zo; + + fmt::print(f, R"()", xs, ys); + f << std::endl; + + f << R"()" << std::endl; + + struct face_id_t + { + size_t model; + size_t face; + }; + std::vector face_ids; + face_ids.reserve(total_faces); + + for (size_t i = 0; i < faces.size(); i++) + for (size_t f = 0; f < faces[i].faces.size(); f++) + face_ids.emplace_back(i, f); + + std::sort(face_ids.begin(), face_ids.end(), [&bsp, &faces, yo](face_id_t a, face_id_t b) { + float za = yo; + float zb = yo; + auto facea = faces[a.model].faces[a.face]; + auto faceb = faces[b.model].faces[b.face]; + + for (size_t e = 0; e < facea->numedges; e++) + za = std::max(za, Face_PointAtIndex(&bsp, facea, e)[2] + faces[a.model].origin[2]); + + for (size_t e = 0; e < faceb->numedges; e++) + zb = std::max(zb, Face_PointAtIndex(&bsp, faceb, e)[2] + faces[b.model].origin[2]); + + return za < zb; + }); + + float low_z = total_bounds.maxs()[2], high_z = total_bounds.mins()[2]; + + for (auto &face_index : face_ids) + { + auto face = faces[face_index.model].faces[face_index.face]; + + for (auto &pt : Face_Points(&bsp, face)) + { + low_z = std::min(low_z, pt[2] + faces[face_index.model].origin[2]); + high_z = std::max(high_z, pt[2] + faces[face_index.model].origin[2]); + } + } + + for (auto &face_index : face_ids) + { + auto face = faces[face_index.model].faces[face_index.face]; + auto pts = Face_Points(&bsp, face); + std::string pts_str; + float nz = xo; + + for (auto &pt : pts) + { + std::format_to(std::back_inserter(pts_str), "{},{} ", (pt[0] + faces[face_index.model].origin[0]) - xo, ys - ((pt[1] + faces[face_index.model].origin[1]) - yo)); + nz = std::max(nz, pt[2] + faces[face_index.model].origin[2]); + } + + float z_scale = (nz - low_z) / (high_z - low_z); + float d = (0.5 + (z_scale * 0.5)); + qvec3b color { 255, 255, 255 }; + + const char *tex = Face_TextureName(&bsp, face); + + if (tex) + { + if (auto texptr = img::find(tex)) + color = texptr->averageColor; + } + + fmt::print(f, R"svg()svg", pts_str, color[0] * d, color[1] * d, color[2] * d); + f << std::endl; + } + + f << R"()" << std::endl; + + f << R"()" << std::endl; + f << R"()" << std::endl; + + f << R"()" << std::endl; + + } else if (!strcmp(argv[i], "--scale")) { i++; if (i == argc - 1) { diff --git a/lightpreview/mainwindow.cpp b/lightpreview/mainwindow.cpp index 89dd1f75..e58976c4 100644 --- a/lightpreview/mainwindow.cpp +++ b/lightpreview/mainwindow.cpp @@ -283,39 +283,39 @@ void MainWindow::createPropertiesSidebar() // setup event handlers connect(reload_button, &QAbstractButton::clicked, this, &MainWindow::reload); - connect(lightmap_only, &QAbstractButton::toggled, this, [=](bool checked) { glView->setLighmapOnly(checked); }); - connect(fullbright, &QAbstractButton::toggled, this, [=](bool checked) { glView->setFullbright(checked); }); - connect(normals, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawNormals(checked); }); - connect(showtris, &QAbstractButton::toggled, this, [=](bool checked) { glView->setShowTris(checked); }); + connect(lightmap_only, &QAbstractButton::toggled, this, [this](bool checked) { glView->setLighmapOnly(checked); }); + connect(fullbright, &QAbstractButton::toggled, this, [this](bool checked) { glView->setFullbright(checked); }); + connect(normals, &QAbstractButton::toggled, this, [this](bool checked) { glView->setDrawNormals(checked); }); + connect(showtris, &QAbstractButton::toggled, this, [this](bool checked) { glView->setShowTris(checked); }); connect(showtris_seethrough, &QAbstractButton::toggled, this, - [=](bool checked) { glView->setShowTrisSeeThrough(checked); }); - connect(visculling, &QAbstractButton::toggled, this, [=](bool checked) { + [this](bool checked) { glView->setShowTrisSeeThrough(checked); }); + connect(visculling, &QAbstractButton::toggled, this, [this, keepcullposition, keepcullfrustum](bool checked) { glView->setVisCulling(checked); keepcullposition->setEnabled(checked); keepcullfrustum->setEnabled(keepcullposition->isEnabled()); }); - connect(keepcullposition, &QAbstractButton::toggled, this, [=](bool checked) { + connect(keepcullposition, &QAbstractButton::toggled, this, [this, keepcullfrustum](bool checked) { glView->setKeepCullOrigin(checked); keepcullfrustum->setEnabled(checked); }); - connect(keepcullfrustum, &QAbstractButton::toggled, this, [=](bool checked) { glView->setKeepCullFrustum(checked); }); - connect(drawflat, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawFlat(checked); }); - connect(hull0, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawLeafs(checked ? std::optional{0} : std::nullopt); }); - connect(hull1, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawLeafs(checked ? std::optional{1} : std::nullopt); }); - connect(hull2, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawLeafs(checked ? std::optional{2} : std::nullopt); }); - connect(hull3, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawLeafs(checked ? std::optional{3} : std::nullopt); }); - connect(hull4, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawLeafs(checked ? std::optional{4} : std::nullopt); }); - connect(hull5, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawLeafs(checked ? std::optional{5} : std::nullopt); }); - connect(drawportals, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawPortals(checked); }); - connect(drawleak, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawLeak(checked); }); - connect(keepposition, &QAbstractButton::toggled, this, [=](bool checked) { glView->setKeepOrigin(checked); }); + connect(keepcullfrustum, &QAbstractButton::toggled, this, [this](bool checked) { glView->setKeepCullFrustum(checked); }); + connect(drawflat, &QAbstractButton::toggled, this, [this](bool checked) { glView->setDrawFlat(checked); }); + connect(hull0, &QAbstractButton::toggled, this, [this](bool checked) { glView->setDrawLeafs(checked ? std::optional{0} : std::nullopt); }); + connect(hull1, &QAbstractButton::toggled, this, [this](bool checked) { glView->setDrawLeafs(checked ? std::optional{1} : std::nullopt); }); + connect(hull2, &QAbstractButton::toggled, this, [this](bool checked) { glView->setDrawLeafs(checked ? std::optional{2} : std::nullopt); }); + connect(hull3, &QAbstractButton::toggled, this, [this](bool checked) { glView->setDrawLeafs(checked ? std::optional{3} : std::nullopt); }); + connect(hull4, &QAbstractButton::toggled, this, [this](bool checked) { glView->setDrawLeafs(checked ? std::optional{4} : std::nullopt); }); + connect(hull5, &QAbstractButton::toggled, this, [this](bool checked) { glView->setDrawLeafs(checked ? std::optional{5} : std::nullopt); }); + connect(drawportals, &QAbstractButton::toggled, this, [this](bool checked) { glView->setDrawPortals(checked); }); + connect(drawleak, &QAbstractButton::toggled, this, [this](bool checked) { glView->setDrawLeak(checked); }); + connect(keepposition, &QAbstractButton::toggled, this, [this](bool checked) { glView->setKeepOrigin(checked); }); connect(nearest, &QAbstractButton::toggled, this, - [=](bool checked) { glView->setMagFilter(checked ? QOpenGLTexture::Nearest : QOpenGLTexture::Linear); }); + [this](bool checked) { glView->setMagFilter(checked ? QOpenGLTexture::Nearest : QOpenGLTexture::Linear); }); connect(draw_opaque, &QAbstractButton::toggled, this, - [=](bool checked) { glView->setDrawTranslucencyAsOpaque(checked); }); + [this](bool checked) { glView->setDrawTranslucencyAsOpaque(checked); }); connect(glView, &GLView::cameraMoved, this, &MainWindow::displayCameraPositionInfo); connect(show_bmodels, &QAbstractButton::toggled, this, - [=](bool checked) { glView->setShowBmodels(checked); }); + [this](bool checked) { glView->setShowBmodels(checked); }); // set up load timer m_fileReloadTimer = std::make_unique(); @@ -476,7 +476,7 @@ void MainWindow::showEvent(QShowEvent *event) // FIXME: support more command-line options? auto args = QCoreApplication::arguments(); if (args.size() == 2) { - QTimer::singleShot(0, this, [=] { loadFile(args.at(1)); }); + QTimer::singleShot(0, this, [this, args] { loadFile(args.at(1)); }); } }