diff --git a/ref/vk/NOTES.md b/ref/vk/NOTES.md index 12a3d456..6787f224 100644 --- a/ref/vk/NOTES.md +++ b/ref/vk/NOTES.md @@ -543,4 +543,47 @@ GAME SKILL LEVEL:1 .. and only then R_NewMap ``` +# 2023-09-28 E303 +## #526 +Replace textures for specific brush entities. +For a single texture it might be as easy as: +``` +{ + "_xvk_ent_id" "39" + "_xvk_texture" "generic028" + "_xvk_material" "generic_metal1" +} +``` +For multiple replacements: +0. Multiple entries +``` +{ + "_xvk_ent_id" "39" + "_xvk_texture" "generic028" + "_xvk_material" "generic_metal1" +} + +{ + "_xvk_ent_id" "39" + "_xvk_texture" "generic029" + "_xvk_material" "generic_metal2" +} +``` + +1. Pairwise +``` +{ + "_xvk_ent_id" "39" + "_xvk_texture" "generic028 generic029 ..." + "_xvk_material" "generic_metal1 generic_metal2 ..." +} +``` + +2. Pair list <-- preferred +``` +{ + "_xvk_ent_id" "39" + "_xvk_texture_material" "generic028 generic_metal1 generic029 generic_metal2 ... ..." +} +``` diff --git a/ref/vk/vk_brush.c b/ref/vk/vk_brush.c index 852300cb..30b96f44 100644 --- a/ref/vk/vk_brush.c +++ b/ref/vk/vk_brush.c @@ -1053,6 +1053,17 @@ static qboolean getSmoothedNormalFor(const model_t* mod, int vertex_index, int s return true; } +static const xvk_mapent_func_any_t *getModelFuncAnyPatch( const model_t *const mod ) { + for (int i = 0; i < g_map_entities.func_any_count; ++i) { + const xvk_mapent_func_any_t *const fw = g_map_entities.func_any + i; + if (Q_strcmp(mod->name, fw->model) == 0) { + return fw; + } + } + + return NULL; +} + static qboolean fillBrushSurfaces(fill_geometries_args_t args) { int vertex_offset = 0; int num_geometries = 0; @@ -1064,6 +1075,8 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) { connectVertices(args.mod); + const xvk_mapent_func_any_t *const entity_patch = getModelFuncAnyPatch(args.mod); + // Load sorted by gl_texturenum // TODO this does not make that much sense in vulkan (can sort later) for (int t = 0; t <= args.sizes.max_texture_id; ++t) { @@ -1076,12 +1089,11 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) { int index_count = 0; vec3_t tangent; const int orig_tex_id = surf->texinfo->texture->gl_texturenum; - int tex_id = surf->texinfo->texture->gl_texturenum; - const xvk_patch_surface_t *const psurf = R_VkPatchGetSurface(surface_index); - - if (t != tex_id) + if (t != orig_tex_id) continue; + int tex_id = orig_tex_id; + const xvk_patch_surface_t *const psurf = R_VkPatchGetSurface(surface_index); if (psurf && psurf->tex_id >= 0) tex_id = psurf->tex_id; @@ -1109,6 +1121,15 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) { return false; } + if (entity_patch) { + for (int i = 0; i < entity_patch->matmap_count; ++i) { + if (entity_patch->matmap[i].from_tex == tex_id) { + tex_id = entity_patch->matmap[i].to_mat.index; + break; + } + } + } + VectorClear(model_geometry->emissive); model_geometry->surf_deprecate = surf; @@ -1234,17 +1255,6 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) { return true; } -static const xvk_mapent_func_wall_t *getModelFuncWallPatch( const model_t *const mod ) { - for (int i = 0; i < g_map_entities.func_walls_count; ++i) { - const xvk_mapent_func_wall_t *const fw = g_map_entities.func_walls + i; - if (Q_strcmp(mod->name, fw->model) == 0) { - return fw; - } - } - - return NULL; -} - static qboolean createRenderModel( const model_t *mod, vk_brush_model_t *bmodel, const model_sizes_t sizes ) { bmodel->geometry = R_GeometryRangeAlloc(sizes.num_vertices, sizes.num_indices); if (!bmodel->geometry.block_handle.size) { @@ -1414,8 +1424,8 @@ void R_VkBrushModelCollectEmissiveSurfaces( const struct model_s *mod, qboolean vk_brush_model_t *const bmodel = mod->cache.data; ASSERT(bmodel); - const xvk_mapent_func_wall_t *func_wall = getModelFuncWallPatch(mod); - const qboolean is_static = is_worldmodel || func_wall; + const xvk_mapent_func_any_t *func_any = getModelFuncAnyPatch(mod); + const qboolean is_static = is_worldmodel || (func_any && func_any->origin_patched); typedef struct { int model_surface_index; @@ -1487,14 +1497,14 @@ void R_VkBrushModelCollectEmissiveSurfaces( const struct model_s *mod, qboolean rt_light_add_polygon_t polylight = loadPolyLight(mod, s->surface_index, s->surf, s->emissive); - // func_wall surfaces do not really belong to BSP+PVS system, so they can't be used + // func_any surfaces do not really belong to BSP+PVS system, so they can't be used // for lights visibility calculation directly. - if (func_wall) { + if (func_any && func_any->origin_patched) { // TODO this is not really dynamic, but this flag signals using MovingSurface visibility calc polylight.dynamic = true; matrix3x4 m; Matrix3x4_LoadIdentity(m); - Matrix3x4_SetOrigin(m, func_wall->origin[0], func_wall->origin[1], func_wall->origin[2]); + Matrix3x4_SetOrigin(m, func_any->origin[0], func_any->origin[1], func_any->origin[2]); polylight.transform_row = &m; } diff --git a/ref/vk/vk_mapents.c b/ref/vk/vk_mapents.c index 6317eafb..a8ba8366 100644 --- a/ref/vk/vk_mapents.c +++ b/ref/vk/vk_mapents.c @@ -138,8 +138,8 @@ static unsigned parseEntPropClassname(const char* value, class_name_e *out, unsi *out = LightEnvironment; } else if (Q_strcmp(value, "worldspawn") == 0) { *out = Worldspawn; - } else if (Q_strcmp(value, "func_wall") == 0) { - *out = FuncWall; + } else if (Q_strncmp(value, "func_", 5) == 0) { + *out = FuncAny; } else { *out = Ignored; } @@ -339,17 +339,17 @@ static void readWorldspawn( const entity_props_t *props ) { }; } -static void readFuncWall( const entity_props_t *const props, uint32_t have_fields, int props_count ) { - DEBUG("func_wall entity=%d model=\"%s\", props_count=%d", g_map_entities.entity_count, (have_fields & Field_model) ? props->model : "N/A", props_count); +static void readFuncAny( const entity_props_t *const props, uint32_t have_fields, int props_count ) { + DEBUG("func_any entity=%d model=\"%s\", props_count=%d", g_map_entities.entity_count, (have_fields & Field_model) ? props->model : "N/A", props_count); - if (g_map_entities.func_walls_count >= MAX_FUNC_WALL_ENTITIES) { - ERR("Too many func_wall entities, max supported = %d", MAX_FUNC_WALL_ENTITIES); + if (g_map_entities.func_any_count >= MAX_FUNC_ANY_ENTITIES) { + ERR("Too many func_any entities, max supported = %d", MAX_FUNC_ANY_ENTITIES); return; } - xvk_mapent_func_wall_t *const e = g_map_entities.func_walls + g_map_entities.func_walls_count; + xvk_mapent_func_any_t *const e = g_map_entities.func_any + g_map_entities.func_any_count; - *e = (xvk_mapent_func_wall_t){0}; + *e = (xvk_mapent_func_any_t){0}; Q_strncpy( e->model, props->model, sizeof( e->model )); @@ -376,10 +376,10 @@ static void readFuncWall( const entity_props_t *const props, uint32_t have_field e->entity_index = g_map_entities.entity_count; g_map_entities.refs[g_map_entities.entity_count] = (xvk_mapent_ref_t){ - .class = FuncWall, - .index = g_map_entities.func_walls_count, + .class = FuncAny, + .index = g_map_entities.func_any_count, }; - ++g_map_entities.func_walls_count; + ++g_map_entities.func_any_count; } static void addPatchSurface( const entity_props_t *props, uint32_t have_fields ) { @@ -483,15 +483,67 @@ static void patchLightEntity( const entity_props_t *props, int ent_id, uint32_t fillLightFromProps(light, props, have_fields, true, ent_id); } -static void patchFuncWallEntity( const entity_props_t *props, uint32_t have_fields, int index ) { +static void patchFuncAnyEntity( const entity_props_t *props, uint32_t have_fields, int index ) { ASSERT(index >= 0); - ASSERT(index < g_map_entities.func_walls_count); - xvk_mapent_func_wall_t *const fw = g_map_entities.func_walls + index; + ASSERT(index < g_map_entities.func_any_count); + xvk_mapent_func_any_t *const e = g_map_entities.func_any + index; - if (have_fields & Field_origin) - VectorCopy(props->origin, fw->origin); + if (have_fields & Field_origin) { + VectorCopy(props->origin, e->origin); + e->origin_patched = true; - DEBUG("Patching ent=%d func_wall=%d %f %f %f", fw->entity_index, index, fw->origin[0], fw->origin[1], fw->origin[2]); + DEBUG("Patching ent=%d func_any=%d %f %f %f", e->entity_index, index, e->origin[0], e->origin[1], e->origin[2]); + } + + if (have_fields & Field__xvk_map_material) { + const char *s = props->_xvk_map_material; + while (*s) { + while (*s && isspace(*s)) ++s; // skip space + const char *from_begin = s; + while (*s && !isspace(*s)) ++s; // find first space or end + const int from_len = s - from_begin; + if (!from_len) + break; + + while (*s && isspace(*s)) ++s; // skip space + const char *to_begin = s; + while (*s && !isspace(*s)) ++s; // find first space or end + const int to_len = s - to_begin; + if (!to_len) + break; + + string from_tex, to_mat; + Q_strncpy(from_tex, from_begin, Q_min(sizeof from_tex, from_len + 1)); + Q_strncpy(to_mat, to_begin, Q_min(sizeof to_mat, to_len + 1)); + + const int from_tex_index = XVK_FindTextureNamedLike(from_tex); + const r_vk_material_ref_t to_mat_ref = R_VkMaterialGetForName(to_mat); + + DEBUG("Adding mapping from tex \"%s\"(%d) to mat \"%s\"(%d) for entity=%d", + from_tex, from_tex_index, to_mat, to_mat_ref.index, e->entity_index); + + if (from_tex_index <= 0) { + ERR("When patching entity=%d couldn't find map-from texture \"%s\"", e->entity_index, from_tex); + continue; + } + + if (to_mat_ref.index <= 0) { + ERR("When patching entity=%d couldn't find map-to material \"%s\"", e->entity_index, to_mat); + continue; + } + + if (e->matmap_count == MAX_MATERIAL_MAPPINGS) { + ERR("Cannot map tex \"%s\"(%d) to mat \"%s\"(%d) for entity=%d: too many mappings, " + "consider increasing MAX_MATERIAL_MAPPINGS", + from_tex, from_tex_index, to_mat, to_mat_ref.index, e->entity_index); + continue; + } + + e->matmap[e->matmap_count].from_tex = from_tex_index; + e->matmap[e->matmap_count].to_mat = to_mat_ref; + ++e->matmap_count; + } + } } static void patchEntity( const entity_props_t *props, uint32_t have_fields ) { @@ -511,8 +563,8 @@ static void patchEntity( const entity_props_t *props, uint32_t have_fields ) { case LightEnvironment: patchLightEntity(props, ei, have_fields, ref->index); break; - case FuncWall: - patchFuncWallEntity(props, have_fields, ref->index); + case FuncAny: + patchFuncAnyEntity(props, have_fields, ref->index); break; default: WARN("vk_mapents: trying to patch unsupported entity %d class %d", ei, ref->class); @@ -593,8 +645,8 @@ static void parseEntities( char *string, qboolean is_patch ) { readWorldspawn( &values ); break; - case FuncWall: - readFuncWall( &values, have_fields, props_count ); + case FuncAny: + readFuncAny( &values, have_fields, props_count ); break; case Unknown: @@ -735,7 +787,7 @@ void XVK_ParseMapEntities( void ) { g_map_entities.num_lights = 0; g_map_entities.single_environment_index = NoEnvironmentLights; g_map_entities.entity_count = 0; - g_map_entities.func_walls_count = 0; + g_map_entities.func_any_count = 0; g_map_entities.smoothing.threshold = cosf(DEG2RAD(45.f)); g_map_entities.smoothing.excluded_count = 0; for (int i = 0; i < g_map_entities.smoothing.groups_count; ++i) diff --git a/ref/vk/vk_mapents.h b/ref/vk/vk_mapents.h index 25698682..e31b9077 100644 --- a/ref/vk/vk_mapents.h +++ b/ref/vk/vk_mapents.h @@ -1,8 +1,13 @@ #pragma once +#include "vk_materials.h" + #include "xash3d_types.h" #include "const.h" // typedef word, needed for bspfile.h #include "bspfile.h" // MAX_MAP_ENTITIES +// TODO string_view instead of string. map entities string/buffer is supposed to be alive for the entire map duration +// NOTE that the above is not true for string in patches. but we can change that in parsePatches + #define ENT_PROP_LIST(X) \ X(0, vec3_t, origin, Vec3) \ X(1, vec3_t, angles, Vec3) \ @@ -29,6 +34,7 @@ X(22, float, _xvk_smoothing_threshold, Float) \ X(23, int_array_t, _xvk_smoothing_excluded_pairs, IntArray) \ X(24, int_array_t, _xvk_smoothing_group, IntArray) \ + X(25, string, _xvk_map_material, String) \ /* NOTE: not used X(22, int, rendermode, Int) \ @@ -44,7 +50,7 @@ typedef enum { LightSpot, LightEnvironment, Worldspawn, - FuncWall, + FuncAny, Ignored, Xvk_Target, } class_name_e; @@ -99,13 +105,22 @@ typedef struct { string model; vec3_t origin; + qboolean origin_patched; + +#define MAX_MATERIAL_MAPPINGS 8 + int matmap_count; + struct { + int from_tex; + r_vk_material_ref_t to_mat; + } matmap[MAX_MATERIAL_MAPPINGS]; + /* NOTE: not used. Might be needed for #118 in the future. int rendermode, renderamt, renderfx; color24 rendercolor; struct cl_entity_s *ent; */ -} xvk_mapent_func_wall_t; +} xvk_mapent_func_any_t; typedef struct { class_name_e class; @@ -130,9 +145,9 @@ typedef struct { int num_targets; xvk_mapent_target_t targets[MAX_MAPENT_TARGETS]; -#define MAX_FUNC_WALL_ENTITIES 64 - int func_walls_count; - xvk_mapent_func_wall_t func_walls[MAX_FUNC_WALL_ENTITIES]; +#define MAX_FUNC_ANY_ENTITIES 1024 + int func_any_count; + xvk_mapent_func_any_t func_any[MAX_FUNC_ANY_ENTITIES]; // TODO find out how to read this from the engine, or make its size dynamic //#define MAX_MAP_ENTITIES 2048 diff --git a/ref/vk/vk_materials.c b/ref/vk/vk_materials.c index e910b925..c064b49d 100644 --- a/ref/vk/vk_materials.c +++ b/ref/vk/vk_materials.c @@ -303,3 +303,18 @@ r_vk_material_t R_VkMaterialGetForTexture( int tex_index ) { return g_materials.materials[tex_index]; } + +r_vk_material_ref_t R_VkMaterialGetForName( const char *name ) { + // TODO separate material table + // For now it depends on 1-to-1 mapping between materials and textures + return (r_vk_material_ref_t){.index = VK_FindTexture(name)}; +} + +r_vk_material_t R_VkMaterialGetForRef( r_vk_material_ref_t ref ) { + // TODO separate material table + // For now it depends on 1-to-1 mapping between materials and textures + ASSERT(ref.index >= 0); + ASSERT(ref.index < MAX_TEXTURES); + + return g_materials.materials[ref.index]; +} diff --git a/ref/vk/vk_materials.h b/ref/vk/vk_materials.h index b171a3e8..2c809e46 100644 --- a/ref/vk/vk_materials.h +++ b/ref/vk/vk_materials.h @@ -27,3 +27,6 @@ struct model_s; void R_VkMaterialsLoadForModel( const struct model_s* mod ); r_vk_material_t R_VkMaterialGetForTexture( int tex_id ); + +r_vk_material_ref_t R_VkMaterialGetForName( const char *name ); +r_vk_material_t R_VkMaterialGetForRef( r_vk_material_ref_t ref ); diff --git a/ref/vk/vk_scene.c b/ref/vk/vk_scene.c index d6ed7dad..6fc130f6 100644 --- a/ref/vk/vk_scene.c +++ b/ref/vk/vk_scene.c @@ -591,11 +591,11 @@ static void drawEntity( cl_entity_t *ent, int render_mode ) case mod_brush: R_RotateForEntity( model, ent ); - // If this is potentially a func_wall model + // If this is potentially a func_any model if (ent->model->name[0] == '*') { - for (int i = 0; i < g_map_entities.func_walls_count; ++i) { - xvk_mapent_func_wall_t *const fw = g_map_entities.func_walls + i; - if (Q_strcmp(ent->model->name, fw->model) == 0) { + for (int i = 0; i < g_map_entities.func_any_count; ++i) { + xvk_mapent_func_any_t *const fw = g_map_entities.func_any + i; + if (Q_strcmp(ent->model->name, fw->model) == 0 && fw->origin_patched) { /* DEBUG("ent->index=%d (%s) mapent:%d off=%f %f %f", */ /* ent->index, ent->model->name, fw->entity_index, */ /* fw->origin[0], fw->origin[1], fw->origin[2]); */