From 8248995e2dc0b410311e16609e231b345e8e4a7f Mon Sep 17 00:00:00 2001 From: Skies912 Date: Wed, 10 Jun 2026 20:17:36 -0400 Subject: [PATCH 1/4] Add Valve-220 map format support to quemap. Auto-detects Valve-220 by peeking for '[' after the texture name; falls back to standard Q3 format if not present. Both formats are supported transparently with no flags required. Co-Authored-By: Claude Sonnet 4.6 --- src/quemap/map.c | 31 +++++++++++++++++++++++++------ src/quemap/map.h | 5 +++++ src/quemap/qbsp.c | 9 +++++++++ src/quemap/texture.c | 17 +++++++++++++++++ 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/quemap/map.c b/src/quemap/map.c index 8079c81df5..8939b90709 100644 --- a/src/quemap/map.c +++ b/src/quemap/map.c @@ -530,12 +530,31 @@ static brush_t *ParseBrush(parser_t *parser, entity_t *entity) { g_strlcpy(side->texture, token, sizeof(side->texture)); - // read the texture parameters - Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->shift.x, 1); - Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->shift.y, 1); - Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->rotate, 1); - Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->scale.x, 1); - Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->scale.y, 1); + // detect Valve-220 vs standard Q1/Q3 format by peeking for '[' + Parse_PeekToken(parser, PARSE_NO_WRAP, token, sizeof(token)); + + if (!g_strcmp0(token, "[")) { + // Valve-220: [ ux uy uz shift_x ] [ vx vy vz shift_y ] rotation scale_x scale_y + side->valve = true; + for (int32_t i = 0; i < 2; i++) { + Parse_Token(parser, PARSE_NO_WRAP, token, sizeof(token)); // consume "[" + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->axis[i].x, 1); + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->axis[i].y, 1); + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->axis[i].z, 1); + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->axis[i].w, 1); // shift + Parse_Token(parser, PARSE_NO_WRAP, token, sizeof(token)); // consume "]" + } + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->rotate, 1); + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->scale.x, 1); + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->scale.y, 1); + } else { + // Standard Q1/Q3: shift_x shift_y rotation scale_x scale_y + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->shift.x, 1); + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->shift.y, 1); + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->rotate, 1); + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->scale.x, 1); + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->scale.y, 1); + } if (!Parse_IsEOL(parser)) { Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_INT32, &side->contents, 1); diff --git a/src/quemap/map.h b/src/quemap/map.h index aeaed80e4d..819490588e 100644 --- a/src/quemap/map.h +++ b/src/quemap/map.h @@ -78,6 +78,11 @@ typedef struct brush_side_s { */ vec2_t scale; + /** + * @brief True if this brush side uses Valve-220 explicit texture axes. + */ + bool valve; + /** * @brief The texture axis for S and T, in xyz + offset notation. */ diff --git a/src/quemap/qbsp.c b/src/quemap/qbsp.c index d2e5f80d76..18271a296b 100644 --- a/src/quemap/qbsp.c +++ b/src/quemap/qbsp.c @@ -168,6 +168,15 @@ int32_t BSP_Main(void) { LoadMapFile(map_name); + bool valve = false; + for (int32_t i = 0; i < num_brush_sides; i++) { + if (brush_sides[i].valve) { + valve = true; + break; + } + } + Com_Print("Map format: %s\n", valve ? "Quake3 (Valve)" : "Quake3"); + EmitPlanes(); EmitMaterials(); EmitBrushes(); diff --git a/src/quemap/texture.c b/src/quemap/texture.c index 16b00bd1b9..22506bc451 100644 --- a/src/quemap/texture.c +++ b/src/quemap/texture.c @@ -68,6 +68,23 @@ static void TextureAxisForPlane(const plane_t *plane, vec3_t *xv, vec3_t *yv) { */ void TextureVectorsForBrushSide(brush_side_t *side, const vec3_t origin) { + if (side->valve) { + // Valve-220: axes are already stored in side->axis as (direction, shift); apply scale and origin offset + const vec2_t scale = { + .x = side->scale.x ?: 1.f, + .y = side->scale.y ?: 1.f, + }; + for (int32_t i = 0; i < 2; i++) { + const vec3_t dir = Vec3(side->axis[i].x, side->axis[i].y, side->axis[i].z); + const float shift = side->axis[i].w; + for (int32_t j = 0; j < 3; j++) { + side->axis[i].xyzw[j] /= scale.xy[i]; + } + side->axis[i].w = shift + Vec3_Dot(origin, dir); + } + return; + } + vec3_t axis[2]; TextureAxisForPlane(&planes[side->plane], &axis[0], &axis[1]); From 321b356f8a13241a3db11552ccb82e0d2a7ef84a Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Fri, 12 Jun 2026 08:45:57 -0400 Subject: [PATCH 2/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/quemap/map.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/quemap/map.c b/src/quemap/map.c index 8939b90709..3c65bae4ee 100644 --- a/src/quemap/map.c +++ b/src/quemap/map.c @@ -537,7 +537,10 @@ static brush_t *ParseBrush(parser_t *parser, entity_t *entity) { // Valve-220: [ ux uy uz shift_x ] [ vx vy vz shift_y ] rotation scale_x scale_y side->valve = true; for (int32_t i = 0; i < 2; i++) { - Parse_Token(parser, PARSE_NO_WRAP, token, sizeof(token)); // consume "[" + Parse_Token(parser, PARSE_NO_WRAP, token, sizeof(token)); + if (g_strcmp0(token, "[")) { + Com_Error(ERROR_FATAL, "Invalid brush %d (%s)\n", num_brushes, token); + } Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->axis[i].x, 1); Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->axis[i].y, 1); Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->axis[i].z, 1); From 57352fca98e403ee706bd4f7ff10c479e7f7dd49 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Fri, 12 Jun 2026 08:46:17 -0400 Subject: [PATCH 3/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/quemap/texture.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/quemap/texture.c b/src/quemap/texture.c index 22506bc451..1baa96c490 100644 --- a/src/quemap/texture.c +++ b/src/quemap/texture.c @@ -69,18 +69,27 @@ static void TextureAxisForPlane(const plane_t *plane, vec3_t *xv, vec3_t *yv) { void TextureVectorsForBrushSide(brush_side_t *side, const vec3_t origin) { if (side->valve) { - // Valve-220: axes are already stored in side->axis as (direction, shift); apply scale and origin offset + // Valve-220: axes are already stored in side->axis as (direction, shift). + // Note that this function is called once during parsing (origin = 0) and may be called + // a second time when applying entity origin offsets. const vec2_t scale = { .x = side->scale.x ?: 1.f, .y = side->scale.y ?: 1.f, }; + + if (!Vec3_Equal(origin, Vec3_Zero())) { + // Axis xyz are already scaled from the first pass; only apply the origin offset. + for (int32_t i = 0; i < 2; i++) { + side->axis[i].w += Vec3_Dot(origin, side->axis[i].xyz); + } + return; + } + + // First pass: apply scale. for (int32_t i = 0; i < 2; i++) { - const vec3_t dir = Vec3(side->axis[i].x, side->axis[i].y, side->axis[i].z); - const float shift = side->axis[i].w; for (int32_t j = 0; j < 3; j++) { side->axis[i].xyzw[j] /= scale.xy[i]; } - side->axis[i].w = shift + Vec3_Dot(origin, dir); } return; } From e7b59e0ead512cccb83e915d9e5e0a269a0cdcc3 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Fri, 12 Jun 2026 08:46:27 -0400 Subject: [PATCH 4/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/quemap/map.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/quemap/map.c b/src/quemap/map.c index 3c65bae4ee..f342a25d79 100644 --- a/src/quemap/map.c +++ b/src/quemap/map.c @@ -545,7 +545,10 @@ static brush_t *ParseBrush(parser_t *parser, entity_t *entity) { Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->axis[i].y, 1); Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->axis[i].z, 1); Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->axis[i].w, 1); // shift - Parse_Token(parser, PARSE_NO_WRAP, token, sizeof(token)); // consume "]" + Parse_Token(parser, PARSE_NO_WRAP, token, sizeof(token)); + if (g_strcmp0(token, "]")) { + Com_Error(ERROR_FATAL, "Invalid brush %d (%s)\n", num_brushes, token); + } } Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->rotate, 1); Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->scale.x, 1);