diff --git a/src/quemap/map.c b/src/quemap/map.c index 8079c81df..f342a25d7 100644 --- a/src/quemap/map.c +++ b/src/quemap/map.c @@ -530,12 +530,37 @@ 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)); + 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); + Parse_Primitive(parser, PARSE_NO_WRAP, PARSE_FLOAT, &side->axis[i].w, 1); // shift + 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); + 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 aeaed80e4..819490588 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 d2e5f80d7..18271a296 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 16b00bd1b..1baa96c49 100644 --- a/src/quemap/texture.c +++ b/src/quemap/texture.c @@ -68,6 +68,32 @@ 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). + // 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++) { + for (int32_t j = 0; j < 3; j++) { + side->axis[i].xyzw[j] /= scale.xy[i]; + } + } + return; + } + vec3_t axis[2]; TextureAxisForPlane(&planes[side->plane], &axis[0], &axis[1]);