598 lines
17 KiB
C++
598 lines
17 KiB
C++
/**
|
|
* @file lltexlayerparams.cpp
|
|
* @brief Texture layer parameters
|
|
*
|
|
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#include "lltexlayerparams.h"
|
|
|
|
#include "llavatarappearance.h"
|
|
#include "llimagetga.h"
|
|
#include "llquantize.h"
|
|
#include "lltexlayer.h"
|
|
#include "lltexturemanagerbridge.h"
|
|
#include "llrender2dutils.h"
|
|
#include "llwearable.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLTexLayerParam
|
|
//-----------------------------------------------------------------------------
|
|
LLTexLayerParam::LLTexLayerParam(LLTexLayerInterface *layer)
|
|
: LLViewerVisualParam(),
|
|
mTexLayer(layer),
|
|
mAvatarAppearance(NULL)
|
|
{
|
|
if (mTexLayer != NULL)
|
|
{
|
|
mAvatarAppearance = mTexLayer->getTexLayerSet()->getAvatarAppearance();
|
|
}
|
|
else
|
|
{
|
|
LL_ERRS() << "LLTexLayerParam constructor passed with NULL reference for layer!" << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
LLTexLayerParam::LLTexLayerParam(LLAvatarAppearance *appearance)
|
|
: LLViewerVisualParam(),
|
|
mTexLayer(NULL),
|
|
mAvatarAppearance(appearance)
|
|
{
|
|
}
|
|
|
|
LLTexLayerParam::LLTexLayerParam(const LLTexLayerParam& pOther)
|
|
: LLViewerVisualParam(pOther),
|
|
mTexLayer(pOther.mTexLayer),
|
|
mAvatarAppearance(pOther.mAvatarAppearance)
|
|
{
|
|
}
|
|
|
|
BOOL LLTexLayerParam::setInfo(LLViewerVisualParamInfo *info, BOOL add_to_appearance)
|
|
{
|
|
LLViewerVisualParam::setInfo(info);
|
|
|
|
if (add_to_appearance)
|
|
{
|
|
mAvatarAppearance->addVisualParam( this);
|
|
this->setParamLocation(mAvatarAppearance->isSelf() ? LOC_AV_SELF : LOC_AV_OTHER);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLTexLayerParamAlpha
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// static
|
|
LLTexLayerParamAlpha::param_alpha_ptr_list_t LLTexLayerParamAlpha::sInstances;
|
|
|
|
// static
|
|
void LLTexLayerParamAlpha::dumpCacheByteCount()
|
|
{
|
|
S32 gl_bytes = 0;
|
|
getCacheByteCount( &gl_bytes);
|
|
LL_INFOS() << "Processed Alpha Texture Cache GL:" << (gl_bytes/1024) << "KB" << LL_ENDL;
|
|
}
|
|
|
|
// static
|
|
void LLTexLayerParamAlpha::getCacheByteCount(S32* gl_bytes)
|
|
{
|
|
*gl_bytes = 0;
|
|
|
|
for (param_alpha_ptr_list_t::iterator iter = sInstances.begin();
|
|
iter != sInstances.end(); ++iter)
|
|
{
|
|
LLTexLayerParamAlpha* instance = *iter;
|
|
LLGLTexture* tex = instance->mCachedProcessedTexture;
|
|
if (tex)
|
|
{
|
|
S32 bytes = (S32)tex->getWidth() * tex->getHeight() * tex->getComponents();
|
|
|
|
if (tex->hasGLTexture())
|
|
{
|
|
*gl_bytes += bytes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LLTexLayerParamAlpha::LLTexLayerParamAlpha(LLTexLayerInterface* layer)
|
|
: LLTexLayerParam(layer),
|
|
mCachedProcessedTexture(NULL),
|
|
mStaticImageTGA(),
|
|
mStaticImageRaw(),
|
|
mNeedsCreateTexture(FALSE),
|
|
mStaticImageInvalid(FALSE),
|
|
mAvgDistortionVec(1.f, 1.f, 1.f),
|
|
mCachedEffectiveWeight(0.f)
|
|
{
|
|
sInstances.push_front(this);
|
|
}
|
|
|
|
LLTexLayerParamAlpha::LLTexLayerParamAlpha(LLAvatarAppearance* appearance)
|
|
: LLTexLayerParam(appearance),
|
|
mCachedProcessedTexture(NULL),
|
|
mStaticImageTGA(),
|
|
mStaticImageRaw(),
|
|
mNeedsCreateTexture(FALSE),
|
|
mStaticImageInvalid(FALSE),
|
|
mAvgDistortionVec(1.f, 1.f, 1.f),
|
|
mCachedEffectiveWeight(0.f)
|
|
{
|
|
sInstances.push_front(this);
|
|
}
|
|
|
|
LLTexLayerParamAlpha::LLTexLayerParamAlpha(const LLTexLayerParamAlpha& pOther)
|
|
: LLTexLayerParam(pOther),
|
|
mCachedProcessedTexture(pOther.mCachedProcessedTexture),
|
|
mStaticImageTGA(pOther.mStaticImageTGA),
|
|
mStaticImageRaw(pOther.mStaticImageRaw),
|
|
mNeedsCreateTexture(pOther.mNeedsCreateTexture),
|
|
mStaticImageInvalid(pOther.mStaticImageInvalid),
|
|
mAvgDistortionVec(pOther.mAvgDistortionVec),
|
|
mCachedEffectiveWeight(pOther.mCachedEffectiveWeight)
|
|
{
|
|
sInstances.push_front(this);
|
|
}
|
|
|
|
LLTexLayerParamAlpha::~LLTexLayerParamAlpha()
|
|
{
|
|
deleteCaches();
|
|
sInstances.remove(this);
|
|
}
|
|
|
|
/*virtual*/ LLViewerVisualParam* LLTexLayerParamAlpha::cloneParam(LLWearable* wearable) const
|
|
{
|
|
return new LLTexLayerParamAlpha(*this);
|
|
}
|
|
|
|
void LLTexLayerParamAlpha::deleteCaches()
|
|
{
|
|
mStaticImageTGA = NULL; // deletes image
|
|
mCachedProcessedTexture = NULL;
|
|
mStaticImageRaw = NULL;
|
|
mNeedsCreateTexture = FALSE;
|
|
}
|
|
|
|
BOOL LLTexLayerParamAlpha::getMultiplyBlend() const
|
|
{
|
|
return ((LLTexLayerParamAlphaInfo *)getInfo())->mMultiplyBlend;
|
|
}
|
|
|
|
void LLTexLayerParamAlpha::setWeight(F32 weight, bool upload_bake)
|
|
{
|
|
if (mIsAnimating || mTexLayer == NULL)
|
|
{
|
|
return;
|
|
}
|
|
F32 min_weight = getMinWeight();
|
|
F32 max_weight = getMaxWeight();
|
|
F32 new_weight = llclamp(weight, min_weight, max_weight);
|
|
U8 cur_u8 = F32_to_U8(mCurWeight, min_weight, max_weight);
|
|
U8 new_u8 = F32_to_U8(new_weight, min_weight, max_weight);
|
|
if (cur_u8 != new_u8)
|
|
{
|
|
mCurWeight = new_weight;
|
|
|
|
if ((mAvatarAppearance->getSex() & getSex()) &&
|
|
(mAvatarAppearance->isSelf() && !mIsDummy)) // only trigger a baked texture update if we're changing a wearable's visual param.
|
|
{
|
|
mAvatarAppearance->invalidateComposite(mTexLayer->getTexLayerSet(), upload_bake);
|
|
mTexLayer->invalidateMorphMasks();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLTexLayerParamAlpha::setAnimationTarget(F32 target_value, bool upload_bake)
|
|
{
|
|
// do not animate dummy parameters
|
|
if (mIsDummy)
|
|
{
|
|
setWeight(target_value, upload_bake);
|
|
return;
|
|
}
|
|
|
|
mTargetWeight = target_value;
|
|
setWeight(target_value, upload_bake);
|
|
mIsAnimating = TRUE;
|
|
if (mNext)
|
|
{
|
|
mNext->setAnimationTarget(target_value, upload_bake);
|
|
}
|
|
}
|
|
|
|
void LLTexLayerParamAlpha::animate(F32 delta, bool upload_bake)
|
|
{
|
|
if (mNext)
|
|
{
|
|
mNext->animate(delta, upload_bake);
|
|
}
|
|
}
|
|
|
|
BOOL LLTexLayerParamAlpha::getSkip() const
|
|
{
|
|
if (!mTexLayer)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
const LLAvatarAppearance *appearance = mTexLayer->getTexLayerSet()->getAvatarAppearance();
|
|
|
|
if (((LLTexLayerParamAlphaInfo *)getInfo())->mSkipIfZeroWeight)
|
|
{
|
|
F32 effective_weight = (appearance->getSex() & getSex()) ? mCurWeight : getDefaultWeight();
|
|
if (is_approx_zero(effective_weight))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
LLWearableType::EType type = (LLWearableType::EType)getWearableType();
|
|
if ((type != LLWearableType::WT_INVALID) && !appearance->isWearingWearableType(type))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static LLTrace::BlockTimerStatHandle FTM_TEX_LAYER_PARAM_ALPHA("alpha render");
|
|
BOOL LLTexLayerParamAlpha::render(S32 x, S32 y, S32 width, S32 height)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_TEX_LAYER_PARAM_ALPHA);
|
|
BOOL success = TRUE;
|
|
|
|
if (!mTexLayer)
|
|
{
|
|
return success;
|
|
}
|
|
|
|
F32 effective_weight = (mTexLayer->getTexLayerSet()->getAvatarAppearance()->getSex() & getSex()) ? mCurWeight : getDefaultWeight();
|
|
BOOL weight_changed = effective_weight != mCachedEffectiveWeight;
|
|
if (getSkip())
|
|
{
|
|
return success;
|
|
}
|
|
|
|
LLTexLayerParamAlphaInfo *info = (LLTexLayerParamAlphaInfo *)getInfo();
|
|
gGL.flush();
|
|
if (info->mMultiplyBlend)
|
|
{
|
|
gGL.blendFunc(LLRender::BF_DEST_ALPHA, LLRender::BF_ZERO); // Multiplication: approximates a min() function
|
|
}
|
|
else
|
|
{
|
|
gGL.setSceneBlendType(LLRender::BT_ADD); // Addition: approximates a max() function
|
|
}
|
|
|
|
if (!info->mStaticImageFileName.empty() && !mStaticImageInvalid)
|
|
{
|
|
if (mStaticImageTGA.isNull())
|
|
{
|
|
// Don't load the image file until we actually need it the first time. Like now.
|
|
mStaticImageTGA = LLTexLayerStaticImageList::getInstance()->getImageTGA(info->mStaticImageFileName);
|
|
// We now have something in one of our caches
|
|
LLTexLayerSet::sHasCaches |= mStaticImageTGA.notNull() ? TRUE : FALSE;
|
|
|
|
if (mStaticImageTGA.isNull())
|
|
{
|
|
LL_WARNS() << "Unable to load static file: " << info->mStaticImageFileName << LL_ENDL;
|
|
mStaticImageInvalid = TRUE; // don't try again.
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
const S32 image_tga_width = mStaticImageTGA->getWidth();
|
|
const S32 image_tga_height = mStaticImageTGA->getHeight();
|
|
if (!mCachedProcessedTexture ||
|
|
(mCachedProcessedTexture->getWidth() != image_tga_width) ||
|
|
(mCachedProcessedTexture->getHeight() != image_tga_height) ||
|
|
(weight_changed))
|
|
{
|
|
mCachedEffectiveWeight = effective_weight;
|
|
|
|
if (!mCachedProcessedTexture)
|
|
{
|
|
llassert(gTextureManagerBridgep);
|
|
mCachedProcessedTexture = gTextureManagerBridgep->getLocalTexture(image_tga_width, image_tga_height, 1, FALSE);
|
|
|
|
// We now have something in one of our caches
|
|
LLTexLayerSet::sHasCaches |= mCachedProcessedTexture ? TRUE : FALSE;
|
|
|
|
mCachedProcessedTexture->setExplicitFormat(GL_ALPHA8, GL_ALPHA);
|
|
}
|
|
|
|
// Applies domain and effective weight to data as it is decoded. Also resizes the raw image if needed.
|
|
mStaticImageRaw = NULL;
|
|
mStaticImageRaw = new LLImageRaw;
|
|
mStaticImageTGA->decodeAndProcess(mStaticImageRaw, info->mDomain, effective_weight);
|
|
mNeedsCreateTexture = TRUE;
|
|
LL_DEBUGS() << "Built Cached Alpha: " << info->mStaticImageFileName << ": (" << mStaticImageRaw->getWidth() << ", " << mStaticImageRaw->getHeight() << ") " << "Domain: " << info->mDomain << " Weight: " << effective_weight << LL_ENDL;
|
|
}
|
|
|
|
if (mCachedProcessedTexture)
|
|
{
|
|
{
|
|
// Create the GL texture, and then hang onto it for future use.
|
|
if (mNeedsCreateTexture)
|
|
{
|
|
mCachedProcessedTexture->createGLTexture(0, mStaticImageRaw);
|
|
mNeedsCreateTexture = FALSE;
|
|
gGL.getTexUnit(0)->bind(mCachedProcessedTexture);
|
|
mCachedProcessedTexture->setAddressMode(LLTexUnit::TAM_CLAMP);
|
|
}
|
|
|
|
LLGLSNoAlphaTest gls_no_alpha_test;
|
|
gGL.getTexUnit(0)->bind(mCachedProcessedTexture);
|
|
gl_rect_2d_simple_tex(width, height);
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
stop_glerror();
|
|
}
|
|
}
|
|
|
|
// Don't keep the cache for other people's avatars
|
|
// (It's not really a "cache" in that case, but the logic is the same)
|
|
if (!mAvatarAppearance->isSelf())
|
|
{
|
|
mCachedProcessedTexture = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.color4f(0.f, 0.f, 0.f, effective_weight);
|
|
gl_rect_2d_simple(width, height);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLTexLayerParamAlphaInfo
|
|
//-----------------------------------------------------------------------------
|
|
LLTexLayerParamAlphaInfo::LLTexLayerParamAlphaInfo() :
|
|
mMultiplyBlend(FALSE),
|
|
mSkipIfZeroWeight(FALSE),
|
|
mDomain(0.f)
|
|
{
|
|
}
|
|
|
|
BOOL LLTexLayerParamAlphaInfo::parseXml(LLXmlTreeNode* node)
|
|
{
|
|
llassert(node->hasName("param") && node->getChildByName("param_alpha"));
|
|
|
|
if (!LLViewerVisualParamInfo::parseXml(node))
|
|
return FALSE;
|
|
|
|
LLXmlTreeNode* param_alpha_node = node->getChildByName("param_alpha");
|
|
if (!param_alpha_node)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static LLStdStringHandle tga_file_string = LLXmlTree::addAttributeString("tga_file");
|
|
if (param_alpha_node->getFastAttributeString(tga_file_string, mStaticImageFileName))
|
|
{
|
|
// Don't load the image file until it's actually needed.
|
|
}
|
|
// else
|
|
// {
|
|
// LL_WARNS() << "<param_alpha> element is missing tga_file attribute." << LL_ENDL;
|
|
// }
|
|
|
|
static LLStdStringHandle multiply_blend_string = LLXmlTree::addAttributeString("multiply_blend");
|
|
param_alpha_node->getFastAttributeBOOL(multiply_blend_string, mMultiplyBlend);
|
|
|
|
static LLStdStringHandle skip_if_zero_string = LLXmlTree::addAttributeString("skip_if_zero");
|
|
param_alpha_node->getFastAttributeBOOL(skip_if_zero_string, mSkipIfZeroWeight);
|
|
|
|
static LLStdStringHandle domain_string = LLXmlTree::addAttributeString("domain");
|
|
param_alpha_node->getFastAttributeF32(domain_string, mDomain);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
LLTexLayerParamColor::LLTexLayerParamColor(LLTexLayerInterface* layer)
|
|
: LLTexLayerParam(layer),
|
|
mAvgDistortionVec(1.f, 1.f, 1.f)
|
|
{
|
|
}
|
|
|
|
LLTexLayerParamColor::LLTexLayerParamColor(LLAvatarAppearance *appearance)
|
|
: LLTexLayerParam(appearance),
|
|
mAvgDistortionVec(1.f, 1.f, 1.f)
|
|
{
|
|
}
|
|
|
|
LLTexLayerParamColor::LLTexLayerParamColor(const LLTexLayerParamColor& pOther)
|
|
: LLTexLayerParam(pOther),
|
|
mAvgDistortionVec(pOther.mAvgDistortionVec)
|
|
{
|
|
}
|
|
|
|
LLTexLayerParamColor::~LLTexLayerParamColor()
|
|
{
|
|
}
|
|
|
|
/*virtual*/ LLViewerVisualParam* LLTexLayerParamColor::cloneParam(LLWearable* wearable) const
|
|
{
|
|
return new LLTexLayerParamColor(*this);
|
|
}
|
|
|
|
LLColor4 LLTexLayerParamColor::getNetColor() const
|
|
{
|
|
const LLTexLayerParamColorInfo *info = (LLTexLayerParamColorInfo *)getInfo();
|
|
|
|
llassert(info->mNumColors >= 1);
|
|
|
|
F32 effective_weight = (mAvatarAppearance && (mAvatarAppearance->getSex() & getSex())) ? mCurWeight : getDefaultWeight();
|
|
|
|
S32 index_last = info->mNumColors - 1;
|
|
F32 scaled_weight = effective_weight * index_last;
|
|
S32 index_start = (S32) scaled_weight;
|
|
S32 index_end = index_start + 1;
|
|
if (index_start == index_last)
|
|
{
|
|
return info->mColors[index_last];
|
|
}
|
|
else
|
|
{
|
|
F32 weight = scaled_weight - index_start;
|
|
const LLColor4 *start = &info->mColors[ index_start ];
|
|
const LLColor4 *end = &info->mColors[ index_end ];
|
|
return LLColor4((1.f - weight) * start->mV[VX] + weight * end->mV[VX],
|
|
(1.f - weight) * start->mV[VY] + weight * end->mV[VY],
|
|
(1.f - weight) * start->mV[VZ] + weight * end->mV[VZ],
|
|
(1.f - weight) * start->mV[VW] + weight * end->mV[VW]);
|
|
}
|
|
}
|
|
|
|
void LLTexLayerParamColor::setWeight(F32 weight, bool upload_bake)
|
|
{
|
|
if (mIsAnimating)
|
|
{
|
|
return;
|
|
}
|
|
|
|
F32 min_weight = getMinWeight();
|
|
F32 max_weight = getMaxWeight();
|
|
F32 new_weight = llclamp(weight, min_weight, max_weight);
|
|
U8 cur_u8 = F32_to_U8(mCurWeight, min_weight, max_weight);
|
|
U8 new_u8 = F32_to_U8(new_weight, min_weight, max_weight);
|
|
if (cur_u8 != new_u8)
|
|
{
|
|
mCurWeight = new_weight;
|
|
|
|
const LLTexLayerParamColorInfo *info = (LLTexLayerParamColorInfo *)getInfo();
|
|
|
|
if (info->mNumColors <= 0)
|
|
{
|
|
// This will happen when we set the default weight the first time.
|
|
return;
|
|
}
|
|
|
|
if ((mAvatarAppearance->getSex() & getSex()) && (mAvatarAppearance->isSelf() && !mIsDummy)) // only trigger a baked texture update if we're changing a wearable's visual param.
|
|
{
|
|
onGlobalColorChanged(upload_bake);
|
|
if (mTexLayer)
|
|
{
|
|
mAvatarAppearance->invalidateComposite(mTexLayer->getTexLayerSet(), upload_bake);
|
|
}
|
|
}
|
|
|
|
// LL_INFOS() << "param " << mName << " = " << new_weight << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
void LLTexLayerParamColor::setAnimationTarget(F32 target_value, bool upload_bake)
|
|
{
|
|
// set value first then set interpolating flag to ignore further updates
|
|
mTargetWeight = target_value;
|
|
setWeight(target_value, upload_bake);
|
|
mIsAnimating = TRUE;
|
|
if (mNext)
|
|
{
|
|
mNext->setAnimationTarget(target_value, upload_bake);
|
|
}
|
|
}
|
|
|
|
void LLTexLayerParamColor::animate(F32 delta, bool upload_bake)
|
|
{
|
|
if (mNext)
|
|
{
|
|
mNext->animate(delta, upload_bake);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLTexLayerParamColorInfo
|
|
//-----------------------------------------------------------------------------
|
|
LLTexLayerParamColorInfo::LLTexLayerParamColorInfo() :
|
|
mOperation(LLTexLayerParamColor::OP_ADD),
|
|
mNumColors(0)
|
|
{
|
|
}
|
|
|
|
BOOL LLTexLayerParamColorInfo::parseXml(LLXmlTreeNode *node)
|
|
{
|
|
llassert(node->hasName("param") && node->getChildByName("param_color"));
|
|
|
|
if (!LLViewerVisualParamInfo::parseXml(node))
|
|
return FALSE;
|
|
|
|
LLXmlTreeNode* param_color_node = node->getChildByName("param_color");
|
|
if (!param_color_node)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
std::string op_string;
|
|
static LLStdStringHandle operation_string = LLXmlTree::addAttributeString("operation");
|
|
if (param_color_node->getFastAttributeString(operation_string, op_string))
|
|
{
|
|
LLStringUtil::toLower(op_string);
|
|
if (op_string == "add") mOperation = LLTexLayerParamColor::OP_ADD;
|
|
else if (op_string == "multiply") mOperation = LLTexLayerParamColor::OP_MULTIPLY;
|
|
else if (op_string == "blend") mOperation = LLTexLayerParamColor::OP_BLEND;
|
|
}
|
|
|
|
mNumColors = 0;
|
|
|
|
LLColor4U color4u;
|
|
for (LLXmlTreeNode* child = param_color_node->getChildByName("value");
|
|
child;
|
|
child = param_color_node->getNextNamedChild())
|
|
{
|
|
if ((mNumColors < MAX_COLOR_VALUES))
|
|
{
|
|
static LLStdStringHandle color_string = LLXmlTree::addAttributeString("color");
|
|
if (child->getFastAttributeColor4U(color_string, color4u))
|
|
{
|
|
mColors[ mNumColors ].setVec(color4u);
|
|
mNumColors++;
|
|
}
|
|
}
|
|
}
|
|
if (!mNumColors)
|
|
{
|
|
LL_WARNS() << "<param_color> is missing <value> sub-elements" << LL_ENDL;
|
|
return FALSE;
|
|
}
|
|
|
|
if ((mOperation == LLTexLayerParamColor::OP_BLEND) && (mNumColors != 1))
|
|
{
|
|
LL_WARNS() << "<param_color> with operation\"blend\" must have exactly one <value>" << LL_ENDL;
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|