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:
parent
7839792964
commit
e97691b27c
|
@ -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 ... ..."
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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]); */
|
||||
|
|
Loading…
Reference in New Issue