Merge branch 'Garux:master' into master

This commit is contained in:
KG7x 2022-03-18 19:05:05 +03:00 committed by GitHub
commit 5569544508
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 255 additions and 106 deletions

View File

@ -61,7 +61,7 @@
<p>Since distance clipping is not a feature natively built into Quake III Arena, using distance culling would result in a hall of mirrors (HOM) effect where the culled geometry begins, since nothing is being drawn in the frame buffer. To compensate for this, the foghull feature uses a series of six skybox images that are drawn in place of the absent culled geometry, thus preventing the HOM effect.</p>
<h2>Skybox Images</h2>
<p>The skybox images used with the foghull feature should never be actually seen since the idea is to use the fog to obsure the maximum distance that the player can see. It only exists to prevent the HOM effect. To pull this off in a convincing manner, the skybox images should be six identical 8x8 pixel (to save on texture memory) textures each filled with a flat color matching the exact color of the fog. The six skybox images must be named in accordance to the skyParms farbox convention, using the _ft, _rt, _bk, _lf, _up, _dn suffixes.</p>
<p>The skybox images used with the foghull feature should never be actually seen since the idea is to use the fog to obscure the maximum distance that the player can see. It only exists to prevent the HOM effect. To pull this off in a convincing manner, the skybox images should be six identical 8x8 pixel (to save on texture memory) textures each filled with a flat color matching the exact color of the fog. The six skybox images must be named in accordance to the skyParms farbox convention, using the _ft, _rt, _bk, _lf, _up, _dn suffixes.</p>
<h2>Foghull Shaders</h2>
<p>Two shaders are required when using the foghull feature, a fog volume shader and a skybox shader, both of which are simple, standard shaders.</p>

View File

@ -391,8 +391,14 @@ textures/eerie/ironcrosslt2_10000
<h2 id="q3map_shadeAngle">q3map_shadeAngle angle</h2>
<p>Specifies the breaking angle for phong shading. This allows for smooth shadows between brush faces like patches. The angle parameter is the angle between adjacent faces at which smoothing will start to occur. Typical values are usually in the 120-179 range.</p>
<h2 id="q3map_skylight">q3map_skylight amount iterations</h2>
<p>This replaces q3map_surfacelight and q3map_lightSubdivide on sky surfaces for much faster and more uniform sky illumination. Amount is a brightness value, similar to what you would use in q3map_sun. Good values are between 50 and 200. Iterations is an exponential factor. 3 is the best value that balances speed and quality. Values of 4 and 5 are higher quality at the expense of higher compile time. Values below 3 are not too useful. See Appendix: Light Emitting Shaders.</p>
<h2 id="q3map_skylight">q3map_skylight <em>amount iterations</em> optional[<em>horizon_min horizon_max sample_color</em>]</h2>
<p>This replaces q3map_surfacelight and q3map_lightSubdivide on sky surfaces for much faster and more uniform sky illumination. May be used multiple times in a single shader. See Appendix: Light Emitting Shaders.</p>
<dl>
<dt>amount</dt><dd>Brightness value, similar to what you would use in q3map_sun. Good values are between 50 and 200.</dd>
<dt>iterations</dt><dd>An exponential factor. 3 is the best value that balances speed and quality. Values of 4 and 5 are higher quality at the expense of higher compile time. Values below 3 are not too useful.</dd>
<dt>horizon_min horizon_max</dt><dd>Two spherical angles, defining portion of sphere to emit light from. Default is <em>0 90</em>, which is upper hemisphere. <em>-90 90</em> will be whole sphere.</dd>
<dt>sample_color</dt><dd>Default = 1: sample color of each individual light from skybox images, if they are present. 0: use shader color, set by q3map_lightRGB/q3map_lightImage/_up skybox image/qer_editorImage.</dd>
</dl>
<h2 id="q3map_splotchFix">q3map_splotchFix</h2>
<p>This is used on lightmapped model shaders if splotched lighting artifacts appear. Any shadows at the ambient/dark level will be flooded from neighbouring luxels. This gets rid of shadow acne, but a surface must be more or less uniformly lit or this looks ugly. Try using q3map_lightmapSampleOffset first before using this as a last resort.</p>

View File

@ -58,7 +58,7 @@ void Map_Snapshot(){
if ( file_exists( snapshotsDir.c_str() ) || Q_mkdir( snapshotsDir.c_str() ) ) {
std::size_t lSize = 0;
const auto strNewPath = StringOutputStream( 256 )( snapshotsDir.c_str(), '/', mapname );
const auto strNewPath = StringOutputStream( 256 )( snapshotsDir.c_str(), '/', path_get_filename_start( mapname ) );
const char* ext = path_get_filename_base_end( strNewPath.c_str() );
StringOutputStream snapshotFilename( 256 );

View File

@ -44,6 +44,7 @@ void UnGetToken();
/// \brief
/// \return true, if there is another token on the line.
/// Warning: 2nd of two sequential calls can cross the line.
bool TokenAvailable();
/// \brief Parses next token and emits \c Error, if it's not equal to \p match.

View File

@ -171,7 +171,7 @@ Q3Map2 Version History + Changelog (Reverse Chronological Order)
ignored on sun traces
- Added BSP file size printout
- Filtering of any kind now disables adaptive supersampling on a per-light,
per-ightmap basis
per-lightmap basis
- Fixed another _minlight <-> styled light interaction bug (thanks pjw!)
@ -551,7 +551,7 @@ Known issues:
"_flare" "1" -- use default flare (different for each game)
"_flareshader" "path/to/flareshader" -- use a specific flare shader
Note: This only matters in SOF2/JK2 now. Make a light targetted (a spotlight)
to get it to aim the correct direction, otherwise it defaults to pointing
to get it to aim the correct direction, otherwise it defaults to pointing
downward. You cannot have omnidirectional flares
- Lightgrid size is automatically increased to accommodate large maps. The
MAX_MAP_LIGHTGRID error will never happen again
@ -662,7 +662,7 @@ Known issues:
q3map_surfacemodel <path to md3> <density in units> <odds of appearing>
<min scale> <max scale> <min angle> <max angle> <oriented>
The last flag is 1 or 0, and sets whether the model gets fitted to the
orientation of the surface. Not functional yet. See screenshots page for
shots of this in action.

View File

@ -38,58 +38,50 @@
this creates a sun light
*/
static void CreateSunLight( sun_t *sun ){
int i;
float photons, d, angle, elevation, da, de;
Vector3 direction;
/* dummy check */
if ( sun == NULL ) {
return;
}
static void CreateSunLight( sun_t& sun ){
/* fixup */
value_maximize( sun->numSamples, 1 );
value_maximize( sun.numSamples, 1 );
/* set photons */
photons = sun->photons / sun->numSamples;
const float photons = sun.photons / sun.numSamples;
/* create the right number of suns */
for ( i = 0; i < sun->numSamples; i++ )
for ( int i = 0; i < sun.numSamples; ++i )
{
/* calculate sun direction */
Vector3 direction;
if ( i == 0 ) {
direction = sun->direction;
direction = sun.direction;
}
else
{
/*
sun->direction[ 0 ] = cos( angle ) * cos( elevation );
sun->direction[ 1 ] = sin( angle ) * cos( elevation );
sun->direction[ 2 ] = sin( elevation );
sun.direction[ 0 ] = cos( angle ) * cos( elevation );
sun.direction[ 1 ] = sin( angle ) * cos( elevation );
sun.direction[ 2 ] = sin( elevation );
xz_dist = sqrt( x*x + z*z )
latitude = atan2( xz_dist, y ) * RADIANS
longitude = atan2( x, z ) * RADIANS
*/
d = sqrt( sun->direction[ 0 ] * sun->direction[ 0 ] + sun->direction[ 1 ] * sun->direction[ 1 ] );
angle = atan2( sun->direction[ 1 ], sun->direction[ 0 ] );
elevation = atan2( sun->direction[ 2 ], d );
const double d = sqrt( sun.direction[ 0 ] * sun.direction[ 0 ] + sun.direction[ 1 ] * sun.direction[ 1 ] );
double angle = atan2( sun.direction[ 1 ], sun.direction[ 0 ] );
double elevation = atan2( sun.direction[ 2 ], d );
/* jitter the angles (loop to keep random sample within sun->deviance steridians) */
/* jitter the angles (loop to keep random sample within sun.deviance steridians) */
float da, de;
do
{
da = ( Random() * 2.0f - 1.0f ) * sun->deviance;
de = ( Random() * 2.0f - 1.0f ) * sun->deviance;
da = ( Random() * 2.0f - 1.0f ) * sun.deviance;
de = ( Random() * 2.0f - 1.0f ) * sun.deviance;
}
while ( ( da * da + de * de ) > ( sun->deviance * sun->deviance ) );
while ( ( da * da + de * de ) > ( sun.deviance * sun.deviance ) );
angle += da;
elevation += de;
/* debug code */
//% Sys_Printf( "%d: Angle: %3.4lf Elevation: %3.3lf\n", sun->numSamples, radians_to_degrees( angle ), radians_to_degrees( elevation ) );
//% Sys_Printf( "%d: Angle: %3.4lf Elevation: %3.3lf\n", sun.numSamples, radians_to_degrees( angle ), radians_to_degrees( elevation ) );
/* create new vector */
direction = vector3_for_spherical( angle, elevation );
@ -104,8 +96,8 @@ static void CreateSunLight( sun_t *sun ){
light.type = ELightType::Sun;
light.fade = 1.0f;
light.falloffTolerance = falloffTolerance;
light.filterRadius = sun->filterRadius / sun->numSamples;
light.style = noStyles ? LS_NORMAL : sun->style;
light.filterRadius = sun.filterRadius / sun.numSamples;
light.style = noStyles ? LS_NORMAL : sun.style;
/* set the light's position out to infinity */
light.origin = direction * ( MAX_WORLD_COORD * 8.0f ); /* MAX_WORLD_COORD * 2.0f */
@ -115,65 +107,163 @@ static void CreateSunLight( sun_t *sun ){
light.dist = vector3_dot( light.origin, light.normal );
/* set color and photons */
light.color = sun->color;
light.color = sun.color;
light.photons = photons * skyScale;
}
/* another sun? */
if ( sun->next != NULL ) {
CreateSunLight( sun->next );
}
}
class SkyProbes
{
struct SkyProbe
{
Vector3 color;
Vector3 normal;
};
std::vector<SkyProbe> m_probes;
public:
SkyProbes() = default;
SkyProbes( const String64& skyParmsImageBase ){
if( !skyParmsImageBase.empty() ){
std::vector<const image_t*> images;
for( const auto suffix : { "_lf", "_rt", "_ft", "_bk", "_up", "_dn" } )
{
if( nullptr == images.emplace_back( ImageLoad( StringOutputStream( 64 )( skyParmsImageBase, suffix ) ) ) ){
Sys_Warning( "Couldn't find image %s\n", StringOutputStream( 64 )( skyParmsImageBase, suffix ).c_str() );
return;
}
}
const size_t res = 64;
m_probes.reserve( res * res * 6 );
/* Q3 skybox (top view)
^Y
|
bk
lf rt ->X
ft
*/
// postrotate everything from _rt (+x)
const std::array<Matrix4, 6> transforms{ matrix4_scale_for_vec3( Vector3( -1, -1, 1 ) ),
g_matrix4_identity,
matrix4_rotation_for_sincos_z( -1, 0 ),
matrix4_rotation_for_sincos_z( 1, 0 ),
matrix4_rotation_for_sincos_y( -1, 0 ),
matrix4_rotation_for_sincos_y( 1, 0 ) };
/* img
0-----> width / U
|he
|ig
|ht
V
*/
for( size_t i = 0; i < 6; ++i )
{
for( size_t u = 0; u < res; ++u )
{
for( size_t v = 0; v < res; ++v )
{
const Vector3 normal = vector3_normalised( Vector3( 1, 1 - u * 2.f / res, 1 - v * 2.f / res ) );
Vector3 color( 0 );
{
const image_t& img = *images[i];
const size_t w = img.width * u / res;
const size_t w2 = std::max( w + 1, img.width * ( u + 1 ) / res );
const size_t h = img.height * v / res;
const size_t h2 = std::max( h + 1, img.height * ( v + 1 ) / res );
for( size_t iw = w; iw < w2; ++iw )
{
for( size_t ih = h; ih < h2; ++ih )
{
color += vector3_from_array( img.at( iw, ih ) );
}
}
color /= ( ( w2 - w ) * ( h2 - h ) );
}
color *= vector3_dot( normal, g_vector3_axis_x );
m_probes.push_back( SkyProbe{ color / 255, matrix4_transformed_direction( transforms[i], normal ) } );
}
}
}
}
}
Vector3 sampleColour( const Vector3& direction, const Vector3& limitDirection ) const {
Vector3 color( 0 );
float weightSum = 0;
for( const auto& probe : m_probes )
{
const double dot = vector3_dot( probe.normal, direction );
if( dot > 0 && vector3_dot( probe.normal, limitDirection ) >= 0 ){
color += probe.color * dot;
weightSum += dot;
}
}
return weightSum != 0? color / weightSum : color;
}
operator bool() const {
return !m_probes.empty();
}
};
/*
CreateSkyLights() - ydnar
simulates sky light with multiple suns
*/
static void CreateSkyLights( const Vector3& color, float value, int iterations, float filterRadius, int style ){
int i, j, numSuns;
int angleSteps, elevationSteps;
float angle, elevation;
float angleStep, elevationStep;
sun_t sun;
static void CreateSkyLights( const skylight_t& skylight, const Vector3& color, float filterRadius, int style, const String64& skyParmsImageBase ){
/* dummy check */
if ( value <= 0.0f || iterations < 2 ) {
if ( skylight.value <= 0.0f || skylight.iterations < 2 || skylight.horizon_min > skylight.horizon_max ) {
return;
}
const SkyProbes probes = skylight.sample_color? SkyProbes( skyParmsImageBase ) : SkyProbes();
const Vector3 probesLimitDirection = skylight.horizon_min >= 0? g_vector3_axis_z : skylight.horizon_max <= 0? -g_vector3_axis_z : Vector3( 0 );
/* basic sun setup */
sun_t sun;
std::vector<sun_t> suns;
sun.color = color;
sun.deviance = 0.0f;
sun.filterRadius = filterRadius;
sun.numSamples = 1;
sun.style = noStyles ? LS_NORMAL : style;
sun.next = NULL;
/* setup */
elevationSteps = iterations - 1;
angleSteps = elevationSteps * 4;
elevationStep = degrees_to_radians( 90.0f / iterations ); /* skip elevation 0 */
angleStep = degrees_to_radians( 360.0f / angleSteps );
const int doBot = ( skylight.horizon_min == -90 );
const int doTop = ( skylight.horizon_max == 90 );
const int angleSteps = ( skylight.iterations - 1 ) * 4;
const float eleStep = 90.0f / skylight.iterations;
const float elevationStep = degrees_to_radians( eleStep ); /* skip elevation 0 */
const float angleStep = degrees_to_radians( 360.0f / angleSteps );
// const int elevationSteps = skylight.iterations - 1;
const float eleMin = doBot? -90 + eleStep * 1.5 : skylight.horizon_min + eleStep * 0.5;
const float eleMax = doTop? 90 - eleStep * 1.5 : skylight.horizon_max - eleStep * 0.5;
const int elevationSteps = float_to_integer( 1 + std::max( 0.f, ( eleMax - eleMin ) / eleStep ) );
/* calc individual sun brightness */
numSuns = angleSteps * elevationSteps + 1;
sun.photons = value / numSuns;
const int numSuns = angleSteps * elevationSteps + doBot + doTop;
sun.photons = skylight.value / numSuns * std::max( .25, ( skylight.horizon_max - skylight.horizon_min ) / 90.0 );
suns.reserve( numSuns );
/* iterate elevation */
elevation = elevationStep * 0.5f;
angle = 0.0f;
for ( i = 0, elevation = elevationStep * 0.5f; i < elevationSteps; i++ )
float elevation = degrees_to_radians( std::min( eleMin, float( skylight.horizon_max ) ) );
float angle = 0.0f;
for ( int i = 0; i < elevationSteps; ++i )
{
/* iterate angle */
for ( j = 0; j < angleSteps; j++ )
for ( int j = 0; j < angleSteps; ++j )
{
/* create sun */
sun.direction = vector3_for_spherical( angle, elevation );
CreateSunLight( &sun );
if( probes )
sun.color = probes.sampleColour( sun.direction, probesLimitDirection );
suns.push_back( sun );
/* move */
angle += angleStep;
@ -184,12 +274,35 @@ static void CreateSkyLights( const Vector3& color, float value, int iterations,
angle += angleStep / elevationSteps;
}
/* create vertical sun */
sun.direction = g_vector3_axis_z;
CreateSunLight( &sun );
/* create vertical suns */
if( doBot ){
sun.direction = -g_vector3_axis_z;
if( probes )
sun.color = probes.sampleColour( sun.direction, probesLimitDirection );
suns.push_back( sun );
}
if( doTop ){
sun.direction = g_vector3_axis_z;
if( probes )
sun.color = probes.sampleColour( sun.direction, probesLimitDirection );
suns.push_back( sun );
}
/* short circuit */
return;
/* normalize colours */
if( probes ){
float max = 0;
for( const auto& sun : suns )
{
value_maximize( max, sun.color[0] );
value_maximize( max, sun.color[1] );
value_maximize( max, sun.color[2] );
}
if( max != 0 )
for( auto& sun : suns )
sun.color /= max;
}
std::for_each( suns.begin(), suns.end(), CreateSunLight );
}
@ -407,20 +520,19 @@ static void CreateEntityLights(){
numSpotLights--;
/* make a sun */
sun_t sun;
sun_t sun{};
sun.direction = -light.normal;
sun.color = light.color;
sun.photons = intensity;
sun.deviance = degrees_to_radians( deviance );
sun.numSamples = numSamples;
sun.style = noStyles ? LS_NORMAL : light.style;
sun.next = NULL;
/* free original light before sun insertion */
lights.pop_front();
/* make a sun light */
CreateSunLight( &sun );
CreateSunLight( sun );
/* skip the rest of this love story */
continue;
@ -482,17 +594,19 @@ static void CreateSurfaceLights(){
const shaderInfo_t *si = info->si;
/* sunlight? */
if ( si->sun != NULL && !nss ) {
if ( !si->suns.empty() && !nss ) {
shaderInfo_t* si_ = const_cast<shaderInfo_t*>( si ); /* FIXME: hack! */
Sys_FPrintf( SYS_VRB, "Sun: %s\n", si->shader.c_str() );
CreateSunLight( si->sun );
const_cast<shaderInfo_t*>( si )->sun = NULL; /* FIXME: leak! */
std::for_each( si_->suns.begin(), si_->suns.end(), CreateSunLight );
si_->suns.clear(); /* FIXME: hack! */
}
/* sky light? */
if ( si->skyLightValue > 0.0f ) {
if ( !si->skylights.empty() ) {
Sys_FPrintf( SYS_VRB, "Sky: %s\n", si->shader.c_str() );
CreateSkyLights( si->color, si->skyLightValue, si->skyLightIterations, si->lightFilterRadius, si->lightStyle );
const_cast<shaderInfo_t*>( si )->skyLightValue = 0.0f; /* FIXME: hack! */
for( const skylight_t& skylight : si->skylights )
CreateSkyLights( skylight, si->color, si->lightFilterRadius, si->lightStyle, si->skyParmsImageBase );
const_cast<shaderInfo_t*>( si )->skylights.clear(); /* FIXME: hack! */
}
/* try to early out */

View File

@ -383,7 +383,7 @@ struct image_t
name( name ),
filename( filename ),
width( width ),
height(height ),
height( height ),
pixels( pixels )
{}
image_t( image_t&& other ) noexcept :
@ -396,17 +396,29 @@ struct image_t
~image_t(){
free( pixels );
}
byte *at( int width, int height ) const {
return pixels + 4 * ( height * this->width + width );
}
};
struct sun_t
{
sun_t *next;
Vector3 direction, color;
float photons, deviance, filterRadius;
int numSamples, style;
};
struct skylight_t
{
float value;
int iterations;
int horizon_min = 0;
int horizon_max = 90;
bool sample_color = true;
};
struct surfaceModel_t
{
@ -559,9 +571,8 @@ struct shaderInfo_t
const image_t *lightImage;
const image_t *normalImage;
float skyLightValue; /* ydnar */
int skyLightIterations; /* ydnar */
sun_t *sun; /* ydnar */
std::vector<skylight_t> skylights; /* ydnar */
std::vector<sun_t> suns; /* ydnar */
Vector3 color{ 0 }; /* normalized color */
Color4f averageColor = { 0, 0, 0, 0 };

View File

@ -1075,36 +1075,33 @@ static void ParseShaderFile( const char *filename ){
degree of 0 = from the east, 90 = north, etc. altitude of 0 = sunrise/set, 90 = noon
ydnar: sof2map has bareword 'sun' token, so we support that as well */
else if ( striEqual( token, "sun" ) /* sof2 */ || striEqual( token, "q3map_sun" ) || striEqual( token, "q3map_sunExt" ) ) {
sun_t *sun;
sun_t& sun = si->suns.emplace_back();
/* ydnar: extended sun directive? */
const bool ext = striEqual( token, "q3map_sunext" );
/* allocate sun */
sun = safe_calloc( sizeof( *sun ) );
/* set style */
sun->style = si->lightStyle;
sun.style = si->lightStyle;
/* get color */
text.GetToken( false );
sun->color[ 0 ] = atof( token );
sun.color[ 0 ] = atof( token );
text.GetToken( false );
sun->color[ 1 ] = atof( token );
sun.color[ 1 ] = atof( token );
text.GetToken( false );
sun->color[ 2 ] = atof( token );
sun.color[ 2 ] = atof( token );
if ( colorsRGB ) {
sun->color[0] = Image_LinearFloatFromsRGBFloat( sun->color[0] );
sun->color[1] = Image_LinearFloatFromsRGBFloat( sun->color[1] );
sun->color[2] = Image_LinearFloatFromsRGBFloat( sun->color[2] );
sun.color[0] = Image_LinearFloatFromsRGBFloat( sun.color[0] );
sun.color[1] = Image_LinearFloatFromsRGBFloat( sun.color[1] );
sun.color[2] = Image_LinearFloatFromsRGBFloat( sun.color[2] );
}
/* normalize it */
ColorNormalize( sun->color );
ColorNormalize( sun.color );
/* scale color by brightness */
text.GetToken( false );
sun->photons = atof( token );
sun.photons = atof( token );
/* get sun angle/elevation */
text.GetToken( false );
@ -1113,24 +1110,20 @@ static void ParseShaderFile( const char *filename ){
text.GetToken( false );
const double b = degrees_to_radians( atof( token ) );
sun->direction = vector3_for_spherical( a, b );
sun.direction = vector3_for_spherical( a, b );
/* get filter radius from shader */
sun->filterRadius = si->lightFilterRadius;
sun.filterRadius = si->lightFilterRadius;
/* ydnar: get sun angular deviance/samples */
if ( ext && TokenAvailable() ) {
text.GetToken( false );
sun->deviance = degrees_to_radians( atof( token ) );
sun.deviance = degrees_to_radians( atof( token ) );
text.GetToken( false );
sun->numSamples = atoi( token );
sun.numSamples = atoi( token );
}
/* store sun */
sun->next = si->sun;
si->sun = sun;
/* apply sky surfaceparm */
ApplySurfaceParm( "sky", &si->contentFlags, &si->surfaceFlags, &si->compileFlags );
@ -1220,14 +1213,38 @@ static void ParseShaderFile( const char *filename ){
/* ydnar/splashdamage: q3map_skyLight <value> <iterations> */
else if ( striEqual( token, "q3map_skyLight" ) ) {
skylight_t& skylight = si->skylights.emplace_back();
text.GetToken( false );
si->skyLightValue = atof( token );
skylight.value = atof( token );
text.GetToken( false );
si->skyLightIterations = atoi( token );
skylight.iterations = atoi( token );
/* clamp */
value_maximize( si->skyLightValue, 0.0f );
value_maximize( si->skyLightIterations, 2 );
value_maximize( skylight.value, 0.0f );
value_maximize( skylight.iterations, 2 );
/* read optional extension: horizon_min horizon_max sample_color*/
if( TokenAvailable() ){
text.GetToken( false );
skylight.horizon_min = std::clamp( atoi( token ), -90, 90 );
}
else{
continue; // avoid two sequential TokenAvailable()
}
if( TokenAvailable() ){
text.GetToken( false );
skylight.horizon_max = std::clamp( atoi( token ), -90, 90 );
}
else{
continue; // avoid two sequential TokenAvailable()
}
if( TokenAvailable() ){
text.GetToken( false );
skylight.sample_color = atoi( token ) != 0;
}
else{
continue; // avoid two sequential TokenAvailable()
}
}
/* q3map_surfacelight <value> */