vk: patch: allow mapping texture to materials for brush entities

```
{
	"_xvk_ent_id" "39"
	"_xvk_map_material" "generic028 mirror"
}
```

NOTES:
- might severely degrade performance in some cases/under debug as we're
  doing N^2 search with strcmp for rendered entities.
This commit is contained in:
Ivan Avdeev 2023-09-29 12:48:32 -04:00
parent 7839792964
commit e97691b27c
7 changed files with 189 additions and 51 deletions

View File

@ -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 ... ..."
}
```

View File

@ -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;
}

View File

@ -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)

View File

@ -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

View File

@ -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];
}

View File

@ -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 );

View File

@ -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]); */