decouple lightmapping state from emissive state
allow sky to skip lightmapping if lightgrid is enabled in Q2 mode again
This commit is contained in:
parent
94357818f9
commit
1fbe12767e
|
|
@ -348,7 +348,27 @@ public:
|
||||||
this->id = ID;
|
this->id = ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool surf_is_lightmapped(const surfflags_t &flags) const override { return !(flags.native & TEX_SPECIAL); }
|
bool surf_is_lightmapped(const surfflags_t &flags, const char *texname, bool light_nodraw, bool lightgrid_enabled) const override
|
||||||
|
{
|
||||||
|
/* don't save lightmaps for "trigger" texture */
|
||||||
|
if (!Q_strcasecmp(texname, "trigger"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* don't save lightmaps for "skip" texture */
|
||||||
|
if (!Q_strcasecmp(texname, "skip"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return !(flags.native & TEX_SPECIAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool surf_is_emissive(const surfflags_t &flags, const char *texname) const override
|
||||||
|
{
|
||||||
|
/* don't save lightmaps for "trigger" texture */
|
||||||
|
if (!Q_strcasecmp(texname, "trigger"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool surf_is_subdivided(const surfflags_t &flags) const override { return !(flags.native & TEX_SPECIAL); }
|
bool surf_is_subdivided(const surfflags_t &flags) const override { return !(flags.native & TEX_SPECIAL); }
|
||||||
|
|
||||||
|
|
@ -956,11 +976,26 @@ struct gamedef_q2_t : public gamedef_t
|
||||||
max_entity_key = 256;
|
max_entity_key = 256;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool surf_is_lightmapped(const surfflags_t &flags) const override
|
bool surf_is_lightmapped(const surfflags_t &flags, const char *texname, bool light_nodraw, bool lightgrid_enabled) const override
|
||||||
{
|
{ // Q2RTX should light nodraw faces
|
||||||
|
if (light_nodraw && (flags.native & Q2_SURF_NODRAW)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The only reason to lightmap sky faces in Q2 is to light models floating over sky.
|
||||||
|
// If lightgrid is in use, this reason is no longer relevant, so skip lightmapping.
|
||||||
|
if (lightgrid_enabled && (flags.native & Q2_SURF_SKY)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return !(flags.native & (Q2_SURF_NODRAW | Q2_SURF_SKIP));
|
return !(flags.native & (Q2_SURF_NODRAW | Q2_SURF_SKIP));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool surf_is_emissive(const surfflags_t &flags, const char *texname) const override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool surf_is_subdivided(const surfflags_t &flags) const override { return !(flags.native & Q2_SURF_SKY); }
|
bool surf_is_subdivided(const surfflags_t &flags) const override { return !(flags.native & Q2_SURF_SKY); }
|
||||||
|
|
||||||
bool surfflags_are_valid(const surfflags_t &flags) const override
|
bool surfflags_are_valid(const surfflags_t &flags) const override
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,10 @@ struct gamedef_t
|
||||||
|
|
||||||
gamedef_t(const char *default_base_dir);
|
gamedef_t(const char *default_base_dir);
|
||||||
|
|
||||||
virtual bool surf_is_lightmapped(const surfflags_t &flags) const = 0;
|
// surface stores lightmap/luxel color data
|
||||||
|
virtual bool surf_is_lightmapped(const surfflags_t &flags, const char *texname, bool light_nodraw, bool lightgrid_enabled) const = 0;
|
||||||
|
// surface can be emissive
|
||||||
|
virtual bool surf_is_emissive(const surfflags_t &flags, const char *texname) const = 0;
|
||||||
virtual bool surf_is_subdivided(const surfflags_t &flags) const = 0;
|
virtual bool surf_is_subdivided(const surfflags_t &flags) const = 0;
|
||||||
virtual bool surfflags_are_valid(const surfflags_t &flags) const = 0;
|
virtual bool surfflags_are_valid(const surfflags_t &flags) const = 0;
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ struct lightsurf_t
|
||||||
qvec3d tnormal;
|
qvec3d tnormal;
|
||||||
|
|
||||||
/* 16 in vanilla. engines will hate you if this is not power-of-two-and-at-least-one */
|
/* 16 in vanilla. engines will hate you if this is not power-of-two-and-at-least-one */
|
||||||
float lightmapscale;
|
float lightmapscale = 0.f;
|
||||||
|
|
||||||
faceextents_t extents, vanilla_extents;
|
faceextents_t extents, vanilla_extents;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ void SetupDirt(settings::worldspawn_keys &cfg);
|
||||||
std::unique_ptr<lightsurf_t> CreateLightmapSurface(const mbsp_t *bsp, const mface_t *face, const facesup_t *facesup,
|
std::unique_ptr<lightsurf_t> CreateLightmapSurface(const mbsp_t *bsp, const mface_t *face, const facesup_t *facesup,
|
||||||
const bspx_decoupled_lm_perface *facesup_decoupled, const settings::worldspawn_keys &cfg);
|
const bspx_decoupled_lm_perface *facesup_decoupled, const settings::worldspawn_keys &cfg);
|
||||||
bool Face_IsLightmapped(const mbsp_t *bsp, const mface_t *face);
|
bool Face_IsLightmapped(const mbsp_t *bsp, const mface_t *face);
|
||||||
|
bool Face_IsEmissive(const mbsp_t *bsp, const mface_t *face);
|
||||||
void DirectLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const settings::worldspawn_keys &cfg);
|
void DirectLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const settings::worldspawn_keys &cfg);
|
||||||
void IndirectLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const settings::worldspawn_keys &cfg);
|
void IndirectLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const settings::worldspawn_keys &cfg);
|
||||||
void PostProcessLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const settings::worldspawn_keys &cfg);
|
void PostProcessLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const settings::worldspawn_keys &cfg);
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,10 @@ static void MakeBounceLight(const mbsp_t *bsp, const settings::worldspawn_keys &
|
||||||
qvec3d texture_color, int32_t style, const std::vector<qvec3f> &points, const winding_t &winding, const vec_t &area,
|
qvec3d texture_color, int32_t style, const std::vector<qvec3f> &points, const winding_t &winding, const vec_t &area,
|
||||||
const qvec3d &facenormal, const qvec3d &facemidpoint)
|
const qvec3d &facenormal, const qvec3d &facemidpoint)
|
||||||
{
|
{
|
||||||
|
if (!Face_IsEmissive(bsp, surf.face)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bouncelightpoints += points.size();
|
bouncelightpoints += points.size();
|
||||||
|
|
||||||
// Calculate emit color and intensity...
|
// Calculate emit color and intensity...
|
||||||
|
|
|
||||||
|
|
@ -709,7 +709,7 @@ static void SaveLightmapSurfaces(mbsp_t *bsp)
|
||||||
logging::parallel_for(static_cast<size_t>(0), bsp->dfaces.size(), [&bsp](size_t i) {
|
logging::parallel_for(static_cast<size_t>(0), bsp->dfaces.size(), [&bsp](size_t i) {
|
||||||
auto &surf = light_surfaces[i];
|
auto &surf = light_surfaces[i];
|
||||||
|
|
||||||
if (!surf) {
|
if (!surf || surf->samples.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -920,7 +920,7 @@ static void LightWorld(bspdata_t *bspdata, bool forcedscale)
|
||||||
|
|
||||||
logging::header("Direct Lighting"); // mxd
|
logging::header("Direct Lighting"); // mxd
|
||||||
logging::parallel_for(static_cast<size_t>(0), bsp.dfaces.size(), [&bsp](size_t i) {
|
logging::parallel_for(static_cast<size_t>(0), bsp.dfaces.size(), [&bsp](size_t i) {
|
||||||
if (light_surfaces[i]) {
|
if (light_surfaces[i] && Face_IsLightmapped(&bsp, &bsp.dfaces[i])) {
|
||||||
#if defined(HAVE_EMBREE) && defined(__SSE2__)
|
#if defined(HAVE_EMBREE) && defined(__SSE2__)
|
||||||
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
|
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -934,7 +934,7 @@ static void LightWorld(bspdata_t *bspdata, bool forcedscale)
|
||||||
|
|
||||||
logging::header("Indirect Lighting"); // mxd
|
logging::header("Indirect Lighting"); // mxd
|
||||||
logging::parallel_for(static_cast<size_t>(0), bsp.dfaces.size(), [&bsp](size_t i) {
|
logging::parallel_for(static_cast<size_t>(0), bsp.dfaces.size(), [&bsp](size_t i) {
|
||||||
if (light_surfaces[i]) {
|
if (light_surfaces[i] && Face_IsLightmapped(&bsp, &bsp.dfaces[i])) {
|
||||||
#if defined(HAVE_EMBREE) && defined(__SSE2__)
|
#if defined(HAVE_EMBREE) && defined(__SSE2__)
|
||||||
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
|
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -947,7 +947,7 @@ static void LightWorld(bspdata_t *bspdata, bool forcedscale)
|
||||||
if (!light_options.nolighting.value()) {
|
if (!light_options.nolighting.value()) {
|
||||||
logging::header("Post-Processing"); // mxd
|
logging::header("Post-Processing"); // mxd
|
||||||
logging::parallel_for(static_cast<size_t>(0), bsp.dfaces.size(), [&bsp](size_t i) {
|
logging::parallel_for(static_cast<size_t>(0), bsp.dfaces.size(), [&bsp](size_t i) {
|
||||||
if (light_surfaces[i]) {
|
if (light_surfaces[i] && Face_IsLightmapped(&bsp, &bsp.dfaces[i])) {
|
||||||
#if defined(HAVE_EMBREE) && defined(__SSE2__)
|
#if defined(HAVE_EMBREE) && defined(__SSE2__)
|
||||||
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
|
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
370
light/ltface.cc
370
light/ltface.cc
|
|
@ -589,150 +589,154 @@ static std::unique_ptr<lightsurf_t> Lightsurf_Init(const modelinfo_t *modelinfo,
|
||||||
lightsurf->bsp = bsp;
|
lightsurf->bsp = bsp;
|
||||||
lightsurf->face = face;
|
lightsurf->face = face;
|
||||||
|
|
||||||
/* if liquid doesn't have the TEX_SPECIAL flag set, the map was qbsp'ed with
|
if (Face_IsLightmapped(bsp, face)) {
|
||||||
* lit water in mind. In that case receive light from both top and bottom.
|
/* if liquid doesn't have the TEX_SPECIAL flag set, the map was qbsp'ed with
|
||||||
* (lit will only be rendered in compatible engines, but degrades gracefully.)
|
* lit water in mind. In that case receive light from both top and bottom.
|
||||||
*/
|
* (lit will only be rendered in compatible engines, but degrades gracefully.)
|
||||||
lightsurf->twosided = Face_IsTranslucent(bsp, face);
|
*/
|
||||||
|
lightsurf->twosided = Face_IsTranslucent(bsp, face);
|
||||||
|
|
||||||
// pick the larger of the two scales
|
// pick the larger of the two scales
|
||||||
lightsurf->lightmapscale =
|
lightsurf->lightmapscale =
|
||||||
(facesup && facesup->lmscale < modelinfo->lightmapscale) ? facesup->lmscale : modelinfo->lightmapscale;
|
(facesup && facesup->lmscale < modelinfo->lightmapscale) ? facesup->lmscale : modelinfo->lightmapscale;
|
||||||
|
|
||||||
const surfflags_t &extended_flags = extended_texinfo_flags[face->texinfo];
|
const surfflags_t &extended_flags = extended_texinfo_flags[face->texinfo];
|
||||||
lightsurf->curved = extended_flags.phong_angle != 0 || Q2_FacePhongValue(bsp, face);
|
lightsurf->curved = extended_flags.phong_angle != 0 || Q2_FacePhongValue(bsp, face);
|
||||||
|
|
||||||
// nodirt
|
// nodirt
|
||||||
if (modelinfo->dirt.is_changed()) {
|
if (modelinfo->dirt.is_changed()) {
|
||||||
lightsurf->nodirt = (modelinfo->dirt.value() == -1);
|
lightsurf->nodirt = (modelinfo->dirt.value() == -1);
|
||||||
} else {
|
} else {
|
||||||
lightsurf->nodirt = extended_flags.no_dirt;
|
lightsurf->nodirt = extended_flags.no_dirt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// minlight
|
// minlight
|
||||||
if (modelinfo->minlight.is_changed()) {
|
if (modelinfo->minlight.is_changed()) {
|
||||||
lightsurf->minlight = modelinfo->minlight.value();
|
lightsurf->minlight = modelinfo->minlight.value();
|
||||||
} else if (extended_flags.minlight) {
|
} else if (extended_flags.minlight) {
|
||||||
lightsurf->minlight = *extended_flags.minlight;
|
lightsurf->minlight = *extended_flags.minlight;
|
||||||
} else {
|
} else {
|
||||||
lightsurf->minlight = light_options.minlight.value();
|
lightsurf->minlight = light_options.minlight.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
// minlightMottle
|
// minlightMottle
|
||||||
if (modelinfo->minlightMottle.is_changed()) {
|
if (modelinfo->minlightMottle.is_changed()) {
|
||||||
lightsurf->minlightMottle = modelinfo->minlightMottle.value();
|
lightsurf->minlightMottle = modelinfo->minlightMottle.value();
|
||||||
} else if (light_options.minlightMottle.is_changed()) {
|
} else if (light_options.minlightMottle.is_changed()) {
|
||||||
lightsurf->minlightMottle = light_options.minlightMottle.value();
|
lightsurf->minlightMottle = light_options.minlightMottle.value();
|
||||||
} else {
|
} else {
|
||||||
// default value depends on game
|
// default value depends on game
|
||||||
|
if (bsp->loadversion->game->id == GAME_QUAKE_II) {
|
||||||
|
lightsurf->minlightMottle = true;
|
||||||
|
} else {
|
||||||
|
lightsurf->minlightMottle = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Q2 uses a 0-1 range for minlight
|
||||||
if (bsp->loadversion->game->id == GAME_QUAKE_II) {
|
if (bsp->loadversion->game->id == GAME_QUAKE_II) {
|
||||||
lightsurf->minlightMottle = true;
|
lightsurf->minlight *= 128.f;
|
||||||
} else {
|
|
||||||
lightsurf->minlightMottle = false;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Q2 uses a 0-1 range for minlight
|
// maxlight
|
||||||
if (bsp->loadversion->game->id == GAME_QUAKE_II) {
|
if (modelinfo->maxlight.is_changed()) {
|
||||||
lightsurf->minlight *= 128.f;
|
lightsurf->maxlight = modelinfo->maxlight.value();
|
||||||
}
|
|
||||||
|
|
||||||
// maxlight
|
|
||||||
if (modelinfo->maxlight.is_changed()) {
|
|
||||||
lightsurf->maxlight = modelinfo->maxlight.value();
|
|
||||||
} else {
|
|
||||||
lightsurf->maxlight = extended_flags.maxlight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// lightcolorscale
|
|
||||||
if (modelinfo->lightcolorscale.is_changed()) {
|
|
||||||
lightsurf->lightcolorscale = modelinfo->lightcolorscale.value();
|
|
||||||
} else {
|
|
||||||
lightsurf->lightcolorscale = extended_flags.lightcolorscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Q2 uses a 0-1 range for minlight
|
|
||||||
if (bsp->loadversion->game->id == GAME_QUAKE_II) {
|
|
||||||
lightsurf->maxlight *= 128.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// minlight_color
|
|
||||||
if (modelinfo->minlight_color.is_changed()) {
|
|
||||||
lightsurf->minlight_color = modelinfo->minlight_color.value();
|
|
||||||
} else if (!qv::emptyExact(extended_flags.minlight_color)) {
|
|
||||||
lightsurf->minlight_color = extended_flags.minlight_color;
|
|
||||||
} else {
|
|
||||||
lightsurf->minlight_color = light_options.minlight_color.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* never receive dirtmapping on lit liquids */
|
|
||||||
if (Face_IsTranslucent(bsp, face)) {
|
|
||||||
lightsurf->nodirt = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* handle glass alpha */
|
|
||||||
if (modelinfo->alpha.value() < 1) {
|
|
||||||
/* skip culling of rays coming from the back side of the face */
|
|
||||||
lightsurf->twosided = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* object channel mask */
|
|
||||||
if (extended_flags.object_channel_mask) {
|
|
||||||
lightsurf->object_channel_mask = *extended_flags.object_channel_mask;
|
|
||||||
} else {
|
|
||||||
lightsurf->object_channel_mask = modelinfo->object_channel_mask.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extended_flags.surflight_minlight_scale) {
|
|
||||||
lightsurf->surflight_minlight_scale = *extended_flags.surflight_minlight_scale;
|
|
||||||
} else {
|
|
||||||
lightsurf->surflight_minlight_scale = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set up the plane, not including model offset */
|
|
||||||
qplane3d &plane = lightsurf->plane;
|
|
||||||
if (face->side) {
|
|
||||||
plane = -bsp->dplanes[face->planenum];
|
|
||||||
} else {
|
|
||||||
plane = bsp->dplanes[face->planenum];
|
|
||||||
}
|
|
||||||
|
|
||||||
const mtexinfo_t *tex = &bsp->texinfo[face->texinfo];
|
|
||||||
lightsurf->snormal = qv::normalize(tex->vecs.row(0).xyz());
|
|
||||||
lightsurf->tnormal = -qv::normalize(tex->vecs.row(1).xyz());
|
|
||||||
|
|
||||||
/* Set up the surface points */
|
|
||||||
if (light_options.world_units_per_luxel.is_changed()) {
|
|
||||||
if (bsp->loadversion->game->id == GAME_QUAKE_II && (Face_Texinfo(bsp, face)->flags.native & Q2_SURF_SKY)) {
|
|
||||||
lightsurf->extents = faceextents_t(*face, *bsp, world_units_per_luxel_t{}, 512.f);
|
|
||||||
} else if (extended_flags.world_units_per_luxel) {
|
|
||||||
lightsurf->extents =
|
|
||||||
faceextents_t(*face, *bsp, world_units_per_luxel_t{}, *extended_flags.world_units_per_luxel);
|
|
||||||
} else {
|
} else {
|
||||||
lightsurf->extents =
|
lightsurf->maxlight = extended_flags.maxlight;
|
||||||
faceextents_t(*face, *bsp, world_units_per_luxel_t{}, light_options.world_units_per_luxel.value());
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
lightsurf->extents = faceextents_t(*face, *bsp, lightsurf->lightmapscale);
|
// lightcolorscale
|
||||||
|
if (modelinfo->lightcolorscale.is_changed()) {
|
||||||
|
lightsurf->lightcolorscale = modelinfo->lightcolorscale.value();
|
||||||
|
} else {
|
||||||
|
lightsurf->lightcolorscale = extended_flags.lightcolorscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Q2 uses a 0-1 range for minlight
|
||||||
|
if (bsp->loadversion->game->id == GAME_QUAKE_II) {
|
||||||
|
lightsurf->maxlight *= 128.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// minlight_color
|
||||||
|
if (modelinfo->minlight_color.is_changed()) {
|
||||||
|
lightsurf->minlight_color = modelinfo->minlight_color.value();
|
||||||
|
} else if (!qv::emptyExact(extended_flags.minlight_color)) {
|
||||||
|
lightsurf->minlight_color = extended_flags.minlight_color;
|
||||||
|
} else {
|
||||||
|
lightsurf->minlight_color = light_options.minlight_color.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* never receive dirtmapping on lit liquids */
|
||||||
|
if (Face_IsTranslucent(bsp, face)) {
|
||||||
|
lightsurf->nodirt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle glass alpha */
|
||||||
|
if (modelinfo->alpha.value() < 1) {
|
||||||
|
/* skip culling of rays coming from the back side of the face */
|
||||||
|
lightsurf->twosided = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* object channel mask */
|
||||||
|
if (extended_flags.object_channel_mask) {
|
||||||
|
lightsurf->object_channel_mask = *extended_flags.object_channel_mask;
|
||||||
|
} else {
|
||||||
|
lightsurf->object_channel_mask = modelinfo->object_channel_mask.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extended_flags.surflight_minlight_scale) {
|
||||||
|
lightsurf->surflight_minlight_scale = *extended_flags.surflight_minlight_scale;
|
||||||
|
} else {
|
||||||
|
lightsurf->surflight_minlight_scale = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set up the plane, not including model offset */
|
||||||
|
qplane3d &plane = lightsurf->plane;
|
||||||
|
if (face->side) {
|
||||||
|
plane = -bsp->dplanes[face->planenum];
|
||||||
|
} else {
|
||||||
|
plane = bsp->dplanes[face->planenum];
|
||||||
|
}
|
||||||
|
|
||||||
|
const mtexinfo_t *tex = &bsp->texinfo[face->texinfo];
|
||||||
|
lightsurf->snormal = qv::normalize(tex->vecs.row(0).xyz());
|
||||||
|
lightsurf->tnormal = -qv::normalize(tex->vecs.row(1).xyz());
|
||||||
|
|
||||||
|
/* Set up the surface points */
|
||||||
|
if (light_options.world_units_per_luxel.is_changed()) {
|
||||||
|
if (bsp->loadversion->game->id == GAME_QUAKE_II && (Face_Texinfo(bsp, face)->flags.native & Q2_SURF_SKY)) {
|
||||||
|
lightsurf->extents = faceextents_t(*face, *bsp, world_units_per_luxel_t{}, 512.f);
|
||||||
|
} else if (extended_flags.world_units_per_luxel) {
|
||||||
|
lightsurf->extents =
|
||||||
|
faceextents_t(*face, *bsp, world_units_per_luxel_t{}, *extended_flags.world_units_per_luxel);
|
||||||
|
} else {
|
||||||
|
lightsurf->extents =
|
||||||
|
faceextents_t(*face, *bsp, world_units_per_luxel_t{}, light_options.world_units_per_luxel.value());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lightsurf->extents = faceextents_t(*face, *bsp, lightsurf->lightmapscale);
|
||||||
|
}
|
||||||
|
lightsurf->vanilla_extents = faceextents_t(*face, *bsp, LMSCALE_DEFAULT);
|
||||||
|
|
||||||
|
CalcPoints(modelinfo, modelinfo->offset, lightsurf.get(), bsp, face);
|
||||||
|
|
||||||
|
/* Correct the plane for the model offset (must be done last,
|
||||||
|
calculation of face extents / points needs the uncorrected plane) */
|
||||||
|
qvec3d planepoint = (plane.normal * plane.dist) + modelinfo->offset;
|
||||||
|
plane.dist = qv::dot(plane.normal, planepoint);
|
||||||
|
|
||||||
|
/* Correct bounding sphere */
|
||||||
|
lightsurf->extents.origin += modelinfo->offset;
|
||||||
|
lightsurf->extents.bounds = lightsurf->extents.bounds.translate(modelinfo->offset);
|
||||||
|
|
||||||
|
lightsurf->intersection_stream.resize(lightsurf->samples.size());
|
||||||
|
lightsurf->occlusion_stream.resize(lightsurf->samples.size());
|
||||||
|
|
||||||
|
/* Setup vis data */
|
||||||
|
CalcPvs(bsp, lightsurf.get());
|
||||||
}
|
}
|
||||||
lightsurf->vanilla_extents = faceextents_t(*face, *bsp, LMSCALE_DEFAULT);
|
|
||||||
|
|
||||||
CalcPoints(modelinfo, modelinfo->offset, lightsurf.get(), bsp, face);
|
// emissiveness is handled later and allocated only if necessary
|
||||||
|
|
||||||
/* Correct the plane for the model offset (must be done last,
|
|
||||||
calculation of face extents / points needs the uncorrected plane) */
|
|
||||||
qvec3d planepoint = (plane.normal * plane.dist) + modelinfo->offset;
|
|
||||||
plane.dist = qv::dot(plane.normal, planepoint);
|
|
||||||
|
|
||||||
/* Correct bounding sphere */
|
|
||||||
lightsurf->extents.origin += modelinfo->offset;
|
|
||||||
lightsurf->extents.bounds = lightsurf->extents.bounds.translate(modelinfo->offset);
|
|
||||||
|
|
||||||
lightsurf->intersection_stream.resize(lightsurf->samples.size());
|
|
||||||
lightsurf->occlusion_stream.resize(lightsurf->samples.size());
|
|
||||||
|
|
||||||
/* Setup vis data */
|
|
||||||
CalcPvs(bsp, lightsurf.get());
|
|
||||||
|
|
||||||
return lightsurf;
|
return lightsurf;
|
||||||
}
|
}
|
||||||
|
|
@ -802,8 +806,10 @@ static void Lightmap_ClearAll(lightmapdict_t *lightmaps)
|
||||||
* otherwise emit a warning.
|
* otherwise emit a warning.
|
||||||
*/
|
*/
|
||||||
static void Lightmap_Save(
|
static void Lightmap_Save(
|
||||||
lightmapdict_t *lightmaps, const lightsurf_t *lightsurf, lightmap_t *lightmap, const int style)
|
const mbsp_t *bsp, lightmapdict_t *lightmaps, const lightsurf_t *lightsurf, lightmap_t *lightmap, const int style)
|
||||||
{
|
{
|
||||||
|
Q_assert(Face_IsLightmapped(bsp, lightsurf->face));
|
||||||
|
|
||||||
if (lightmap->style == INVALID_LIGHTSTYLE) {
|
if (lightmap->style == INVALID_LIGHTSTYLE) {
|
||||||
lightmap->style = style;
|
lightmap->style = style;
|
||||||
}
|
}
|
||||||
|
|
@ -1294,7 +1300,7 @@ static void LightFace_Entity(
|
||||||
sample.color += rs.getPushedRayColor(j);
|
sample.color += rs.getPushedRayColor(j);
|
||||||
sample.direction += rs.getPushedRayNormalContrib(j);
|
sample.direction += rs.getPushedRayNormalContrib(j);
|
||||||
|
|
||||||
Lightmap_Save(lightmaps, lightsurf, cached_lightmap, cached_style);
|
Lightmap_Save(bsp, lightmaps, lightsurf, cached_lightmap, cached_style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1360,7 +1366,7 @@ static void LightPoint_Entity(const mbsp_t *bsp, raystream_occlusion_t &rs, cons
|
||||||
* LightFace_Sky
|
* LightFace_Sky
|
||||||
* =============
|
* =============
|
||||||
*/
|
*/
|
||||||
static void LightFace_Sky(const sun_t *sun, lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
static void LightFace_Sky(const mbsp_t *bsp, const sun_t *sun, lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
||||||
{
|
{
|
||||||
const settings::worldspawn_keys &cfg = *lightsurf->cfg;
|
const settings::worldspawn_keys &cfg = *lightsurf->cfg;
|
||||||
const modelinfo_t *modelinfo = lightsurf->modelinfo;
|
const modelinfo_t *modelinfo = lightsurf->modelinfo;
|
||||||
|
|
@ -1467,7 +1473,7 @@ static void LightFace_Sky(const sun_t *sun, lightsurf_t *lightsurf, lightmapdict
|
||||||
sample.direction += rs.getPushedRayNormalContrib(j);
|
sample.direction += rs.getPushedRayNormalContrib(j);
|
||||||
total_light_ray_hits++;
|
total_light_ray_hits++;
|
||||||
|
|
||||||
Lightmap_Save(lightmaps, lightsurf, cached_lightmap, cached_style);
|
Lightmap_Save(bsp, lightmaps, lightsurf, cached_lightmap, cached_style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1672,7 +1678,7 @@ static void LightFace_Min(const mbsp_t *bsp, const mface_t *face, const qvec3d &
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hit) {
|
if (hit) {
|
||||||
Lightmap_Save(lightmaps, lightsurf, lightmap, style);
|
Lightmap_Save(bsp, lightmaps, lightsurf, lightmap, style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1753,7 +1759,7 @@ static void LightFace_LocalMin(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hit) {
|
if (hit) {
|
||||||
Lightmap_Save(lightmaps, lightsurf, lightmap, entity->style.value());
|
Lightmap_Save(bsp, lightmaps, lightsurf, lightmap, entity->style.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1818,7 +1824,7 @@ static void LightFace_AutoMin(const mbsp_t *bsp, const mface_t *face, lightsurf_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Lightmap_Save(lightmaps, lightsurf, lightmap, grid_sample.style);
|
Lightmap_Save(bsp, lightmaps, lightsurf, lightmap, grid_sample.style);
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear occluded state, since we filled in all occluded samples with a color
|
// clear occluded state, since we filled in all occluded samples with a color
|
||||||
|
|
@ -1833,7 +1839,7 @@ static void LightFace_AutoMin(const mbsp_t *bsp, const mface_t *face, lightsurf_
|
||||||
* LightFace_DirtDebug
|
* LightFace_DirtDebug
|
||||||
* =============
|
* =============
|
||||||
*/
|
*/
|
||||||
static void LightFace_DirtDebug(const lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
static void LightFace_DirtDebug(const mbsp_t *bsp, const lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
||||||
{
|
{
|
||||||
const settings::worldspawn_keys &cfg = *lightsurf->cfg;
|
const settings::worldspawn_keys &cfg = *lightsurf->cfg;
|
||||||
/* use a style 0 light map */
|
/* use a style 0 light map */
|
||||||
|
|
@ -1846,7 +1852,7 @@ static void LightFace_DirtDebug(const lightsurf_t *lightsurf, lightmapdict_t *li
|
||||||
sample.color = {light};
|
sample.color = {light};
|
||||||
}
|
}
|
||||||
|
|
||||||
Lightmap_Save(lightmaps, lightsurf, lightmap, 0);
|
Lightmap_Save(bsp, lightmaps, lightsurf, lightmap, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -1854,7 +1860,7 @@ static void LightFace_DirtDebug(const lightsurf_t *lightsurf, lightmapdict_t *li
|
||||||
* LightFace_PhongDebug
|
* LightFace_PhongDebug
|
||||||
* =============
|
* =============
|
||||||
*/
|
*/
|
||||||
static void LightFace_PhongDebug(const lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
static void LightFace_PhongDebug(const mbsp_t *bsp, const lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
||||||
{
|
{
|
||||||
/* use a style 0 light map */
|
/* use a style 0 light map */
|
||||||
lightmap_t *lightmap = Lightmap_ForStyle(lightmaps, 0, lightsurf);
|
lightmap_t *lightmap = Lightmap_ForStyle(lightmaps, 0, lightsurf);
|
||||||
|
|
@ -1870,10 +1876,10 @@ static void LightFace_PhongDebug(const lightsurf_t *lightsurf, lightmapdict_t *l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Lightmap_Save(lightmaps, lightsurf, lightmap, 0);
|
Lightmap_Save(bsp, lightmaps, lightsurf, lightmap, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void LightFace_DebugMottle(const lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
static void LightFace_DebugMottle(const mbsp_t *bsp, const lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
||||||
{
|
{
|
||||||
/* use a style 0 light map */
|
/* use a style 0 light map */
|
||||||
lightmap_t *lightmap = Lightmap_ForStyle(lightmaps, 0, lightsurf);
|
lightmap_t *lightmap = Lightmap_ForStyle(lightmaps, 0, lightsurf);
|
||||||
|
|
@ -1887,7 +1893,7 @@ static void LightFace_DebugMottle(const lightsurf_t *lightsurf, lightmapdict_t *
|
||||||
sample.color = qvec3f(minlight + Mottle(lightsurf->samples[i].point));
|
sample.color = qvec3f(minlight + Mottle(lightsurf->samples[i].point));
|
||||||
}
|
}
|
||||||
|
|
||||||
Lightmap_Save(lightmaps, lightsurf, lightmap, 0);
|
Lightmap_Save(bsp, lightmaps, lightsurf, lightmap, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// mxd. Surface light falloff. Returns color in [0,255]
|
// mxd. Surface light falloff. Returns color in [0,255]
|
||||||
|
|
@ -2067,7 +2073,7 @@ LightFace_SurfaceLight(const mbsp_t *bsp, lightsurf_t *lightsurf, lightmapdict_t
|
||||||
|
|
||||||
// If surface light contributed anything, save.
|
// If surface light contributed anything, save.
|
||||||
if (hit)
|
if (hit)
|
||||||
Lightmap_Save(lightmaps, lightsurf, lightmap, lightmapstyle);
|
Lightmap_Save(bsp, lightmaps, lightsurf, lightmap, lightmapstyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2157,7 +2163,7 @@ LightPoint_SurfaceLight(const mbsp_t *bsp, const std::vector<uint8_t> *pvs, rays
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void LightFace_OccludedDebug(lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
static void LightFace_OccludedDebug(const mbsp_t *bsp, lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
||||||
{
|
{
|
||||||
Q_assert(light_options.debugmode == debugmodes::debugoccluded);
|
Q_assert(light_options.debugmode == debugmodes::debugoccluded);
|
||||||
|
|
||||||
|
|
@ -2177,10 +2183,10 @@ static void LightFace_OccludedDebug(lightsurf_t *lightsurf, lightmapdict_t *ligh
|
||||||
surf_sample.occluded = false;
|
surf_sample.occluded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Lightmap_Save(lightmaps, lightsurf, lightmap, 0);
|
Lightmap_Save(bsp, lightmaps, lightsurf, lightmap, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void LightFace_DebugNeighbours(lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
static void LightFace_DebugNeighbours(const mbsp_t *bsp, lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
|
||||||
{
|
{
|
||||||
Q_assert(light_options.debugmode == debugmodes::debugneighbours);
|
Q_assert(light_options.debugmode == debugmodes::debugneighbours);
|
||||||
|
|
||||||
|
|
@ -2219,7 +2225,7 @@ static void LightFace_DebugNeighbours(lightsurf_t *lightsurf, lightmapdict_t *li
|
||||||
lightsurf->samples[i].occluded = false;
|
lightsurf->samples[i].occluded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Lightmap_Save(lightmaps, lightsurf, lightmap, 0);
|
Lightmap_Save(bsp, lightmaps, lightsurf, lightmap, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dirtmapping borrowed from q3map2, originally by RaP7oR */
|
/* Dirtmapping borrowed from q3map2, originally by RaP7oR */
|
||||||
|
|
@ -2828,28 +2834,34 @@ static std::vector<qvec4f> BoxBlurImage(const std::vector<qvec4f> &input, int w,
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if the given face can actually store luxel data
|
||||||
bool Face_IsLightmapped(const mbsp_t *bsp, const mface_t *face)
|
bool Face_IsLightmapped(const mbsp_t *bsp, const mface_t *face)
|
||||||
{
|
{
|
||||||
const mtexinfo_t *texinfo = Face_Texinfo(bsp, face);
|
const mtexinfo_t *texinfo = Face_Texinfo(bsp, face);
|
||||||
|
|
||||||
if (texinfo == nullptr)
|
if (texinfo == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Q2RTX should light nodraw faces
|
|
||||||
if (light_options.q2rtx.value() && bsp->loadversion->game->id == GAME_QUAKE_II &&
|
|
||||||
(texinfo->flags.native & Q2_SURF_NODRAW)) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Very specific hack: the only reason to lightmap sky faces in Q2 is to light mdl's floating over sky.
|
const char *texname = Face_TextureName(bsp, face);
|
||||||
// If lightgrid is in use, this reason is no longer relevant, so skip lightmapping.
|
|
||||||
// TEMP DISABLED
|
|
||||||
/*if (light_options.lightgrid.value() && bsp->loadversion->game->id == GAME_QUAKE_II &&
|
|
||||||
(texinfo->flags.native & Q2_SURF_SKY)) {
|
|
||||||
return false;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return bsp->loadversion->game->surf_is_lightmapped(texinfo->flags);
|
return bsp->loadversion->game->surf_is_lightmapped(texinfo->flags, texname,
|
||||||
|
light_options.q2rtx.value() && bsp->loadversion->game->id == GAME_QUAKE_II, // FIXME: move to own config option. -light_nodraw?
|
||||||
|
light_options.lightgrid.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the given face can actually emit light
|
||||||
|
bool Face_IsEmissive(const mbsp_t *bsp, const mface_t *face)
|
||||||
|
{
|
||||||
|
const mtexinfo_t *texinfo = Face_Texinfo(bsp, face);
|
||||||
|
|
||||||
|
if (texinfo == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *texname = Face_TextureName(bsp, face);
|
||||||
|
|
||||||
|
return bsp->loadversion->game->surf_is_emissive(texinfo->flags, texname);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -3222,8 +3234,6 @@ void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup,
|
||||||
{
|
{
|
||||||
const char *texname = Face_TextureName(bsp, face);
|
const char *texname = Face_TextureName(bsp, face);
|
||||||
Q_assert(Face_IsLightmapped(bsp, face));
|
Q_assert(Face_IsLightmapped(bsp, face));
|
||||||
Q_assert(Q_strcasecmp(texname, "skip") != 0);
|
|
||||||
Q_assert(Q_strcasecmp(texname, "trigger") != 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int mapnum = 0; mapnum < numstyles; mapnum++) {
|
for (int mapnum = 0; mapnum < numstyles; mapnum++) {
|
||||||
|
|
@ -3287,18 +3297,10 @@ std::unique_ptr<lightsurf_t> CreateLightmapSurface(const mbsp_t *bsp, const mfac
|
||||||
if (face->numedges < 3)
|
if (face->numedges < 3)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
if (!Face_IsLightmapped(bsp, face))
|
// if we don't have a lightmap or emissive, we're done
|
||||||
return nullptr;
|
if (!Face_IsEmissive(bsp, face) && !Face_IsLightmapped(bsp, face)) {
|
||||||
|
|
||||||
const char *texname = Face_TextureName(bsp, face);
|
|
||||||
|
|
||||||
/* don't save lightmaps for "trigger" texture */
|
|
||||||
if (!Q_strcasecmp(texname, "trigger"))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
/* don't save lightmaps for "skip" texture */
|
|
||||||
if (!Q_strcasecmp(texname, "skip"))
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
return Lightsurf_Init(modelinfo, cfg, face, bsp, facesup, facesup_decoupled);
|
return Lightsurf_Init(modelinfo, cfg, face, bsp, facesup, facesup_decoupled);
|
||||||
}
|
}
|
||||||
|
|
@ -3343,7 +3345,7 @@ void DirectLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const settings::
|
||||||
}
|
}
|
||||||
for (const sun_t &sun : GetSuns())
|
for (const sun_t &sun : GetSuns())
|
||||||
if (sun.sunlight > 0)
|
if (sun.sunlight > 0)
|
||||||
LightFace_Sky(&sun, &lightsurf, lightmaps);
|
LightFace_Sky(bsp, &sun, &lightsurf, lightmaps);
|
||||||
|
|
||||||
// mxd. Add surface lights...
|
// mxd. Add surface lights...
|
||||||
// FIXME: negative surface lights
|
// FIXME: negative surface lights
|
||||||
|
|
@ -3356,16 +3358,16 @@ void DirectLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const settings::
|
||||||
|
|
||||||
/* replace lightmaps with AO for debugging */
|
/* replace lightmaps with AO for debugging */
|
||||||
if (light_options.debugmode == debugmodes::dirt)
|
if (light_options.debugmode == debugmodes::dirt)
|
||||||
LightFace_DirtDebug(&lightsurf, lightmaps);
|
LightFace_DirtDebug(bsp, &lightsurf, lightmaps);
|
||||||
|
|
||||||
if (light_options.debugmode == debugmodes::phong)
|
if (light_options.debugmode == debugmodes::phong)
|
||||||
LightFace_PhongDebug(&lightsurf, lightmaps);
|
LightFace_PhongDebug(bsp, &lightsurf, lightmaps);
|
||||||
|
|
||||||
if (light_options.debugmode == debugmodes::debugoccluded)
|
if (light_options.debugmode == debugmodes::debugoccluded)
|
||||||
LightFace_OccludedDebug(&lightsurf, lightmaps);
|
LightFace_OccludedDebug(bsp, &lightsurf, lightmaps);
|
||||||
|
|
||||||
if (light_options.debugmode == debugmodes::debugneighbours)
|
if (light_options.debugmode == debugmodes::debugneighbours)
|
||||||
LightFace_DebugNeighbours(&lightsurf, lightmaps);
|
LightFace_DebugNeighbours(bsp, &lightsurf, lightmaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -3457,12 +3459,12 @@ void PostProcessLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const setti
|
||||||
}
|
}
|
||||||
for (const sun_t &sun : GetSuns())
|
for (const sun_t &sun : GetSuns())
|
||||||
if (sun.sunlight < 0)
|
if (sun.sunlight < 0)
|
||||||
LightFace_Sky(&sun, &lightsurf, lightmaps);
|
LightFace_Sky(bsp, &sun, &lightsurf, lightmaps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (light_options.debugmode == debugmodes::mottle)
|
if (light_options.debugmode == debugmodes::mottle)
|
||||||
LightFace_DebugMottle(&lightsurf, lightmaps);
|
LightFace_DebugMottle(bsp, &lightsurf, lightmaps);
|
||||||
}
|
}
|
||||||
// lightgrid
|
// lightgrid
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ static void MakeSurfaceLight(const mbsp_t *bsp, const settings::worldspawn_keys
|
||||||
{
|
{
|
||||||
auto &surf_ptr = LightSurfaces()[face - bsp->dfaces.data()];
|
auto &surf_ptr = LightSurfaces()[face - bsp->dfaces.data()];
|
||||||
|
|
||||||
if (!surf_ptr) {
|
if (!surf_ptr || !Face_IsEmissive(bsp, face)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue