/** * @file llvosky.cpp * @brief LLVOSky class implementation * * $LicenseInfo:firstyear=2001&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 "llviewerprecompiledheaders.h" #include "llvosky.h" #include "llfeaturemanager.h" #include "llviewercontrol.h" #include "llframetimer.h" #include "llagent.h" #include "llagentcamera.h" #include "lldrawable.h" #include "llface.h" #include "llcubemap.h" #include "lldrawpoolsky.h" #include "lldrawpoolwater.h" #include "llglheaders.h" #include "llsky.h" #include "llviewercamera.h" #include "llviewertexturelist.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llworld.h" #include "pipeline.h" #include "lldrawpoolwlsky.h" #include "v3colorutil.h" #include "llsettingssky.h" #include "llenvironment.h" #include "lltrace.h" #include "llfasttimer.h" #undef min #undef max namespace { const S32 NUM_TILES_X = 8; const S32 NUM_TILES_Y = 4; const S32 NUM_TILES = NUM_TILES_X * NUM_TILES_Y; // Heavenly body constants const F32 SUN_DISK_RADIUS = 0.5f; const F32 MOON_DISK_RADIUS = SUN_DISK_RADIUS * 0.9f; const F32 SUN_INTENSITY = 1e5; // Texture coordinates: const LLVector2 TEX00 = LLVector2(0.f, 0.f); const LLVector2 TEX01 = LLVector2(0.f, 1.f); const LLVector2 TEX10 = LLVector2(1.f, 0.f); const LLVector2 TEX11 = LLVector2(1.f, 1.f); const F32 LIGHT_DIRECTION_THRESHOLD = (F32) cosf(DEG_TO_RAD * 1.f); const F32 COLOR_CHANGE_THRESHOLD = 0.01f; LLTrace::BlockTimerStatHandle FTM_VOSKY_UPDATETIMER("VOSky Update Timer Tick"); LLTrace::BlockTimerStatHandle FTM_VOSKY_UPDATEFORCED("VOSky Update Forced"); F32Seconds UPDATE_EXPRY(2.0f); } /*************************************** SkyTex ***************************************/ S32 LLSkyTex::sComponents = 4; S32 LLSkyTex::sResolution = 64; F32 LLSkyTex::sInterpVal = 0.f; S32 LLSkyTex::sCurrent = 0; LLSkyTex::LLSkyTex() : mSkyData(NULL), mSkyDirs(NULL) { } void LLSkyTex::init() { mSkyData = new LLColor4[sResolution * sResolution]; mSkyDirs = new LLVector3[sResolution * sResolution]; for (S32 i = 0; i < 2; ++i) { mTexture[i] = LLViewerTextureManager::getLocalTexture(FALSE); mTexture[i]->setAddressMode(LLTexUnit::TAM_CLAMP); mImageRaw[i] = new LLImageRaw(sResolution, sResolution, sComponents); initEmpty(i); } } void LLSkyTex::cleanupGL() { mTexture[0] = NULL; mTexture[1] = NULL; } void LLSkyTex::restoreGL() { for (S32 i = 0; i < 2; i++) { mTexture[i] = LLViewerTextureManager::getLocalTexture(FALSE); mTexture[i]->setAddressMode(LLTexUnit::TAM_CLAMP); } } LLSkyTex::~LLSkyTex() { delete[] mSkyData; mSkyData = NULL; delete[] mSkyDirs; mSkyDirs = NULL; } void LLSkyTex::initEmpty(const S32 tex) { U8* data = mImageRaw[tex]->getData(); for (S32 i = 0; i < sResolution; ++i) { for (S32 j = 0; j < sResolution; ++j) { const S32 basic_offset = (i * sResolution + j); S32 offset = basic_offset * sComponents; data[offset] = 0; data[offset+1] = 0; data[offset+2] = 0; data[offset+3] = 255; mSkyData[basic_offset].setToBlack(); } } createGLImage(tex); } void LLSkyTex::create(const F32 brightness) { /// Brightness ignored for now. U8* data = mImageRaw[sCurrent]->getData(); for (S32 i = 0; i < sResolution; ++i) { for (S32 j = 0; j < sResolution; ++j) { const S32 basic_offset = (i * sResolution + j); S32 offset = basic_offset * sComponents; U32* pix = (U32*)(data + offset); LLColor4U temp = LLColor4U(mSkyData[basic_offset]); *pix = temp.asRGBA(); } } createGLImage(sCurrent); } void LLSkyTex::createGLImage(S32 which) { mTexture[which]->createGLTexture(0, mImageRaw[which], 0, TRUE, LLGLTexture::LOCAL); mTexture[which]->setAddressMode(LLTexUnit::TAM_CLAMP); } void LLSkyTex::bindTexture(BOOL curr) { gGL.getTexUnit(0)->bind(mTexture[getWhich(curr)], true); } /*************************************** LLHeavenBody ***************************************/ F32 LLHeavenBody::sInterpVal = 0; LLHeavenBody::LLHeavenBody(const F32 rad) : mDirectionCached(LLVector3(0,0,0)), mDirection(LLVector3(0,0,0)), mIntensity(0.f), mDiskRadius(rad), mDraw(FALSE), mHorizonVisibility(1.f), mVisibility(1.f), mVisible(FALSE) { mColor.setToBlack(); mColorCached.setToBlack(); } const LLVector3& LLHeavenBody::getDirection() const { return mDirection; } void LLHeavenBody::setDirection(const LLVector3 &direction) { mDirection = direction; } void LLHeavenBody::setAngularVelocity(const LLVector3 &ang_vel) { mAngularVelocity = ang_vel; } const LLVector3& LLHeavenBody::getAngularVelocity() const { return mAngularVelocity; } const LLVector3& LLHeavenBody::getDirectionCached() const { return mDirectionCached; } void LLHeavenBody::renewDirection() { mDirectionCached = mDirection; } const LLColor3& LLHeavenBody::getColorCached() const { return mColorCached; } void LLHeavenBody::setColorCached(const LLColor3& c) { mColorCached = c; } const LLColor3& LLHeavenBody::getColor() const { return mColor; } void LLHeavenBody::setColor(const LLColor3& c) { mColor = c; } void LLHeavenBody::renewColor() { mColorCached = mColor; } F32 LLHeavenBody::interpVal() { return sInterpVal; } void LLHeavenBody::setInterpVal(const F32 v) { sInterpVal = v; } LLColor3 LLHeavenBody::getInterpColor() const { return sInterpVal * mColor + (1 - sInterpVal) * mColorCached; } const F32& LLHeavenBody::getVisibility() const { return mVisibility; } void LLHeavenBody::setVisibility(const F32 c) { mVisibility = c; } bool LLHeavenBody::isVisible() const { return mVisible; } void LLHeavenBody::setVisible(const bool v) { mVisible = v; } const F32& LLHeavenBody::getIntensity() const { return mIntensity; } void LLHeavenBody::setIntensity(const F32 c) { mIntensity = c; } void LLHeavenBody::setDiskRadius(const F32 radius) { mDiskRadius = radius; } F32 LLHeavenBody::getDiskRadius() const { return mDiskRadius; } void LLHeavenBody::setDraw(const bool draw) { mDraw = draw; } bool LLHeavenBody::getDraw() const { return mDraw; } const LLVector3& LLHeavenBody::corner(const S32 n) const { return mQuadCorner[n]; } LLVector3& LLHeavenBody::corner(const S32 n) { return mQuadCorner[n]; } const LLVector3* LLHeavenBody::corners() const { return mQuadCorner; } /*************************************** Sky ***************************************/ S32 LLVOSky::sResolution = LLSkyTex::getResolution(); S32 LLVOSky::sTileResX = sResolution/NUM_TILES_X; S32 LLVOSky::sTileResY = sResolution/NUM_TILES_Y; LLVOSky::LLVOSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) : LLStaticViewerObject(id, pcode, regionp, TRUE), mSun(SUN_DISK_RADIUS), mMoon(MOON_DISK_RADIUS), mBrightnessScale(1.f), mBrightnessScaleNew(0.f), mBrightnessScaleGuess(1.f), mWeatherChange(FALSE), mCloudDensity(0.2f), mWind(0.f), mForceUpdate(FALSE), mWorldScale(1.f), mBumpSunDir(0.f, 0.f, 1.f) { /// WL PARAMS mInitialized = FALSE; mbCanSelect = FALSE; mUpdateTimer.reset(); mForceUpdateThrottle.setTimerExpirySec(UPDATE_EXPRY); mForceUpdateThrottle.reset(); for (S32 i = 0; i < 6; i++) { mSkyTex[i].init(); mShinyTex[i].init(); } for (S32 i=0; iupdate(); updateDirections(); AtmosphericsVars vars; LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); // invariants across whole sky tex process... vars.blue_density = psky->getBlueDensity(); vars.blue_horizon = psky->getBlueHorizon(); vars.haze_density = psky->getHazeDensity(); vars.haze_horizon = psky->getHazeHorizon(); vars.density_multiplier = psky->getDensityMultiplier(); vars.max_y = psky->getMaxY(); vars.sun_norm = LLEnvironment::instance().getClampedSunNorm(); vars.sunlight = psky->getSunlightColor(); vars.ambient = psky->getAmbientColor(); vars.glow = psky->getGlow(); vars.cloud_shadow = psky->getCloudShadow(); vars.dome_radius = psky->getDomeRadius(); vars.dome_offset = psky->getDomeOffset(); vars.light_atten = psky->getLightAttenuation(vars.max_y); vars.light_transmittance = psky->getLightTransmittance(); // Initialize the cached normalized direction vectors for (S32 side = 0; side < 6; ++side) { for (S32 tile = 0; tile < NUM_TILES; ++tile) { initSkyTextureDirs(side, tile); createSkyTexture(vars, side, tile); } } for (S32 i = 0; i < 6; ++i) { mSkyTex[i].create(1.0f); mShinyTex[i].create(1.0f); } initCubeMap(); mInitialized = true; mHeavenlyBodyUpdated = FALSE ; } void LLVOSky::initCubeMap() { std::vector > images; for (S32 side = 0; side < 6; side++) { images.push_back(mShinyTex[side].getImageRaw()); } if (mCubeMap) { mCubeMap->init(images); } else if (gSavedSettings.getBOOL("RenderWater") && gGLManager.mHasCubeMap && LLCubeMap::sUseCubeMaps) { mCubeMap = new LLCubeMap(); mCubeMap->init(images); } gGL.getTexUnit(0)->disable(); } void LLVOSky::cleanupGL() { S32 i; for (i = 0; i < 6; i++) { mSkyTex[i].cleanupGL(); } if (getCubeMap()) { getCubeMap()->destroyGL(); } } void LLVOSky::restoreGL() { S32 i; for (i = 0; i < 6; i++) { mSkyTex[i].restoreGL(); } LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); if (psky) { setSunTextures(psky->getSunTextureId(), psky->getNextSunTextureId()); setMoonTextures(psky->getMoonTextureId(), psky->getNextMoonTextureId()); } updateDirections(); if (gSavedSettings.getBOOL("RenderWater") && gGLManager.mHasCubeMap && LLCubeMap::sUseCubeMaps) { LLCubeMap* cube_map = getCubeMap(); std::vector > images; for (S32 side = 0; side < 6; side++) { images.push_back(mShinyTex[side].getImageRaw()); } if(cube_map) { cube_map->init(images); } } mForceUpdate = TRUE; if (mDrawable) { gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME, TRUE); } } void LLVOSky::initSkyTextureDirs(const S32 side, const S32 tile) { S32 tile_x = tile % NUM_TILES_X; S32 tile_y = tile / NUM_TILES_X; S32 tile_x_pos = tile_x * sTileResX; S32 tile_y_pos = tile_y * sTileResY; F32 coeff[3] = {0, 0, 0}; const S32 curr_coef = side >> 1; // 0/1 = Z axis, 2/3 = Y, 4/5 = X const S32 side_dir = (((side & 1) << 1) - 1); // even = -1, odd = 1 const S32 x_coef = (curr_coef + 1) % 3; const S32 y_coef = (x_coef + 1) % 3; coeff[curr_coef] = (F32)side_dir; F32 inv_res = 1.f/sResolution; S32 x, y; for (y = tile_y_pos; y < (tile_y_pos + sTileResY); ++y) { for (x = tile_x_pos; x < (tile_x_pos + sTileResX); ++x) { coeff[x_coef] = F32((x<<1) + 1) * inv_res - 1.f; coeff[y_coef] = F32((y<<1) + 1) * inv_res - 1.f; LLVector3 dir(coeff[0], coeff[1], coeff[2]); dir.normalize(); mSkyTex[side].setDir(dir, x, y); mShinyTex[side].setDir(dir, x, y); } } } void LLVOSky::createSkyTexture(AtmosphericsVars& vars, const S32 side, const S32 tile) { S32 tile_x = tile % NUM_TILES_X; S32 tile_y = tile / NUM_TILES_X; S32 tile_x_pos = tile_x * sTileResX; S32 tile_y_pos = tile_y * sTileResY; S32 x, y; for (y = tile_y_pos; y < (tile_y_pos + sTileResY); ++y) { for (x = tile_x_pos; x < (tile_x_pos + sTileResX); ++x) { mSkyTex[side].setPixel(m_legacyAtmospherics.calcSkyColorInDir(vars, mSkyTex[side].getDir(x, y)), x, y); mShinyTex[side].setPixel(m_legacyAtmospherics.calcSkyColorInDir(vars, mSkyTex[side].getDir(x, y), true), x, y); } } } void LLVOSky::updateDirections(void) { LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); mSun.setColor(psky->getSunlightColor()); mMoon.setColor(LLColor3(1.0f, 1.0f, 1.0f)); mSun.renewDirection(); mSun.renewColor(); mMoon.renewDirection(); mMoon.renewColor(); } void LLVOSky::idleUpdate(LLAgent &agent, const F64 &time) { } bool LLVOSky::updateSky() { LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); LLColor4 total_ambient = psky->getTotalAmbient(); if (mDead || !(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY))) { return TRUE; } if (mDead) { // It's dead. Don't update it. return TRUE; } if (gGLManager.mIsDisabled) { return TRUE; } static S32 next_frame = 0; const S32 total_no_tiles = 6 * NUM_TILES; const S32 cycle_frame_no = total_no_tiles + 1; if (mUpdateTimer.getElapsedTimeF32() > 0.025f) { mUpdateTimer.reset(); const S32 frame = next_frame; mForceUpdate = mForceUpdate || (total_no_tiles == frame); ++next_frame; next_frame = next_frame % cycle_frame_no; mInterpVal = (!mInitialized) ? 1 : (F32)next_frame / cycle_frame_no; // sInterpVal = (F32)next_frame / cycle_frame_no; LLSkyTex::setInterpVal( mInterpVal ); LLHeavenBody::setInterpVal( mInterpVal ); updateDirections(); LLVector3 direction = mSun.getDirection(); direction.normalize(); const F32 dot_lighting = direction * mLastLightingDirection; //_WARNS("LAPRAS") << " <" << direction.getValue() << "> dot <" << mLastLightingDirection << "> = " << dot_lighting << " (threshold is " << LIGHT_DIRECTION_THRESHOLD << ")" << LL_ENDL; LLColor3 delta_color; delta_color.setVec(mLastTotalAmbient.mV[0] - total_ambient.mV[0], mLastTotalAmbient.mV[1] - total_ambient.mV[1], mLastTotalAmbient.mV[2] - total_ambient.mV[2]); bool light_direction_changed = (dot_lighting < LIGHT_DIRECTION_THRESHOLD); bool color_changed = (delta_color.length() >= COLOR_CHANGE_THRESHOLD); mForceUpdate = mForceUpdate || light_direction_changed; mForceUpdate = mForceUpdate || color_changed; mForceUpdate = mForceUpdate || !mInitialized; if (mForceUpdate && mForceUpdateThrottle.hasExpired()) { LL_RECORD_BLOCK_TIME(FTM_VOSKY_UPDATEFORCED); mForceUpdateThrottle.setTimerExpirySec(UPDATE_EXPRY); LLSkyTex::stepCurrent(); if (!direction.isExactlyZero()) { mLastLightingDirection = direction; mLastTotalAmbient = total_ambient; mInitialized = TRUE; if (mCubeMap) { updateFog(LLViewerCamera::getInstance()->getFar()); AtmosphericsVars vars; LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); // invariants across whole sky tex process... vars.blue_density = psky->getBlueDensity(); vars.blue_horizon = psky->getBlueHorizon(); vars.haze_density = psky->getHazeDensity(); vars.haze_horizon = psky->getHazeHorizon(); vars.density_multiplier = psky->getDensityMultiplier(); vars.max_y = psky->getMaxY(); vars.sun_norm = LLEnvironment::instance().getClampedSunNorm(); vars.sunlight = psky->getSunlightColor(); vars.ambient = psky->getAmbientColor(); vars.glow = psky->getGlow(); vars.cloud_shadow = psky->getCloudShadow(); vars.dome_radius = psky->getDomeRadius(); vars.dome_offset = psky->getDomeOffset(); vars.light_atten = psky->getLightAttenuation(vars.max_y); vars.light_transmittance = psky->getLightTransmittance(); for (int side = 0; side < 6; side++) { for (int tile = 0; tile < NUM_TILES; tile++) { createSkyTexture(vars, side, tile); } } for (int side = 0; side < 6; side++) { LLImageRaw* raw1 = nullptr; LLImageRaw* raw2 = nullptr; raw1 = mSkyTex[side].getImageRaw(TRUE); raw2 = mSkyTex[side].getImageRaw(FALSE); raw2->copy(raw1); mSkyTex[side].createGLImage(mSkyTex[side].getWhich(FALSE)); raw1 = mShinyTex[side].getImageRaw(TRUE); raw2 = mShinyTex[side].getImageRaw(FALSE); raw2->copy(raw1); mShinyTex[side].createGLImage(mShinyTex[side].getWhich(FALSE)); } next_frame = 0; // update the sky texture for (S32 i = 0; i < 6; ++i) { mSkyTex[i].create(1.0f); mShinyTex[i].create(1.0f); } // update the environment map if (mCubeMap) { std::vector > images; images.reserve(6); for (S32 side = 0; side < 6; side++) { images.push_back(mShinyTex[side].getImageRaw(TRUE)); } mCubeMap->init(images); gGL.getTexUnit(0)->disable(); } } } gPipeline.markRebuild(gSky.mVOGroundp->mDrawable, LLDrawable::REBUILD_ALL, TRUE); mForceUpdate = FALSE; } } if (mDrawable.notNull() && mDrawable->getFace(0) && !mDrawable->getFace(0)->getVertexBuffer()) { gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME, TRUE); } return TRUE; } void LLVOSky::updateTextures() { if (mSunTexturep[0]) { mSunTexturep[0]->addTextureStats( (F32)MAX_IMAGE_AREA ); } if (mSunTexturep[1]) { mSunTexturep[1]->addTextureStats( (F32)MAX_IMAGE_AREA ); } if (mMoonTexturep[0]) { mMoonTexturep[0]->addTextureStats( (F32)MAX_IMAGE_AREA ); } if (mMoonTexturep[1]) { mMoonTexturep[1]->addTextureStats( (F32)MAX_IMAGE_AREA ); } if (mBloomTexturep[0]) { mBloomTexturep[0]->addTextureStats( (F32)MAX_IMAGE_AREA ); } if (mBloomTexturep[1]) { mBloomTexturep[1]->addTextureStats( (F32)MAX_IMAGE_AREA ); } } LLDrawable *LLVOSky::createDrawable(LLPipeline *pipeline) { pipeline->allocDrawable(this); mDrawable->setLit(FALSE); LLDrawPoolSky *poolp = (LLDrawPoolSky*) gPipeline.getPool(LLDrawPool::POOL_SKY); poolp->setSkyTex(mSkyTex); mDrawable->setRenderType(LLPipeline::RENDER_TYPE_SKY); for (S32 i = 0; i < 6; ++i) { mFace[FACE_SIDE0 + i] = mDrawable->addFace(poolp, NULL); } mFace[FACE_SUN] = mDrawable->addFace(poolp, nullptr); mFace[FACE_MOON] = mDrawable->addFace(poolp, nullptr); mFace[FACE_BLOOM] = mDrawable->addFace(poolp, nullptr); return mDrawable; } void LLVOSky::setSunScale(F32 sun_scale) { mSunScale = sun_scale; } void LLVOSky::setMoonScale(F32 moon_scale) { mMoonScale = moon_scale; } void LLVOSky::setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next) { // We test the UUIDs here because we explicitly do not want the default image returned by getFetchedTexture in that case... mSunTexturep[0] = sun_texture.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(sun_texture, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_UI); mSunTexturep[1] = sun_texture_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(sun_texture_next, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_UI); if (mFace[FACE_SUN]) { if (mSunTexturep[0]) { mSunTexturep[0]->setAddressMode(LLTexUnit::TAM_CLAMP); } mFace[FACE_SUN]->setTexture(LLRender::DIFFUSE_MAP, mSunTexturep[0]); if (mSunTexturep[1]) { mSunTexturep[1]->setAddressMode(LLTexUnit::TAM_CLAMP); } mFace[FACE_SUN]->setTexture(LLRender::ALTERNATE_DIFFUSE_MAP, mSunTexturep[1]); } } void LLVOSky::setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next) { LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); LLUUID moon_tex = moon_texture.isNull() ? psky->GetDefaultMoonTextureId() : moon_texture; LLUUID moon_tex_next = moon_texture_next.isNull() ? (moon_texture.isNull() ? psky->GetDefaultMoonTextureId() : moon_texture) : moon_texture_next; mMoonTexturep[0] = moon_tex.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(moon_tex, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_UI); mMoonTexturep[1] = moon_tex_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(moon_tex_next, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_UI); if (mFace[FACE_MOON]) { if (mMoonTexturep[0]) { mMoonTexturep[0]->setAddressMode(LLTexUnit::TAM_CLAMP); } mFace[FACE_MOON]->setTexture(LLRender::DIFFUSE_MAP, mMoonTexturep[0]); if (mMoonTexturep[1]) { mMoonTexturep[1]->setAddressMode(LLTexUnit::TAM_CLAMP); mFace[FACE_MOON]->setTexture(LLRender::ALTERNATE_DIFFUSE_MAP, mMoonTexturep[1]); } } } void LLVOSky::setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next) { LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); LLUUID cloud_noise_tex = cloud_noise_texture.isNull() ? psky->GetDefaultCloudNoiseTextureId() : cloud_noise_texture; LLUUID cloud_noise_tex_next = cloud_noise_texture_next.isNull() ? (cloud_noise_texture.isNull() ? psky->GetDefaultCloudNoiseTextureId() : cloud_noise_texture) : cloud_noise_texture_next; mCloudNoiseTexturep[0] = LLViewerTextureManager::getFetchedTexture(cloud_noise_tex, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_UI); mCloudNoiseTexturep[1] = LLViewerTextureManager::getFetchedTexture(cloud_noise_tex_next, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_UI); if (mCloudNoiseTexturep[0]) { mCloudNoiseTexturep[0]->setAddressMode(LLTexUnit::TAM_WRAP); } if (mCloudNoiseTexturep[1]) { mCloudNoiseTexturep[1]->setAddressMode(LLTexUnit::TAM_WRAP); } } void LLVOSky::setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next) { LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); LLUUID bloom_tex = bloom_texture.isNull() ? psky->GetDefaultBloomTextureId() : bloom_texture; LLUUID bloom_tex_next = bloom_texture_next.isNull() ? (bloom_texture.isNull() ? psky->GetDefaultBloomTextureId() : bloom_texture) : bloom_texture_next; mBloomTexturep[0] = bloom_tex.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(bloom_tex, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_UI); mBloomTexturep[1] = bloom_tex_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(bloom_tex_next, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_UI); if (mBloomTexturep[0]) { mBloomTexturep[0]->setAddressMode(LLTexUnit::TAM_CLAMP); } if (mBloomTexturep[1]) { mBloomTexturep[1]->setAddressMode(LLTexUnit::TAM_CLAMP); } } static LLTrace::BlockTimerStatHandle FTM_GEO_SKY("Sky Geometry"); BOOL LLVOSky::updateGeometry(LLDrawable *drawable) { LL_RECORD_BLOCK_TIME(FTM_GEO_SKY); if (mFace[FACE_REFLECTION] == NULL) { LLDrawPoolWater *poolp = (LLDrawPoolWater*) gPipeline.getPool(LLDrawPool::POOL_WATER); if (gPipeline.getPool(LLDrawPool::POOL_WATER)->getVertexShaderLevel() != 0) { mFace[FACE_REFLECTION] = drawable->addFace(poolp, NULL); } } mCameraPosAgent = drawable->getPositionAgent(); mEarthCenter.mV[0] = mCameraPosAgent.mV[0]; mEarthCenter.mV[1] = mCameraPosAgent.mV[1]; LLVector3 v_agent[8]; for (S32 i = 0; i < 8; ++i) { F32 x_sgn = (i&1) ? 1.f : -1.f; F32 y_sgn = (i&2) ? 1.f : -1.f; F32 z_sgn = (i&4) ? 1.f : -1.f; v_agent[i] = HORIZON_DIST * SKY_BOX_MULT * LLVector3(x_sgn, y_sgn, z_sgn); } LLStrider verticesp; LLStrider normalsp; LLStrider texCoordsp; LLStrider indicesp; U16 index_offset; LLFace *face; for (S32 side = 0; side < 6; ++side) { face = mFace[FACE_SIDE0 + side]; if (!face->getVertexBuffer()) { face->setSize(4, 6); face->setGeomIndex(0); face->setIndicesIndex(0); LLVertexBuffer* buff = new LLVertexBuffer(LLDrawPoolSky::VERTEX_DATA_MASK, GL_STREAM_DRAW_ARB); buff->allocateBuffer(4, 6, TRUE); face->setVertexBuffer(buff); index_offset = face->getGeometry(verticesp,normalsp,texCoordsp, indicesp); S32 vtx = 0; S32 curr_bit = side >> 1; // 0/1 = Z axis, 2/3 = Y, 4/5 = X S32 side_dir = side & 1; // even - 0, odd - 1 S32 i_bit = (curr_bit + 2) % 3; S32 j_bit = (i_bit + 2) % 3; LLVector3 axis; axis.mV[curr_bit] = 1; face->mCenterAgent = (F32)((side_dir << 1) - 1) * axis * HORIZON_DIST; vtx = side_dir << curr_bit; *(verticesp++) = v_agent[vtx]; *(verticesp++) = v_agent[vtx | 1 << j_bit]; *(verticesp++) = v_agent[vtx | 1 << i_bit]; *(verticesp++) = v_agent[vtx | 1 << i_bit | 1 << j_bit]; *(texCoordsp++) = TEX00; *(texCoordsp++) = TEX01; *(texCoordsp++) = TEX10; *(texCoordsp++) = TEX11; // Triangles for each side *indicesp++ = index_offset + 0; *indicesp++ = index_offset + 1; *indicesp++ = index_offset + 3; *indicesp++ = index_offset + 0; *indicesp++ = index_offset + 3; *indicesp++ = index_offset + 2; buff->flush(); } } const LLVector3 &look_at = LLViewerCamera::getInstance()->getAtAxis(); LLVector3 right = look_at % LLVector3::z_axis; LLVector3 up = right % look_at; right.normalize(); up.normalize(); bool draw_sun = updateHeavenlyBodyGeometry(drawable, mSunScale, FACE_SUN, mSun, up, right); bool draw_moon = updateHeavenlyBodyGeometry(drawable, mMoonScale, FACE_MOON, mMoon, up, right); draw_sun &= LLEnvironment::getInstance()->getIsSunUp(); draw_moon &= LLEnvironment::getInstance()->getIsMoonUp(); mSun.setDraw(draw_sun); mMoon.setDraw(draw_moon); const F32 water_height = gAgent.getRegion()->getWaterHeight() + 0.01f; // LLWorld::getInstance()->getWaterHeight() + 0.01f; const F32 camera_height = mCameraPosAgent.mV[2]; const F32 height_above_water = camera_height - water_height; bool sun_flag = FALSE; if (mSun.isVisible()) { sun_flag = !mMoon.isVisible() || ((look_at * mSun.getDirection()) > 0); } bool above_water = (height_above_water > 0); bool render_ref = above_water && gPipeline.getPool(LLDrawPool::POOL_WATER)->getVertexShaderLevel() == 0; setDrawRefl(above_water ? (sun_flag ? 0 : 1) : -1); if (render_ref) { updateReflectionGeometry(drawable, height_above_water, mSun); } LLPipeline::sCompiles++; return TRUE; } bool LLVOSky::updateHeavenlyBodyGeometry(LLDrawable *drawable, F32 scale, const S32 f, LLHeavenBody& hb, const LLVector3 &up, const LLVector3 &right) { mHeavenlyBodyUpdated = TRUE ; LLStrider verticesp; LLStrider normalsp; LLStrider texCoordsp; LLStrider indicesp; S32 index_offset; LLFace *facep; LLVector3 to_dir = hb.getDirection(); LLVector3 draw_pos = to_dir * HEAVENLY_BODY_DIST; LLVector3 hb_right = to_dir % LLVector3::z_axis; LLVector3 hb_up = hb_right % to_dir; hb_right.normalize(); hb_up.normalize(); const F32 enlargm_factor = ( 1 - to_dir.mV[2] ); F32 horiz_enlargement = 1 + enlargm_factor * 0.3f; F32 vert_enlargement = 1 + enlargm_factor * 0.2f; const LLVector3 scaled_right = horiz_enlargement * scale * HEAVENLY_BODY_DIST * HEAVENLY_BODY_FACTOR * hb.getDiskRadius() * hb_right; const LLVector3 scaled_up = vert_enlargement * scale * HEAVENLY_BODY_DIST * HEAVENLY_BODY_FACTOR * hb.getDiskRadius() * hb_up; LLVector3 v_clipped[4]; v_clipped[0] = draw_pos - scaled_right + scaled_up; v_clipped[1] = draw_pos - scaled_right - scaled_up; v_clipped[2] = draw_pos + scaled_right + scaled_up; v_clipped[3] = draw_pos + scaled_right - scaled_up; hb.setVisible(TRUE); facep = mFace[f]; if (!facep->getVertexBuffer()) { facep->setSize(4, 6); LLVertexBuffer* buff = new LLVertexBuffer(LLDrawPoolSky::VERTEX_DATA_MASK, GL_STREAM_DRAW_ARB); if (!buff->allocateBuffer(facep->getGeomCount(), facep->getIndicesCount(), TRUE)) { LL_WARNS() << "Failed to allocate Vertex Buffer for vosky to " << facep->getGeomCount() << " vertices and " << facep->getIndicesCount() << " indices" << LL_ENDL; } facep->setGeomIndex(0); facep->setIndicesIndex(0); facep->setVertexBuffer(buff); } llassert(facep->getVertexBuffer()->getNumIndices() == 6); index_offset = facep->getGeometry(verticesp,normalsp,texCoordsp, indicesp); if (-1 == index_offset) { return TRUE; } for (S32 vtx = 0; vtx < 4; ++vtx) { hb.corner(vtx) = v_clipped[vtx]; *(verticesp++) = hb.corner(vtx) + mCameraPosAgent; } *(texCoordsp++) = TEX01; *(texCoordsp++) = TEX00; *(texCoordsp++) = TEX11; *(texCoordsp++) = TEX10; *indicesp++ = index_offset + 0; *indicesp++ = index_offset + 2; *indicesp++ = index_offset + 1; *indicesp++ = index_offset + 1; *indicesp++ = index_offset + 2; *indicesp++ = index_offset + 3; facep->getVertexBuffer()->flush(); return TRUE; } F32 dtReflection(const LLVector3& p, F32 cos_dir_from_top, F32 sin_dir_from_top, F32 diff_angl_dir) { LLVector3 P = p; P.normalize(); const F32 cos_dir_angle = -P.mV[VZ]; const F32 sin_dir_angle = sqrt(1 - cos_dir_angle * cos_dir_angle); F32 cos_diff_angles = cos_dir_angle * cos_dir_from_top + sin_dir_angle * sin_dir_from_top; F32 diff_angles; if (cos_diff_angles > (1 - 1e-7)) diff_angles = 0; else diff_angles = acos(cos_diff_angles); const F32 rel_diff_angles = diff_angles / diff_angl_dir; const F32 dt = 1 - rel_diff_angles; return (dt < 0) ? 0 : dt; } F32 dtClip(const LLVector3& v0, const LLVector3& v1, F32 far_clip2) { F32 dt_clip; const LLVector3 otrezok = v1 - v0; const F32 A = otrezok.lengthSquared(); const F32 B = v0 * otrezok; const F32 C = v0.lengthSquared() - far_clip2; const F32 det = sqrt(B*B - A*C); dt_clip = (-B - det) / A; if ((dt_clip < 0) || (dt_clip > 1)) dt_clip = (-B + det) / A; return dt_clip; } void LLVOSky::updateReflectionGeometry(LLDrawable *drawable, F32 H, const LLHeavenBody& HB) { const LLVector3 &look_at = LLViewerCamera::getInstance()->getAtAxis(); // const F32 water_height = gAgent.getRegion()->getWaterHeight() + 0.001f; // LLWorld::getInstance()->getWaterHeight() + 0.001f; LLVector3 to_dir = HB.getDirection(); LLVector3 hb_pos = to_dir * (HORIZON_DIST - 10); LLVector3 to_dir_proj = to_dir; to_dir_proj.mV[VZ] = 0; to_dir_proj.normalize(); LLVector3 Right = to_dir % LLVector3::z_axis; LLVector3 Up = Right % to_dir; Right.normalize(); Up.normalize(); // finding angle between look direction and sprite. LLVector3 look_at_right = look_at % LLVector3::z_axis; look_at_right.normalize(); const F32 enlargm_factor = ( 1 - to_dir.mV[2] ); F32 horiz_enlargement = 1 + enlargm_factor * 0.3f; F32 vert_enlargement = 1 + enlargm_factor * 0.2f; F32 vert_size = vert_enlargement * HEAVENLY_BODY_SCALE * HB.getDiskRadius(); Right *= /*cos_lookAt_toDir */ horiz_enlargement * HEAVENLY_BODY_SCALE * HB.getDiskRadius(); Up *= vert_size; LLVector3 v_corner[2]; LLVector3 stretch_corner[2]; LLVector3 top_hb = v_corner[0] = stretch_corner[0] = hb_pos - Right + Up; v_corner[1] = stretch_corner[1] = hb_pos - Right - Up; LLVector2 TEX0t = TEX00; LLVector2 TEX1t = TEX10; LLVector3 lower_corner = v_corner[1]; top_hb.normalize(); const F32 cos_angle_of_view = fabs(top_hb.mV[VZ]); const F32 extension = llmin (5.0f, 1.0f / cos_angle_of_view); const S32 cols = 1; const S32 raws = lltrunc(16 * extension); S32 quads = cols * raws; stretch_corner[0] = lower_corner + extension * (stretch_corner[0] - lower_corner); stretch_corner[1] = lower_corner + extension * (stretch_corner[1] - lower_corner); F32 cos_dir_from_top[2]; LLVector3 dir = stretch_corner[0]; dir.normalize(); cos_dir_from_top[0] = dir.mV[VZ]; dir = stretch_corner[1]; dir.normalize(); cos_dir_from_top[1] = dir.mV[VZ]; const F32 sin_dir_from_top = sqrt(1 - cos_dir_from_top[0] * cos_dir_from_top[0]); const F32 sin_dir_from_top2 = sqrt(1 - cos_dir_from_top[1] * cos_dir_from_top[1]); const F32 cos_diff_dir = cos_dir_from_top[0] * cos_dir_from_top[1] + sin_dir_from_top * sin_dir_from_top2; const F32 diff_angl_dir = acos(cos_diff_dir); v_corner[0] = stretch_corner[0]; v_corner[1] = lower_corner; LLVector2 TEX0tt = TEX01; LLVector2 TEX1tt = TEX11; LLVector3 v_refl_corner[4]; LLVector3 v_sprite_corner[4]; S32 vtx; for (vtx = 0; vtx < 2; ++vtx) { LLVector3 light_proj = v_corner[vtx]; light_proj.normalize(); const F32 z = light_proj.mV[VZ]; const F32 sin_angle = sqrt(1 - z * z); light_proj *= 1.f / sin_angle; light_proj.mV[VZ] = 0; const F32 to_refl_point = H * sin_angle / fabs(z); v_refl_corner[vtx] = to_refl_point * light_proj; } for (vtx = 2; vtx < 4; ++vtx) { const LLVector3 to_dir_vec = (to_dir_proj * v_refl_corner[vtx-2]) * to_dir_proj; v_refl_corner[vtx] = v_refl_corner[vtx-2] + 2 * (to_dir_vec - v_refl_corner[vtx-2]); } for (vtx = 0; vtx < 4; ++vtx) v_refl_corner[vtx].mV[VZ] -= H; S32 side = 0; LLVector3 refl_corn_norm[2]; refl_corn_norm[0] = v_refl_corner[1]; refl_corn_norm[0].normalize(); refl_corn_norm[1] = v_refl_corner[3]; refl_corn_norm[1].normalize(); F32 cos_refl_look_at[2]; cos_refl_look_at[0] = refl_corn_norm[0] * look_at; cos_refl_look_at[1] = refl_corn_norm[1] * look_at; if (cos_refl_look_at[1] > cos_refl_look_at[0]) { side = 2; } //const F32 far_clip = (LLViewerCamera::getInstance()->getFar() - 0.01) / far_clip_factor; const F32 far_clip = 512; const F32 far_clip2 = far_clip*far_clip; F32 dt_clip; F32 vtx_near2, vtx_far2; if ((vtx_far2 = v_refl_corner[side].lengthSquared()) > far_clip2) { // whole thing is sprite: reflection is beyond far clip plane. dt_clip = 1.1f; quads = 1; } else if ((vtx_near2 = v_refl_corner[side+1].lengthSquared()) > far_clip2) { // part is reflection, the rest is sprite. dt_clip = dtClip(v_refl_corner[side + 1], v_refl_corner[side], far_clip2); const LLVector3 P = (1 - dt_clip) * v_refl_corner[side + 1] + dt_clip * v_refl_corner[side]; F32 dt_tex = dtReflection(P, cos_dir_from_top[0], sin_dir_from_top, diff_angl_dir); TEX0tt = LLVector2(0, dt_tex); TEX1tt = LLVector2(1, dt_tex); quads++; } else { // whole thing is correct reflection. dt_clip = -0.1f; } LLFace *face = mFace[FACE_REFLECTION]; if (face) { if (!face->getVertexBuffer() || quads * 4 != face->getGeomCount()) { face->setSize(quads * 4, quads * 6); LLVertexBuffer* buff = new LLVertexBuffer(LLDrawPoolWater::VERTEX_DATA_MASK, GL_STREAM_DRAW_ARB); if (!buff->allocateBuffer(face->getGeomCount(), face->getIndicesCount(), TRUE)) { LL_WARNS() << "Failed to allocate Vertex Buffer for vosky to " << face->getGeomCount() << " vertices and " << face->getIndicesCount() << " indices" << LL_ENDL; } face->setIndicesIndex(0); face->setGeomIndex(0); face->setVertexBuffer(buff); } LLStrider verticesp; LLStrider normalsp; LLStrider texCoordsp; LLStrider indicesp; S32 index_offset; index_offset = face->getGeometry(verticesp, normalsp, texCoordsp, indicesp); if (-1 == index_offset) { return; } LLColor3 hb_col3 = HB.getInterpColor(); hb_col3.clamp(); const LLColor4 hb_col = LLColor4(hb_col3); const F32 min_attenuation = 0.4f; const F32 max_attenuation = 0.7f; const F32 attenuation = min_attenuation + cos_angle_of_view * (max_attenuation - min_attenuation); LLColor4 hb_refl_col = (1 - attenuation) * hb_col + attenuation * getSkyFogColor(); face->setFaceColor(hb_refl_col); LLVector3 v_far[2]; v_far[0] = v_refl_corner[1]; v_far[1] = v_refl_corner[3]; if (dt_clip > 0) { if (dt_clip >= 1) { for (S32 vtx = 0; vtx < 4; ++vtx) { F32 ratio = far_clip / v_refl_corner[vtx].length(); *(verticesp++) = v_refl_corner[vtx] = ratio * v_refl_corner[vtx] + mCameraPosAgent; } const LLVector3 draw_pos = 0.25 * (v_refl_corner[0] + v_refl_corner[1] + v_refl_corner[2] + v_refl_corner[3]); face->mCenterAgent = draw_pos; } else { F32 ratio = far_clip / v_refl_corner[1].length(); v_sprite_corner[1] = v_refl_corner[1] * ratio; ratio = far_clip / v_refl_corner[3].length(); v_sprite_corner[3] = v_refl_corner[3] * ratio; v_refl_corner[1] = (1 - dt_clip) * v_refl_corner[1] + dt_clip * v_refl_corner[0]; v_refl_corner[3] = (1 - dt_clip) * v_refl_corner[3] + dt_clip * v_refl_corner[2]; v_sprite_corner[0] = v_refl_corner[1]; v_sprite_corner[2] = v_refl_corner[3]; for (S32 vtx = 0; vtx < 4; ++vtx) { *(verticesp++) = v_sprite_corner[vtx] + mCameraPosAgent; } const LLVector3 draw_pos = 0.25 * (v_refl_corner[0] + v_sprite_corner[1] + v_refl_corner[2] + v_sprite_corner[3]); face->mCenterAgent = draw_pos; } *(texCoordsp++) = TEX0tt; *(texCoordsp++) = TEX0t; *(texCoordsp++) = TEX1tt; *(texCoordsp++) = TEX1t; *indicesp++ = index_offset + 0; *indicesp++ = index_offset + 2; *indicesp++ = index_offset + 1; *indicesp++ = index_offset + 1; *indicesp++ = index_offset + 2; *indicesp++ = index_offset + 3; index_offset += 4; } if (dt_clip < 1) { if (dt_clip <= 0) { const LLVector3 draw_pos = 0.25 * (v_refl_corner[0] + v_refl_corner[1] + v_refl_corner[2] + v_refl_corner[3]); face->mCenterAgent = draw_pos; } const F32 raws_inv = 1.f / raws; const F32 cols_inv = 1.f / cols; LLVector3 left = v_refl_corner[0] - v_refl_corner[1]; LLVector3 right = v_refl_corner[2] - v_refl_corner[3]; left *= raws_inv; right *= raws_inv; for (S32 raw = 0; raw < raws; ++raw) { F32 dt_v0 = raw * raws_inv; F32 dt_v1 = (raw + 1) * raws_inv; const LLVector3 BL = v_refl_corner[1] + (F32)raw * left; const LLVector3 BR = v_refl_corner[3] + (F32)raw * right; const LLVector3 EL = BL + left; const LLVector3 ER = BR + right; dt_v0 = dt_v1 = dtReflection(EL, cos_dir_from_top[0], sin_dir_from_top, diff_angl_dir); for (S32 col = 0; col < cols; ++col) { F32 dt_h0 = col * cols_inv; *(verticesp++) = (1 - dt_h0) * EL + dt_h0 * ER + mCameraPosAgent; *(verticesp++) = (1 - dt_h0) * BL + dt_h0 * BR + mCameraPosAgent; F32 dt_h1 = (col + 1) * cols_inv; *(verticesp++) = (1 - dt_h1) * EL + dt_h1 * ER + mCameraPosAgent; *(verticesp++) = (1 - dt_h1) * BL + dt_h1 * BR + mCameraPosAgent; *(texCoordsp++) = LLVector2(dt_h0, dt_v1); *(texCoordsp++) = LLVector2(dt_h0, dt_v0); *(texCoordsp++) = LLVector2(dt_h1, dt_v1); *(texCoordsp++) = LLVector2(dt_h1, dt_v0); *indicesp++ = index_offset + 0; *indicesp++ = index_offset + 2; *indicesp++ = index_offset + 1; *indicesp++ = index_offset + 1; *indicesp++ = index_offset + 2; *indicesp++ = index_offset + 3; index_offset += 4; } } } face->getVertexBuffer()->flush(); } } void LLVOSky::updateFog(const F32 distance) { LLEnvironment& environment = LLEnvironment::instance(); if (environment.getCurrentSky() != nullptr) { LLVector3 light_dir = LLVector3(environment.getClampedLightNorm()); m_legacyAtmospherics.updateFog(distance, light_dir); } } void LLVOSky::setSunAndMoonDirectionsCFR(const LLVector3 &sun_dir_cfr, const LLVector3 &moon_dir_cfr) { mSun.setDirection(sun_dir_cfr); mMoon.setDirection(moon_dir_cfr); mLastLightingDirection = mSun.getDirection(); // Push the sun "South" as it approaches directly overhead so that we can always see bump mapping // on the upward facing faces of cubes. { // Same as dot product with the up direction + clamp. F32 sunDot = llmax(0.f, sun_dir_cfr.mV[2]); sunDot *= sunDot; // Create normalized vector that has the sunDir pushed south about an hour and change. LLVector3 adjustedDir = (sun_dir_cfr + LLVector3(0.f, -0.70711f, 0.70711f)) * 0.5f; // Blend between normal sun dir and adjusted sun dir based on how close we are // to having the sun overhead. mBumpSunDir = adjustedDir * sunDot + sun_dir_cfr * (1.0f - sunDot); mBumpSunDir.normalize(); } updateDirections(); LLSkyTex::stepCurrent(); } void LLVOSky::setSunDirectionCFR(const LLVector3 &sun_dir_cfr) { mSun.setDirection(sun_dir_cfr); mLastLightingDirection = mSun.getDirection(); // Push the sun "South" as it approaches directly overhead so that we can always see bump mapping // on the upward facing faces of cubes. { // Same as dot product with the up direction + clamp. F32 sunDot = llmax(0.f, sun_dir_cfr.mV[2]); sunDot *= sunDot; // Create normalized vector that has the sunDir pushed south about an hour and change. LLVector3 adjustedDir = (sun_dir_cfr + LLVector3(0.f, -0.70711f, 0.70711f)) * 0.5f; // Blend between normal sun dir and adjusted sun dir based on how close we are // to having the sun overhead. mBumpSunDir = adjustedDir * sunDot + sun_dir_cfr * (1.0f - sunDot); mBumpSunDir.normalize(); } updateDirections(); LLSkyTex::stepCurrent(); } void LLVOSky::setMoonDirectionCFR(const LLVector3 &moon_dir_cfr) { mMoon.setDirection(moon_dir_cfr); updateDirections(); LLSkyTex::stepCurrent(); }