maputil starting point
This commit is contained in:
parent
b893e67309
commit
cec120dfc9
|
|
@ -122,6 +122,7 @@ add_subdirectory(bsputil)
|
|||
add_subdirectory(light)
|
||||
add_subdirectory(qbsp)
|
||||
add_subdirectory(vis)
|
||||
add_subdirectory(maputil)
|
||||
|
||||
option(DISABLE_TESTS "Disables Tests" OFF)
|
||||
option(DISABLE_DOCS "Disables Docs" OFF)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ add_library(common STATIC
|
|||
imglib.cc
|
||||
settings.cc
|
||||
prtfile.cc
|
||||
mapfile.cc
|
||||
debugger.natvis
|
||||
../include/common/aabb.hh
|
||||
../include/common/aligned_allocator.hh
|
||||
|
|
@ -48,6 +49,7 @@ add_library(common STATIC
|
|||
../include/common/prtfile.hh
|
||||
../include/common/vectorutils.hh
|
||||
../include/common/ostream.hh
|
||||
../include/common/mapfile.hh
|
||||
)
|
||||
|
||||
target_link_libraries(common ${CMAKE_THREAD_LIBS_INIT} TBB::tbb TBB::tbbmalloc fmt::fmt nlohmann_json::nlohmann_json pareto)
|
||||
|
|
|
|||
|
|
@ -973,7 +973,7 @@ struct gamedef_hl_t : public gamedef_q1_like_t<GAME_HALF_LIFE>
|
|||
struct gamedef_q2_t : public gamedef_t
|
||||
{
|
||||
gamedef_q2_t()
|
||||
: gamedef_t("BASEQ2")
|
||||
: gamedef_t("baseq2")
|
||||
{
|
||||
this->id = GAME_QUAKE_II;
|
||||
has_rgb_lightmap = true;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,729 @@
|
|||
#include <common/mapfile.hh>
|
||||
#include <common/log.hh>
|
||||
#include <common/ostream.hh>
|
||||
#include <utility>
|
||||
|
||||
/*static*/ bool brush_side_t::is_valid_texture_projection(const qvec3f &faceNormal, const qvec3f &s_vec, const qvec3f &t_vec)
|
||||
{
|
||||
// TODO: This doesn't match how light does it (TexSpaceToWorld)
|
||||
|
||||
const qvec3f tex_normal = qv::normalize(qv::cross(s_vec, t_vec));
|
||||
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
if (std::isnan(tex_normal[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const float cosangle = qv::dot(tex_normal, faceNormal);
|
||||
|
||||
if (std::isnan(cosangle)) {
|
||||
return false;
|
||||
} else if (fabs(cosangle) < ZERO_EPSILON) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void brush_side_t::validate_texture_projection()
|
||||
{
|
||||
if (!is_valid_texture_projection()) {
|
||||
/*
|
||||
if (qbsp_options.verbose.value()) {
|
||||
logging::print("WARNING: {}: repairing invalid texture projection (\"{}\" near {} {} {})\n", mapface.line,
|
||||
mapface.texname, (int)mapface.planepts[0][0], (int)mapface.planepts[0][1], (int)mapface.planepts[0][2]);
|
||||
} else {
|
||||
issue_stats.num_repaired++;
|
||||
}
|
||||
*/
|
||||
|
||||
// Reset texturing to sensible defaults
|
||||
set_texinfo(texdef_quake_ed_t {
|
||||
{ 0.0, 0.0 },
|
||||
0,
|
||||
{ 1.0, 1.0 }
|
||||
});
|
||||
|
||||
Q_assert(is_valid_texture_projection());
|
||||
}
|
||||
}
|
||||
|
||||
/*static*/ texdef_bp_t brush_side_t::parse_bp(parser_t &parser)
|
||||
{
|
||||
qmat<vec_t, 2, 3> texMat;
|
||||
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
|
||||
if (parser.token != "(") {
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
if (parser.token != "(") {
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < 3; j++) {
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
texMat.at(i, j) = std::stod(parser.token);
|
||||
}
|
||||
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
|
||||
if (parser.token != ")") {
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
|
||||
if (parser.token != ")") {
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
return { texMat };
|
||||
|
||||
parse_error:
|
||||
FError("{}: couldn't parse Brush Primitives texture info", parser.location);
|
||||
}
|
||||
|
||||
/*static*/ texdef_valve_t brush_side_t::parse_valve_220(parser_t &parser)
|
||||
{
|
||||
qmat<vec_t, 2, 3> axis;
|
||||
qvec2d shift, scale;
|
||||
vec_t rotate;
|
||||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
|
||||
if (parser.token != "[") {
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < 3; j++) {
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
axis.at(i, j) = std::stod(parser.token);
|
||||
}
|
||||
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
shift[i] = std::stod(parser.token);
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
|
||||
if (parser.token != "]") {
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
rotate = std::stod(parser.token);
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
scale[0] = std::stod(parser.token);
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
scale[1] = std::stod(parser.token);
|
||||
|
||||
return {
|
||||
shift,
|
||||
rotate,
|
||||
scale,
|
||||
axis
|
||||
};
|
||||
|
||||
parse_error:
|
||||
FError("{}: couldn't parse Valve220 texture info", parser.location);
|
||||
}
|
||||
|
||||
/*static*/ texdef_quake_ed_t brush_side_t::parse_quake_ed(parser_t &parser)
|
||||
{
|
||||
qvec2d shift, scale;
|
||||
vec_t rotate;
|
||||
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
shift[0] = std::stod(parser.token);
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
shift[1] = std::stod(parser.token);
|
||||
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
rotate = std::stod(parser.token);
|
||||
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
scale[0] = std::stod(parser.token);
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
scale[1] = std::stod(parser.token);
|
||||
|
||||
return {
|
||||
shift,
|
||||
rotate,
|
||||
scale
|
||||
};
|
||||
}
|
||||
|
||||
bool brush_side_t::parse_quark_comment(parser_t &parser)
|
||||
{
|
||||
if (!parser.parse_token(PARSE_COMMENT | PARSE_OPTIONAL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parser.token.length() < 5 || strncmp(parser.token.c_str(), "//TX", 4)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// QuArK TX modes can only exist on Quaked-style maps
|
||||
Q_assert(style == texcoord_style_t::quaked);
|
||||
style = texcoord_style_t::etp;
|
||||
|
||||
if (parser.token[4] == '1') {
|
||||
raw = texdef_etp_t { std::get<texdef_quake_ed_t>(raw), false };
|
||||
} else if (parser.token[4] == '2') {
|
||||
raw = texdef_etp_t { std::get<texdef_quake_ed_t>(raw), true };
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void brush_side_t::parse_extended_texinfo(parser_t &parser)
|
||||
{
|
||||
if (!parse_quark_comment(parser)) {
|
||||
// Parse extra Quake 2 surface info
|
||||
if (parser.parse_token(PARSE_OPTIONAL)) {
|
||||
texinfo_quake2_t q2_info;
|
||||
|
||||
q2_info.contents = {std::stoi(parser.token)};
|
||||
|
||||
if (parser.parse_token(PARSE_OPTIONAL)) {
|
||||
q2_info.flags.native = std::stoi(parser.token);
|
||||
}
|
||||
if (parser.parse_token(PARSE_OPTIONAL)) {
|
||||
q2_info.value = std::stoi(parser.token);
|
||||
}
|
||||
|
||||
extended_info = q2_info;
|
||||
|
||||
parse_quark_comment(parser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void brush_side_t::set_texinfo(const texdef_quake_ed_t &texdef)
|
||||
{
|
||||
texture_axis_t axis(plane);
|
||||
qvec3d vectors[2] = {
|
||||
axis.xv,
|
||||
axis.yv
|
||||
};
|
||||
|
||||
/* Rotate axis */
|
||||
vec_t ang = texdef.rotate / 180.0 * Q_PI;
|
||||
vec_t sinv = sin(ang);
|
||||
vec_t cosv = cos(ang);
|
||||
|
||||
size_t sv, tv;
|
||||
|
||||
if (vectors[0][0]) {
|
||||
sv = 0;
|
||||
} else if (vectors[0][1]) {
|
||||
sv = 1;
|
||||
} else {
|
||||
sv = 2; // unreachable, due to TextureAxisFromPlane lookup table
|
||||
}
|
||||
|
||||
if (vectors[1][0]) {
|
||||
tv = 0; // unreachable, due to TextureAxisFromPlane lookup table
|
||||
} else if (vectors[1][1]) {
|
||||
tv = 1;
|
||||
} else {
|
||||
tv = 2;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
vec_t ns = cosv * vectors[i][sv] - sinv * vectors[i][tv];
|
||||
vec_t nt = sinv * vectors[i][sv] + cosv * vectors[i][tv];
|
||||
vectors[i][sv] = ns;
|
||||
vectors[i][tv] = nt;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
for (size_t j = 0; j < 3; j++) {
|
||||
/* Interpret zero scale as no scaling */
|
||||
vecs.at(i, j) = vectors[i][j] / (texdef.scale[i] ? texdef.scale[i] : 1);
|
||||
}
|
||||
}
|
||||
|
||||
vecs.at(0, 3) = texdef.shift[0];
|
||||
vecs.at(1, 3) = texdef.shift[1];
|
||||
|
||||
// TODO: move these self-tests somewhere else, do them for all types
|
||||
#if 0
|
||||
if (false) {
|
||||
// Self-test of SetTexinfo_QuakeEd_New
|
||||
texvecf check;
|
||||
SetTexinfo_QuakeEd_New(plane, shift, rotate, scale, check);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
if (fabs(check.at(i, j) - out->vecs.at(i, j)) > 0.001) {
|
||||
SetTexinfo_QuakeEd_New(plane, shift, rotate, scale, check);
|
||||
FError("fail");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (false) {
|
||||
// Self-test of TexDef_BSPToQuakeEd
|
||||
texdef_quake_ed_t reversed = TexDef_BSPToQuakeEd(plane, std::nullopt, out->vecs, planepts);
|
||||
|
||||
if (!EqualDegrees(reversed.rotate, rotate)) {
|
||||
reversed.rotate += 180;
|
||||
reversed.scale[0] *= -1;
|
||||
reversed.scale[1] *= -1;
|
||||
}
|
||||
|
||||
if (!EqualDegrees(reversed.rotate, rotate)) {
|
||||
ewt::print("wrong rotate got {} expected {}\n", reversed.rotate, rotate);
|
||||
}
|
||||
|
||||
if (fabs(reversed.scale[0] - scale[0]) > 0.001 || fabs(reversed.scale[1] - scale[1]) > 0.001) {
|
||||
ewt::print("wrong scale, got {} {} exp {} {}\n", reversed.scale[0], reversed.scale[1], scale[0], scale[1]);
|
||||
}
|
||||
|
||||
if (fabs(reversed.shift[0] - shift[0]) > 0.1 || fabs(reversed.shift[1] - shift[1]) > 0.1) {
|
||||
ewt::print("wrong shift, got {} {} exp {} {}\n", reversed.shift[0], reversed.shift[1], shift[0], shift[1]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void brush_side_t::set_texinfo(const texdef_valve_t &texdef)
|
||||
{
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
vecs.at(0, i) = texdef.axis.at(0, i) / texdef.scale[0];
|
||||
vecs.at(1, i) = texdef.axis.at(1, i) / texdef.scale[1];
|
||||
}
|
||||
|
||||
vecs.at(0, 3) = texdef.shift[0];
|
||||
vecs.at(1, 3) = texdef.shift[1];
|
||||
}
|
||||
|
||||
void brush_side_t::set_texinfo(const texdef_etp_t &texdef)
|
||||
{
|
||||
qvec3d vectors[2];
|
||||
|
||||
/*
|
||||
* Type 1 uses vecs[0] = (pt[2] - pt[0]) and vecs[1] = (pt[1] - pt[0])
|
||||
* Type 2 reverses the order of the vecs
|
||||
* 128 is the scaling factor assumed by QuArK.
|
||||
*/
|
||||
if (!texdef.tx2) {
|
||||
vectors[0] = planepts[2] - planepts[0];
|
||||
vectors[1] = planepts[1] - planepts[0];
|
||||
} else {
|
||||
vectors[0] = planepts[1] - planepts[0];
|
||||
vectors[1] = planepts[2] - planepts[0];
|
||||
}
|
||||
|
||||
vectors[0] *= 1.0 / 128.0;
|
||||
vectors[1] *= 1.0 / 128.0;
|
||||
|
||||
vec_t a = qv::dot(vectors[0], vectors[0]);
|
||||
vec_t b = qv::dot(vectors[0], vectors[1]);
|
||||
vec_t c = b; /* qv::dot(vectors[1], vectors[0]); */
|
||||
vec_t d = qv::dot(vectors[1], vectors[1]);
|
||||
|
||||
/*
|
||||
* Want to solve for out->vecs:
|
||||
*
|
||||
* | a b | | out->vecs[0] | = | vecs[0] |
|
||||
* | c d | | out->vecs[1] | | vecs[1] |
|
||||
*
|
||||
* => | out->vecs[0] | = __ 1.0__ | d -b | | vecs[0] |
|
||||
* | out->vecs[1] | a*d - b*c | -c a | | vecs[1] |
|
||||
*/
|
||||
vec_t determinant = a * d - b * c;
|
||||
if (fabs(determinant) < ZERO_EPSILON) {
|
||||
logging::print("WARNING: {}: Face with degenerate QuArK-style texture axes\n", location);
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
vecs.at(0, i) = vecs.at(1, i) = 0;
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
vecs.at(0, i) = (d * vectors[0][i] - b * vectors[1][i]) / determinant;
|
||||
vecs.at(1, i) = -(a * vectors[1][i] - c * vectors[0][i]) / determinant;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, the texture offset is indicated by planepts[0] */
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
vectors[0][i] = vecs.at(0, i);
|
||||
vectors[1][i] = vecs.at(1, i);
|
||||
}
|
||||
|
||||
vecs.at(0, 3) = -qv::dot(vectors[0], planepts[0]);
|
||||
vecs.at(1, 3) = -qv::dot(vectors[1], planepts[0]);
|
||||
}
|
||||
|
||||
void brush_side_t::set_texinfo(const texdef_bp_t &texdef)
|
||||
{
|
||||
#if 0
|
||||
const auto &texture = map.load_image_meta(mapface.texname.c_str());
|
||||
const int32_t width = texture ? texture->width : 64;
|
||||
const int32_t height = texture ? texture->height : 64;
|
||||
|
||||
SetTexinfo_BrushPrimitives(texMat, plane.normal, width, height, tx->vecs);
|
||||
#endif
|
||||
FError("todo BP");
|
||||
}
|
||||
|
||||
void brush_side_t::parse_texture_def(parser_t &parser, texcoord_style_t base_format)
|
||||
{
|
||||
if (base_format == texcoord_style_t::brush_primitives) {
|
||||
raw = parse_bp(parser);
|
||||
style = texcoord_style_t::brush_primitives;
|
||||
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
texture = std::move(parser.token);
|
||||
} else if (base_format == texcoord_style_t::quaked) {
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
texture = std::move(parser.token);
|
||||
|
||||
parser.parse_token(PARSE_SAMELINE | PARSE_PEEK);
|
||||
|
||||
if (parser.token == "[") {
|
||||
raw = parse_valve_220(parser);
|
||||
style = texcoord_style_t::valve_220;
|
||||
} else {
|
||||
raw = parse_quake_ed(parser);
|
||||
style = texcoord_style_t::quaked;
|
||||
}
|
||||
} else {
|
||||
FError("{}: Bad brush format", parser.location);
|
||||
}
|
||||
|
||||
// Read extra Q2 params and/or QuArK subtype
|
||||
parse_extended_texinfo(parser);
|
||||
|
||||
std::visit([this](auto &&x) { set_texinfo(x); }, raw);
|
||||
}
|
||||
|
||||
void brush_side_t::parse_plane_def(parser_t &parser)
|
||||
{
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
if (i != 0) {
|
||||
parser.parse_token();
|
||||
}
|
||||
|
||||
if (parser.token != "(") {
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < 3; j++) {
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
planepts[i][j] = std::stod(parser.token);
|
||||
}
|
||||
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
|
||||
if (parser.token != ")") {
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
parse_error:
|
||||
FError("{}: Invalid brush plane format", parser.location);
|
||||
}
|
||||
|
||||
void brush_side_t::write_extended_info(std::ostream &stream)
|
||||
{
|
||||
if (extended_info) {
|
||||
ewt::print(stream, " {} {} {}", extended_info->contents.native, extended_info->flags.native, extended_info->value);
|
||||
}
|
||||
}
|
||||
|
||||
void brush_side_t::write_texinfo(std::ostream &stream, const texdef_quake_ed_t &texdef)
|
||||
{
|
||||
ewt::print(stream, "{} {} {} {} {}", texdef.shift[0], texdef.shift[1], texdef.rotate, texdef.scale[0], texdef.scale[1]);
|
||||
write_extended_info(stream);
|
||||
}
|
||||
|
||||
void brush_side_t::write_texinfo(std::ostream &stream, const texdef_valve_t &texdef)
|
||||
{
|
||||
ewt::print(stream, "[ {} {} {} {} ] [ {} {} {} {} ] {} {} {}",
|
||||
texdef.axis.at(0, 0), texdef.axis.at(0, 1), texdef.axis.at(0, 2), texdef.shift[0],
|
||||
texdef.axis.at(1, 0), texdef.axis.at(1, 1), texdef.axis.at(1, 2), texdef.shift[1],
|
||||
texdef.rotate, texdef.scale[0], texdef.scale[1]);
|
||||
write_extended_info(stream);
|
||||
}
|
||||
|
||||
void brush_side_t::write_texinfo(std::ostream &stream, const texdef_etp_t &texdef)
|
||||
{
|
||||
write_texinfo(stream, (const texdef_quake_ed_t &) texdef);
|
||||
ewt::print(stream, "//TX{}", texdef.tx2 ? '2' : '1');
|
||||
}
|
||||
|
||||
void brush_side_t::write_texinfo(std::ostream &stream, const texdef_bp_t &texdef)
|
||||
{
|
||||
FError("todo bp");
|
||||
}
|
||||
|
||||
void brush_side_t::write(std::ostream &stream)
|
||||
{
|
||||
ewt::print(stream, "( {} {} {} ) ( {} {} {} ) ( {} {} {} ) {} ", planepts[0][0], planepts[0][1], planepts[0][2],
|
||||
planepts[1][0], planepts[1][1], planepts[1][2], planepts[2][0], planepts[2][1], planepts[2][2],
|
||||
texture);
|
||||
|
||||
std::visit([this, &stream](auto &&x) { write_texinfo(stream, x); }, raw);
|
||||
}
|
||||
|
||||
void brush_side_t::convert_to(texcoord_style_t style)
|
||||
{
|
||||
// we're already this style
|
||||
if (this->style == style) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->style = style;
|
||||
}
|
||||
|
||||
void brush_t::parse_brush_face(parser_t &parser, texcoord_style_t base_format)
|
||||
{
|
||||
brush_side_t side;
|
||||
|
||||
side.location = parser.location;
|
||||
|
||||
side.parse_plane_def(parser);
|
||||
|
||||
/* calculate the normal/dist plane equation */
|
||||
qvec3d ab = side.planepts[0] - side.planepts[1];
|
||||
qvec3d cb = side.planepts[2] - side.planepts[1];
|
||||
|
||||
vec_t length;
|
||||
qvec3d normal = qv::normalize(qv::cross(ab, cb), length);
|
||||
vec_t dist = qv::dot(side.planepts[1], normal);
|
||||
|
||||
side.plane = { normal, dist };
|
||||
|
||||
side.parse_texture_def(parser, base_format);
|
||||
|
||||
if (length < NORMAL_EPSILON) {
|
||||
logging::print("WARNING: {}: Brush plane with no normal\n", parser.location);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check for duplicate planes */
|
||||
for (auto &check : faces) {
|
||||
if (qv::epsilonEqual(check.plane, side.plane) ||
|
||||
qv::epsilonEqual(-check.plane, side.plane)) {
|
||||
logging::print("{}: Brush with duplicate plane\n", parser.location);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ericw -- round texture vector values that are within ZERO_EPSILON of integers,
|
||||
// to attempt to attempt to work around corrupted lightmap sizes in DarkPlaces
|
||||
// (it uses 32 bit precision in CalcSurfaceExtents)
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
for (size_t j = 0; j < 4; j++) {
|
||||
vec_t r = Q_rint(side.vecs.at(i, j));
|
||||
if (fabs(side.vecs.at(i, j) - r) < ZERO_EPSILON) {
|
||||
side.vecs.at(i, j) = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
side.validate_texture_projection();
|
||||
|
||||
faces.emplace_back(std::move(side));
|
||||
}
|
||||
|
||||
void brush_t::write(std::ostream &stream)
|
||||
{
|
||||
stream << "{\n";
|
||||
|
||||
if (base_format == texcoord_style_t::brush_primitives) {
|
||||
stream << "brushDef\n{\n";
|
||||
}
|
||||
|
||||
for (auto &face : faces) {
|
||||
face.write(stream);
|
||||
stream << "\n";
|
||||
}
|
||||
|
||||
if (base_format == texcoord_style_t::brush_primitives) {
|
||||
stream << "}\n";
|
||||
}
|
||||
|
||||
stream << "}\n";
|
||||
}
|
||||
|
||||
void brush_t::convert_to(texcoord_style_t style)
|
||||
{
|
||||
for (auto &face : faces) {
|
||||
face.convert_to(style);
|
||||
}
|
||||
|
||||
if (style == texcoord_style_t::brush_primitives) {
|
||||
base_format = style;
|
||||
} else {
|
||||
base_format = texcoord_style_t::quaked;
|
||||
}
|
||||
}
|
||||
|
||||
// map file stuff
|
||||
|
||||
void map_entity_t::parse_entity_dict(parser_t &parser)
|
||||
{
|
||||
std::string key = std::move(parser.token);
|
||||
|
||||
// trim whitespace from start/end
|
||||
while (std::isspace(key.front())) {
|
||||
key.erase(key.begin());
|
||||
}
|
||||
while (std::isspace(key.back())) {
|
||||
key.erase(key.end() - 1);
|
||||
}
|
||||
|
||||
parser.parse_token(PARSE_SAMELINE);
|
||||
epairs.set(key, parser.token);
|
||||
}
|
||||
|
||||
void map_entity_t::parse_brush(parser_t &parser)
|
||||
{
|
||||
// ericw -- brush primitives
|
||||
if (!parser.parse_token(PARSE_PEEK)) {
|
||||
FError("{}: unexpected EOF after {{ beginning brush", parser.location);
|
||||
}
|
||||
|
||||
brush_t brush;
|
||||
|
||||
if (parser.token == "(") {
|
||||
brush.base_format = texcoord_style_t::quaked;
|
||||
} else {
|
||||
parser.parse_token();
|
||||
brush.base_format = texcoord_style_t::brush_primitives;
|
||||
|
||||
// optional
|
||||
if (parser.token == "brushDef") {
|
||||
if (!parser.parse_token()) {
|
||||
FError("Brush primitives: unexpected EOF (nothing after brushDef)");
|
||||
}
|
||||
}
|
||||
|
||||
// mandatory
|
||||
if (parser.token != "{") {
|
||||
FError("Brush primitives: expected second {{ at beginning of brush, got \"{}\"", parser.token);
|
||||
}
|
||||
}
|
||||
// ericw -- end brush primitives
|
||||
|
||||
while (parser.parse_token()) {
|
||||
|
||||
// set linenum after first parsed token
|
||||
if (!brush.location) {
|
||||
brush.location = parser.location;
|
||||
}
|
||||
|
||||
if (parser.token == "}") {
|
||||
break;
|
||||
}
|
||||
|
||||
brush.parse_brush_face(parser, brush.base_format);
|
||||
}
|
||||
|
||||
// ericw -- brush primitives - there should be another closing }
|
||||
if (brush.base_format == texcoord_style_t::brush_primitives) {
|
||||
if (!parser.parse_token()) {
|
||||
FError("Brush primitives: unexpected EOF (no closing brace)");
|
||||
} else if (parser.token != "}") {
|
||||
FError("Brush primitives: Expected }}, got: {}", parser.token);
|
||||
}
|
||||
}
|
||||
// ericw -- end brush primitives
|
||||
|
||||
if (brush.faces.size()) {
|
||||
brushes.push_back(std::move(brush));
|
||||
}
|
||||
}
|
||||
|
||||
bool map_entity_t::parse(parser_t &parser)
|
||||
{
|
||||
location = parser.location;
|
||||
|
||||
if (!parser.parse_token()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parser.token != "{") {
|
||||
FError("{}: Invalid entity format, {{ not found", parser.location);
|
||||
}
|
||||
|
||||
do {
|
||||
if (!parser.parse_token()) {
|
||||
FError("Unexpected EOF (no closing brace)");
|
||||
}
|
||||
|
||||
if (parser.token == "}") {
|
||||
break;
|
||||
} else if (parser.token == "{") {
|
||||
parse_brush(parser);
|
||||
} else {
|
||||
parse_entity_dict(parser);
|
||||
}
|
||||
} while (1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void map_entity_t::write(std::ostream &stream)
|
||||
{
|
||||
stream << "{\n";
|
||||
|
||||
for (auto &kvp : epairs) {
|
||||
ewt::print(stream, "\"{}\" \"{}\"\n", kvp.first, kvp.second);
|
||||
}
|
||||
|
||||
size_t brush_id = 0;
|
||||
|
||||
for (auto &brush : brushes) {
|
||||
ewt::print(stream, "// brush {}\n", brush_id++);
|
||||
brush.write(stream);
|
||||
}
|
||||
|
||||
stream << "}\n";
|
||||
}
|
||||
|
||||
void map_file_t::parse(parser_t &parser)
|
||||
{
|
||||
while (true) {
|
||||
map_entity_t &entity = entities.emplace_back();
|
||||
|
||||
if (!entity.parse(parser)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove dummy entity inserted above
|
||||
assert(!entities.back().epairs.size());
|
||||
entities.pop_back();
|
||||
}
|
||||
|
||||
void map_file_t::write(std::ostream &stream)
|
||||
{
|
||||
size_t ent_id = 0;
|
||||
|
||||
for (auto &entity : entities) {
|
||||
ewt::print(stream, "// entity {}\n", ent_id++);
|
||||
entity.write(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void map_file_t::convert_to(texcoord_style_t style)
|
||||
{
|
||||
for (auto &entity : entities) {
|
||||
for (auto &brush : entity.brushes) {
|
||||
brush.convert_to(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
Copyright (C) 1996-1997 Id Software, Inc.
|
||||
Copyright (C) 1997 Greg Lewis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
See file, 'COPYING', for details.
|
||||
*/
|
||||
// qbsp.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mathlib.hh"
|
||||
#include "bspfile.hh"
|
||||
#include "entdata.h"
|
||||
#include "parser.hh"
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
|
||||
// main brush style; technically these can be mixed
|
||||
enum class texcoord_style_t
|
||||
{
|
||||
quaked,
|
||||
etp,
|
||||
valve_220,
|
||||
brush_primitives
|
||||
};
|
||||
|
||||
// raw texdef values; the unchanged
|
||||
// values in the .map
|
||||
struct texdef_bp_t
|
||||
{
|
||||
qmat<vec_t, 2, 3> axis;
|
||||
};
|
||||
|
||||
struct texdef_quake_ed_t
|
||||
{
|
||||
qvec2d shift;
|
||||
vec_t rotate;
|
||||
qvec2d scale;
|
||||
};
|
||||
|
||||
struct texdef_valve_t : texdef_quake_ed_t
|
||||
{
|
||||
qmat<vec_t, 2, 3> axis;
|
||||
};
|
||||
|
||||
struct texdef_etp_t : texdef_quake_ed_t
|
||||
{
|
||||
bool tx2 = false;
|
||||
};
|
||||
|
||||
// extra Q2 info
|
||||
struct texinfo_quake2_t
|
||||
{
|
||||
contentflags_t contents;
|
||||
surfflags_t flags;
|
||||
int value;
|
||||
};
|
||||
|
||||
// convert a plane to a texture axis; used by quaked
|
||||
struct texture_axis_t
|
||||
{
|
||||
qvec3d xv;
|
||||
qvec3d yv;
|
||||
qvec3d snapped_normal;
|
||||
|
||||
// use_new_axis = !qbsp_options.oldaxis.value()
|
||||
constexpr texture_axis_t(const qplane3d &plane, bool use_new_axis = false)
|
||||
{
|
||||
constexpr qvec3d baseaxis[18] = {
|
||||
{0, 0, 1}, {1, 0, 0}, {0, -1, 0}, // floor
|
||||
{0, 0, -1}, {1, 0, 0}, {0, -1, 0}, // ceiling
|
||||
{1, 0, 0}, {0, 1, 0}, {0, 0, -1}, // west wall
|
||||
{-1, 0, 0}, {0, 1, 0}, {0, 0, -1}, // east wall
|
||||
{0, 1, 0}, {1, 0, 0}, {0, 0, -1}, // south wall
|
||||
{0, -1, 0}, {1, 0, 0}, {0, 0, -1} // north wall
|
||||
};
|
||||
|
||||
vec_t best = 0;
|
||||
size_t bestaxis = 0;
|
||||
|
||||
for (size_t i = 0; i < 6; i++) {
|
||||
vec_t dot = qv::dot(plane.normal, baseaxis[i * 3]);
|
||||
|
||||
if (dot > best || (dot == best && use_new_axis)) {
|
||||
best = dot;
|
||||
bestaxis = i;
|
||||
}
|
||||
}
|
||||
|
||||
xv = baseaxis[bestaxis * 3 + 1];
|
||||
yv = baseaxis[bestaxis * 3 + 2];
|
||||
snapped_normal = baseaxis[bestaxis * 3];
|
||||
}
|
||||
};
|
||||
|
||||
// a single brush side from a .map
|
||||
struct brush_side_t
|
||||
{
|
||||
// source location
|
||||
parser_source_location location;
|
||||
texcoord_style_t style;
|
||||
|
||||
// raw texture name
|
||||
std::string texture;
|
||||
// stores the original values that we loaded with, even if they were invalid.
|
||||
std::variant<texdef_quake_ed_t, texdef_valve_t, texdef_etp_t, texdef_bp_t> raw;
|
||||
// raw plane points
|
||||
std::array<qvec3d, 3> planepts;
|
||||
// Q2/Q3 data, if available
|
||||
std::optional<texinfo_quake2_t> extended_info = std::nullopt;
|
||||
|
||||
// calculated texture vecs
|
||||
texvecf vecs;
|
||||
// calculated plane
|
||||
qplane3d plane;
|
||||
|
||||
// TODO move to qv? keep local?
|
||||
static bool is_valid_texture_projection(const qvec3f &faceNormal, const qvec3f &s_vec, const qvec3f &t_vec);
|
||||
|
||||
inline bool is_valid_texture_projection() const
|
||||
{
|
||||
return is_valid_texture_projection(plane.normal, vecs.row(0).xyz(), vecs.row(1).xyz());
|
||||
}
|
||||
|
||||
void validate_texture_projection();
|
||||
|
||||
// parsing
|
||||
// TODO: move to the individual texdefs?
|
||||
static texdef_bp_t parse_bp(parser_t &parser);
|
||||
static texdef_valve_t parse_valve_220(parser_t &parser);
|
||||
static texdef_quake_ed_t parse_quake_ed(parser_t &parser);
|
||||
|
||||
bool parse_quark_comment(parser_t &parser);
|
||||
void parse_extended_texinfo(parser_t &parser);
|
||||
|
||||
void set_texinfo(const texdef_quake_ed_t &texdef);
|
||||
void set_texinfo(const texdef_valve_t &texdef);
|
||||
void set_texinfo(const texdef_etp_t &texdef);
|
||||
void set_texinfo(const texdef_bp_t &texdef);
|
||||
|
||||
void parse_texture_def(parser_t &parser, texcoord_style_t base_format);
|
||||
void parse_plane_def(parser_t &parser);
|
||||
|
||||
void write_extended_info(std::ostream &stream);
|
||||
|
||||
void write_texinfo(std::ostream &stream, const texdef_quake_ed_t &texdef);
|
||||
void write_texinfo(std::ostream &stream, const texdef_valve_t &texdef);
|
||||
void write_texinfo(std::ostream &stream, const texdef_etp_t &texdef);
|
||||
void write_texinfo(std::ostream &stream, const texdef_bp_t &texdef);
|
||||
|
||||
void write(std::ostream &stream);
|
||||
|
||||
void convert_to(texcoord_style_t style);
|
||||
};
|
||||
|
||||
struct brush_t
|
||||
{
|
||||
parser_source_location location;
|
||||
texcoord_style_t base_format;
|
||||
std::vector<brush_side_t> faces;
|
||||
|
||||
void parse_brush_face(parser_t &parser, texcoord_style_t base_format);
|
||||
|
||||
void write(std::ostream &stream);
|
||||
|
||||
void convert_to(texcoord_style_t style);
|
||||
};
|
||||
|
||||
struct map_entity_t
|
||||
{
|
||||
parser_source_location location;
|
||||
entdict_t epairs;
|
||||
std::vector<brush_t> brushes;
|
||||
|
||||
void parse_entity_dict(parser_t &parser);
|
||||
void parse_brush(parser_t &parser);
|
||||
bool parse(parser_t &parser);
|
||||
|
||||
void write(std::ostream &stream);
|
||||
};
|
||||
|
||||
struct map_file_t
|
||||
{
|
||||
std::vector<map_entity_t> entities;
|
||||
|
||||
void parse(parser_t &parser);
|
||||
|
||||
void write(std::ostream &stream);
|
||||
|
||||
void convert_to(texcoord_style_t style);
|
||||
};
|
||||
|
|
@ -46,6 +46,18 @@ constexpr vec_t DIST_EPSILON = 0.0001;
|
|||
constexpr vec_t DEGREES_EPSILON = 0.001;
|
||||
constexpr vec_t DEFAULT_ON_EPSILON = 0.1;
|
||||
|
||||
/*
|
||||
* The quality of the bsp output is highly sensitive to these epsilon values.
|
||||
* Notes:
|
||||
* - some calculations are sensitive to errors and need the various
|
||||
* epsilons to be such that QBSP_EQUAL_EPSILON < CONTINUOUS_EPSILON.
|
||||
* ( TODO: re-check if CONTINUOUS_EPSILON is still directly related )
|
||||
*/
|
||||
constexpr vec_t ANGLEEPSILON = NORMAL_EPSILON;
|
||||
constexpr vec_t ZERO_EPSILON = DIST_EPSILON;
|
||||
constexpr vec_t QBSP_EQUAL_EPSILON = DIST_EPSILON;
|
||||
constexpr vec_t CONTINUOUS_EPSILON = 0.0005;
|
||||
|
||||
enum planeside_t : int8_t
|
||||
{
|
||||
SIDE_FRONT,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
#include "fs.hh"
|
||||
|
||||
enum : int32_t
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/* Copyright (C) 1996-1997 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
See file, 'COPYING', for details.
|
||||
*/
|
||||
|
||||
int maputil_main(int argc, char **argv);
|
||||
|
|
@ -255,18 +255,6 @@ private:
|
|||
|
||||
extern settings::qbsp_settings qbsp_options;
|
||||
|
||||
/*
|
||||
* The quality of the bsp output is highly sensitive to these epsilon values.
|
||||
* Notes:
|
||||
* - some calculations are sensitive to errors and need the various
|
||||
* epsilons to be such that QBSP_EQUAL_EPSILON < CONTINUOUS_EPSILON.
|
||||
* ( TODO: re-check if CONTINUOUS_EPSILON is still directly related )
|
||||
*/
|
||||
constexpr vec_t ANGLEEPSILON = 0.000001;
|
||||
constexpr vec_t ZERO_EPSILON = 0.0001;
|
||||
constexpr vec_t QBSP_EQUAL_EPSILON = 0.0001;
|
||||
constexpr vec_t CONTINUOUS_EPSILON = 0.0005;
|
||||
|
||||
// the exact bounding box of the brushes is expanded some for the headnode
|
||||
// volume. this is done to avoid a zero-bounded node/leaf, the particular
|
||||
// value doesn't matter but it shows up in the .bsp output.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
set(MAPUTIL_SOURCES
|
||||
maputil.cc
|
||||
../include/maputil/maputil.hh
|
||||
)
|
||||
|
||||
find_package(Lua)
|
||||
|
||||
add_library(libmaputil STATIC ${MAPUTIL_SOURCES})
|
||||
|
||||
target_link_libraries(libmaputil common TBB::tbb TBB::tbbmalloc fmt::fmt)
|
||||
|
||||
if (LUA_LIBRARIES)
|
||||
target_link_libraries(libmaputil ${LUA_LIBRARIES})
|
||||
target_include_directories(libmaputil PRIVATE ${LUA_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
add_executable(maputil main.cc)
|
||||
target_link_libraries(maputil libmaputil)
|
||||
|
||||
if (LUA_LIBRARIES)
|
||||
target_include_directories(maputil PRIVATE ${LUA_INCLUDE_DIR})
|
||||
add_definitions(-DUSE_LUA)
|
||||
endif()
|
||||
|
||||
# HACK: copy .dll dependencies
|
||||
add_custom_command(TARGET maputil POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:TBB::tbb>" "$<TARGET_FILE_DIR:maputil>"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:TBB::tbbmalloc>" "$<TARGET_FILE_DIR:maputil>"
|
||||
)
|
||||
|
||||
install(TARGETS maputil RUNTIME DESTINATION bin)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/* Copyright (C) 1996-1997 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
See file, 'COPYING', for details.
|
||||
*/
|
||||
|
||||
#include <maputil/maputil.hh>
|
||||
#include <common/log.hh>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
try {
|
||||
return maputil_main(argc, argv);
|
||||
} catch (const std::exception &e) {
|
||||
exit_on_exception(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
/* Copyright (C) 1996-1997 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
See file, 'COPYING', for details.
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <common\entdata.h>
|
||||
#include <common\parser.hh>
|
||||
#include <common\log.hh>
|
||||
#include <common\mapfile.hh>
|
||||
|
||||
#ifdef USE_LUA
|
||||
extern "C"
|
||||
{
|
||||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
map_file_t LoadMapOrEntFile(const fs::path &source)
|
||||
{
|
||||
logging::funcheader();
|
||||
|
||||
auto file = fs::load(source);
|
||||
map_file_t map;
|
||||
|
||||
if (!file) {
|
||||
FError("Couldn't load map/entity file \"{}\".", source);
|
||||
return map;
|
||||
}
|
||||
|
||||
parser_t parser(file, {source.string()});
|
||||
|
||||
map.parse(parser);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
constexpr const char *usage = "\
|
||||
usage: maputil [operations...]\
|
||||
\
|
||||
valid operations:\
|
||||
--query \"<Lua expression>\"\
|
||||
perform a query on entities and print out matching results.\
|
||||
see docs for more details on globals.\
|
||||
--script \"<path to Lua script file\"\
|
||||
execute the given Lua script.\
|
||||
--strip_extended_info\
|
||||
removes extended Quake II/III information on faces.\
|
||||
--convert <quake | valve | etp | bp>\
|
||||
convert the current map to the given format.\
|
||||
--save \"<output path>\"\
|
||||
save the current map to the given output path.\
|
||||
";
|
||||
|
||||
static void maputil_query(map_file_t &map_file, const char *query)
|
||||
{
|
||||
#ifdef USE_LUA
|
||||
logging::print("query: {}\n", query);
|
||||
|
||||
lua_State *state = luaL_newstate();
|
||||
|
||||
luaL_openlibs(state);
|
||||
|
||||
int err = luaL_loadstring(state, query);
|
||||
|
||||
if (err != LUA_OK) {
|
||||
logging::print("can't load query: {}\n", lua_tostring(state, -1));
|
||||
lua_pop(state, 1);
|
||||
} else {
|
||||
lua_pushvalue(state, 1);
|
||||
|
||||
int ref = luaL_ref(state, LUA_REGISTRYINDEX);
|
||||
|
||||
lua_pop(state, 1);
|
||||
|
||||
for (auto &entity : map_file.entities) {
|
||||
lua_createtable(state, 0, entity.epairs.size());
|
||||
|
||||
for (auto &kvp : entity.epairs) {
|
||||
|
||||
lua_pushstring(state, kvp.second.c_str());
|
||||
lua_setfield(state, -2, kvp.first.c_str());
|
||||
}
|
||||
|
||||
lua_setglobal(state, "entity");
|
||||
|
||||
lua_rawgeti(state, LUA_REGISTRYINDEX, ref);
|
||||
err = lua_pcall(state, 0, 1, 0);
|
||||
|
||||
if (err != LUA_OK) {
|
||||
logging::print("can't execute query: {}\n", lua_tostring(state, -1));
|
||||
lua_pop(state, 1);
|
||||
} else {
|
||||
int b = lua_toboolean(state, -1);
|
||||
lua_pop(state, 1);
|
||||
|
||||
if (b) {
|
||||
logging::print("MATCHED: {} @ {}\n", entity.epairs.get("classname"), entity.location);
|
||||
}
|
||||
}
|
||||
|
||||
lua_gc(state, LUA_GCCOLLECT);
|
||||
}
|
||||
|
||||
luaL_unref(state, LUA_REGISTRYINDEX, ref);
|
||||
|
||||
lua_close(state);
|
||||
}
|
||||
#else
|
||||
logging::print("maputil not compiled with Lua support\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
int maputil_main(int argc, char **argv)
|
||||
{
|
||||
logging::preinitialize();
|
||||
|
||||
fmt::print("---- maputil / ericw-tools {} ----\n", ERICWTOOLS_VERSION);
|
||||
if (argc == 1) {
|
||||
fmt::print("{}", usage);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fs::path source = argv[1];
|
||||
|
||||
if (!fs::exists(source)) {
|
||||
source = DefaultExtension(argv[1], "map");
|
||||
}
|
||||
|
||||
printf("---------------------\n");
|
||||
fmt::print("{}\n", source);
|
||||
|
||||
map_file_t map_file;
|
||||
|
||||
map_file = LoadMapOrEntFile(source);
|
||||
|
||||
for (int32_t i = 2; i < argc - 1; i++) {
|
||||
|
||||
const char *cmd = argv[i];
|
||||
|
||||
if (!strcmp(cmd, "--query")) {
|
||||
i++;
|
||||
|
||||
const char *query = argv[i];
|
||||
|
||||
maputil_query(map_file, query);
|
||||
} else if (!strcmp(cmd, "--save")) {
|
||||
i++;
|
||||
|
||||
const char *output = argv[i];
|
||||
|
||||
fs::path dest = DefaultExtension(output, "map");
|
||||
fmt::print("saving to {}...\n", dest);
|
||||
|
||||
std::ofstream stream(dest);
|
||||
map_file.write(stream);
|
||||
} else if (!strcmp(cmd, "--strip_extended_info")) {
|
||||
|
||||
for (auto &entity : map_file.entities) {
|
||||
for (auto &brush : entity.brushes) {
|
||||
for (auto &face : brush.faces) {
|
||||
face.extended_info = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!strcmp(cmd, "--convert")) {
|
||||
|
||||
i++;
|
||||
|
||||
const char *type = argv[i];
|
||||
texcoord_style_t dest_style;
|
||||
|
||||
if (!strcmp(type, "quake")) {
|
||||
dest_style = texcoord_style_t::quaked;
|
||||
} else if (!strcmp(type, "valve")) {
|
||||
dest_style = texcoord_style_t::valve_220;
|
||||
} else if (!strcmp(type, "etp")) {
|
||||
dest_style = texcoord_style_t::etp;
|
||||
} else if (!strcmp(type, "bp")) {
|
||||
dest_style = texcoord_style_t::brush_primitives;
|
||||
} else {
|
||||
FError("unknown map style {}", type);
|
||||
}
|
||||
|
||||
map_file.convert_to(dest_style);
|
||||
}
|
||||
}
|
||||
|
||||
printf("---------------------\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2731,9 +2731,7 @@ bool ParseEntity(parser_t &parser, mapentity_t &entity, texture_def_issues_t &is
|
|||
}
|
||||
} while (parser.token != "}");
|
||||
} else {
|
||||
auto brush = ParseBrush(parser, entity, issue_stats);
|
||||
|
||||
if (brush.faces.size()) {
|
||||
if (auto brush = ParseBrush(parser, entity, issue_stats); brush.faces.size()) {
|
||||
entity.mapbrushes.push_back(std::move(brush));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue