Ykkrosh
67a94572ec
Replace almost all texture uses with calls to the new system. Add some anistropic filtering to terrain textures. Let Atlas load terrain texture previews partly-asynchronously by polling. Fix inefficient texture colour determination for minimap. Remove unused global g_TerrainModified. Change GUI texcoord computation to be less efficient but to cope with dynamic texture changes. Fix GUI renderer effects leaving bogus colour state. This was SVN commit r8099.
411 lines
13 KiB
C++
411 lines
13 KiB
C++
/* Copyright (C) 2009 Wildfire Games.
|
|
* This file is part of 0 A.D.
|
|
*
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* Implementation of player colour RenderModifiers.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "renderer/Renderer.h"
|
|
#include "renderer/PlayerRenderer.h"
|
|
#include "renderer/ShadowMap.h"
|
|
|
|
#include "graphics/LightEnv.h"
|
|
#include "graphics/Model.h"
|
|
#include "graphics/TextureManager.h"
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// FastPlayerColorRender
|
|
|
|
FastPlayerColorRender::FastPlayerColorRender()
|
|
{
|
|
debug_assert(ogl_max_tex_units >= 3);
|
|
}
|
|
|
|
FastPlayerColorRender::~FastPlayerColorRender()
|
|
{
|
|
}
|
|
|
|
bool FastPlayerColorRender::IsAvailable()
|
|
{
|
|
return (ogl_max_tex_units >= 3);
|
|
}
|
|
|
|
int FastPlayerColorRender::BeginPass(int pass)
|
|
{
|
|
debug_assert(pass == 0);
|
|
|
|
// Fast player color uses a single pass with three texture environments
|
|
// Note: This uses ARB_texture_env_crossbar (which is checked in GameSetup)
|
|
//
|
|
// We calculate: Result = Color*Texture*(PlayerColor*(1-Texture.a) + 1.0*Texture.a)
|
|
// Algebra gives us:
|
|
// Result = (1 - ((1 - PlayerColor) * (1 - Texture.a)))*Texture*Color
|
|
|
|
// TexEnv #0
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_ONE_MINUS_SRC_ALPHA);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_CONSTANT);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_ONE_MINUS_SRC_COLOR);
|
|
|
|
// Don't care about alpha; set it to something harmless
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
|
|
|
// TexEnv #1
|
|
pglActiveTextureARB(GL_TEXTURE0+1);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_ONE_MINUS_SRC_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
|
|
|
|
// Don't care about alpha; set it to something harmless
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
|
|
|
// TexEnv #2
|
|
pglActiveTextureARB(GL_TEXTURE0+2);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE0);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
|
|
|
|
// Don't care about alpha; set it to something harmless
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
|
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
|
|
return STREAM_POS|STREAM_COLOR|STREAM_UV0;
|
|
}
|
|
|
|
|
|
bool FastPlayerColorRender::EndPass(int UNUSED(pass))
|
|
{
|
|
// Restore state
|
|
pglActiveTextureARB(GL_TEXTURE1);
|
|
glDisable(GL_TEXTURE_2D);
|
|
pglActiveTextureARB(GL_TEXTURE2);
|
|
glDisable(GL_TEXTURE_2D);
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
|
|
return true;
|
|
}
|
|
|
|
void FastPlayerColorRender::PrepareTexture(int UNUSED(pass), CTexturePtr& texture)
|
|
{
|
|
texture->Bind(2);
|
|
texture->Bind(1);
|
|
texture->Bind(0);
|
|
}
|
|
|
|
void FastPlayerColorRender::PrepareModel(int UNUSED(pass), CModel* model)
|
|
{
|
|
// Get the player color
|
|
SMaterialColor colour = model->GetMaterial().GetPlayerColor();
|
|
float* color = &colour.r; // because it's stored RGBA
|
|
|
|
// Set the texture environment color the player color
|
|
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// SlowPlayerColorRender
|
|
|
|
SlowPlayerColorRender::SlowPlayerColorRender()
|
|
{
|
|
}
|
|
|
|
SlowPlayerColorRender::~SlowPlayerColorRender()
|
|
{
|
|
}
|
|
|
|
int SlowPlayerColorRender::BeginPass(int pass)
|
|
{
|
|
// We calculate: Result = (Color*Texture)*Texture.a + (Color*Texture*PlayerColor)*(1-Texture.a)
|
|
// Modulation is done via texture environments, the final interpolation is done via blending
|
|
|
|
if (pass == 0)
|
|
{
|
|
// TexEnv #0
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
|
|
|
|
// Don't care about alpha; set it to something harmless
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
|
|
|
// Render it!
|
|
return STREAM_POS|STREAM_COLOR|STREAM_UV0;
|
|
}
|
|
else
|
|
{
|
|
// TexEnv #0
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_CONSTANT);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
|
|
|
|
// Alpha = Opacity of non-player colored layer
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
|
|
|
// TexEnv #1
|
|
pglActiveTextureARB(GL_TEXTURE1);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
|
|
|
|
// Pass alpha unchanged
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
|
|
// Setup blending
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
|
|
glEnable(GL_ALPHA_TEST);
|
|
glAlphaFunc(GL_LESS, 1.0);
|
|
glDepthMask(0);
|
|
|
|
// Render it!
|
|
return STREAM_POS|STREAM_COLOR|STREAM_UV0;
|
|
}
|
|
}
|
|
|
|
bool SlowPlayerColorRender::EndPass(int pass)
|
|
{
|
|
if (pass == 0)
|
|
return false; // need two passes
|
|
|
|
// Restore state
|
|
pglActiveTextureARB(GL_TEXTURE1);
|
|
glDisable(GL_TEXTURE_2D);
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_ALPHA_TEST);
|
|
glDepthMask(1);
|
|
|
|
return true;
|
|
}
|
|
|
|
void SlowPlayerColorRender::PrepareTexture(int pass, CTexturePtr& texture)
|
|
{
|
|
if (pass == 1)
|
|
texture->Bind(1);
|
|
texture->Bind(0);
|
|
}
|
|
|
|
|
|
void SlowPlayerColorRender::PrepareModel(int pass, CModel* model)
|
|
{
|
|
if (pass == 1)
|
|
{
|
|
// Get the player color
|
|
SMaterialColor colour = model->GetMaterial().GetPlayerColor();
|
|
float* color = &colour.r; // because it's stored RGBA
|
|
|
|
// Set the texture environment color the player color
|
|
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// LitPlayerColorRender
|
|
|
|
LitPlayerColorRender::LitPlayerColorRender()
|
|
{
|
|
}
|
|
|
|
LitPlayerColorRender::~LitPlayerColorRender()
|
|
{
|
|
}
|
|
|
|
int LitPlayerColorRender::BeginPass(int pass)
|
|
{
|
|
debug_assert(GetShadowMap() && GetShadowMap()->GetUseDepthTexture());
|
|
|
|
if (pass == 0)
|
|
{
|
|
// First pass: Lay down the material color
|
|
// We calculate:
|
|
// Material = Texture*(PlayerColor*(1.0-Texture.a) + 1.0*Texture.a))
|
|
// = (1 - ((1 - PlayerColor) * (1 - Texture.a)))*Texture
|
|
|
|
// Incoming Color holds the player color
|
|
// Texture 0 holds the model's texture
|
|
|
|
// TexEnv #0
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_ONE_MINUS_SRC_ALPHA);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_ONE_MINUS_SRC_COLOR);
|
|
|
|
// Don't care about alpha; set it to something harmless
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
|
|
|
// TexEnv #1
|
|
pglActiveTextureARB(GL_TEXTURE0+1);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBindTexture(GL_TEXTURE_2D, GetShadowMap()->GetTexture());
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_ONE_MINUS_SRC_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE0);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
|
|
|
|
// Don't care about alpha; set it to something harmless
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
|
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
|
|
return STREAM_POS|STREAM_UV0;
|
|
}
|
|
else
|
|
{
|
|
// Second pass: Multiply with lighting
|
|
//
|
|
// We calculate:
|
|
// Lighting = Ambient + Diffuse * Shadow
|
|
// and modulate with frame buffer contents
|
|
//
|
|
// Incoming color is diffuse
|
|
// Texture 1 is the shadow map
|
|
|
|
// TexEnv #0
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, GetShadowMap()->GetTexture());
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PRIMARY_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE1);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
|
|
|
// TexEnv #1
|
|
pglActiveTextureARB(GL_TEXTURE1);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBindTexture(GL_TEXTURE_2D, GetShadowMap()->GetTexture());
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_CONSTANT);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
|
|
|
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, &GetLightEnv()->m_UnitsAmbientColor.X);
|
|
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
|
|
// Blending, Z settings
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_DST_COLOR, GL_ZERO);
|
|
|
|
glDepthMask(0);
|
|
|
|
return STREAM_POS|STREAM_COLOR|STREAM_TEXGENTOUV1;
|
|
}
|
|
}
|
|
|
|
|
|
bool LitPlayerColorRender::EndPass(int pass)
|
|
{
|
|
if (pass == 0)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Restore state
|
|
pglActiveTextureARB(GL_TEXTURE1);
|
|
glDisable(GL_TEXTURE_2D);
|
|
pglActiveTextureARB(GL_TEXTURE0);
|
|
|
|
glDisable(GL_BLEND);
|
|
glDepthMask(1);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const CMatrix3D* LitPlayerColorRender::GetTexGenMatrix(int UNUSED(pass))
|
|
{
|
|
return &GetShadowMap()->GetTextureMatrix();
|
|
}
|
|
|
|
void LitPlayerColorRender::PrepareTexture(int pass, CTexturePtr& texture)
|
|
{
|
|
if (pass == 0)
|
|
texture->Bind(0);
|
|
}
|
|
|
|
void LitPlayerColorRender::PrepareModel(int pass, CModel* model)
|
|
{
|
|
if (pass == 0)
|
|
{
|
|
// Get the player color
|
|
SMaterialColor colour = model->GetMaterial().GetPlayerColor();
|
|
|
|
// Send the player color
|
|
glColor3f(colour.r, colour.g, colour.b);
|
|
}
|
|
}
|