/** * @file llenvmanager.cpp * @brief Implementation of classes managing WindLight and water settings. * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2011, 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 "llenvironment.h" #include #include "llagent.h" #include "llviewercontrol.h" // for gSavedSettings #include "llviewerregion.h" #include "llwlhandlers.h" #include "lltrans.h" #include "lltrace.h" #include "llfasttimer.h" #include "llviewercamera.h" #include "pipeline.h" #include "llsky.h" #include "llviewershadermgr.h" #include "llparcel.h" #include "llviewerparcelmgr.h" #include "llsdserialize.h" #include "lldiriterator.h" #include "llsettingsvo.h" #include "llnotificationsutil.h" #include "llregioninfomodel.h" #include #include "llatmosphere.h" #include "llagent.h" #include "roles_constants.h" #include "llestateinfomodel.h" #include "lldispatcher.h" #include "llviewergenericmessage.h" #include "llexperiencelog.h" //========================================================================= namespace { const std::string KEY_ENVIRONMENT("environment"); const std::string KEY_DAYASSET("day_asset"); const std::string KEY_DAYCYCLE("day_cycle"); const std::string KEY_DAYHASH("day_hash"); const std::string KEY_DAYLENGTH("day_length"); const std::string KEY_DAYNAME("day_name"); const std::string KEY_DAYNAMES("day_names"); const std::string KEY_DAYOFFSET("day_offset"); const std::string KEY_ENVVERSION("env_version"); const std::string KEY_ISDEFAULT("is_default"); const std::string KEY_PARCELID("parcel_id"); const std::string KEY_REGIONID("region_id"); const std::string KEY_TRACKALTS("track_altitudes"); const std::string KEY_FLAGS("flags"); const std::string MESSAGE_PUSHENVIRONMENT("PushExpEnvironment"); const std::string ACTION_CLEARENVIRONMENT("ClearEnvironment"); const std::string ACTION_PUSHFULLENVIRONMENT("PushFullEnvironment"); const std::string ACTION_PUSHPARTIALENVIRONMENT("PushPartialEnvironment"); const std::string KEY_ASSETID("asset_id"); const std::string KEY_TRANSITIONTIME("transition_time"); const std::string KEY_ACTION("action"); const std::string KEY_ACTIONDATA("action_data"); const std::string KEY_EXPERIENCEID("public_id"); const std::string KEY_OBJECTNAME("ObjectName"); // some of these do not conform to the '_' format. const std::string KEY_PARCELNAME("ParcelName"); // But changing these would also alter the Experience Log requirements. const std::string KEY_COUNT("Count"); const std::string LISTENER_NAME("LLEnvironmentSingleton"); const std::string PUMP_EXPERIENCE("experience_permission"); const std::string LOCAL_ENV_STORAGE_FILE("local_environment_data.bin"); //--------------------------------------------------------------------- LLTrace::BlockTimerStatHandle FTM_ENVIRONMENT_UPDATE("Update Environment Tick"); LLSettingsBase::Seconds DEFAULT_UPDATE_THRESHOLD(10.0); const LLSettingsBase::Seconds MINIMUM_SPANLENGTH(0.01f); //--------------------------------------------------------------------- inline LLSettingsBase::TrackPosition get_wrapping_distance(LLSettingsBase::TrackPosition begin, LLSettingsBase::TrackPosition end) { if (begin < end) { return end - begin; } else if (begin > end) { return LLSettingsBase::TrackPosition(1.0) - (begin - end); } return 1.0f; } LLSettingsDay::CycleTrack_t::iterator get_wrapping_atafter(LLSettingsDay::CycleTrack_t &collection, const LLSettingsBase::TrackPosition& key) { if (collection.empty()) return collection.end(); LLSettingsDay::CycleTrack_t::iterator it = collection.upper_bound(key); if (it == collection.end()) { // wrap around it = collection.begin(); } return it; } LLSettingsDay::CycleTrack_t::iterator get_wrapping_atbefore(LLSettingsDay::CycleTrack_t &collection, const LLSettingsBase::TrackPosition& key) { if (collection.empty()) return collection.end(); LLSettingsDay::CycleTrack_t::iterator it = collection.lower_bound(key); if (it == collection.end()) { // all keyframes are lower, take the last one. --it; // we know the range is not empty } else if ((*it).first > key) { // the keyframe we are interested in is smaller than the found. if (it == collection.begin()) it = collection.end(); --it; } return it; } LLSettingsDay::TrackBound_t get_bounding_entries(LLSettingsDay::CycleTrack_t &track, const LLSettingsBase::TrackPosition& keyframe) { return LLSettingsDay::TrackBound_t(get_wrapping_atbefore(track, keyframe), get_wrapping_atafter(track, keyframe)); } // Find normalized track position of given time along full length of cycle inline LLSettingsBase::TrackPosition convert_time_to_position(const LLSettingsBase::Seconds& time, const LLSettingsBase::Seconds& len) { // early out to avoid divide by zero. if len is zero then jump to end position if (len == 0.f) return 1.f; LLSettingsBase::TrackPosition position = LLSettingsBase::TrackPosition(fmod((F64)time, (F64)len) / (F64)len); return llclamp(position, 0.0f, 1.0f); } inline LLSettingsBase::BlendFactor convert_time_to_blend_factor(const LLSettingsBase::Seconds& time, const LLSettingsBase::Seconds& len, LLSettingsDay::CycleTrack_t &track) { LLSettingsBase::TrackPosition position = convert_time_to_position(time, len); LLSettingsDay::TrackBound_t bounds(get_bounding_entries(track, position)); LLSettingsBase::TrackPosition spanlength(get_wrapping_distance((*bounds.first).first, (*bounds.second).first)); if (position < (*bounds.first).first) position += 1.0; LLSettingsBase::TrackPosition start = position - (*bounds.first).first; return static_cast(start / spanlength); } //--------------------------------------------------------------------- class LLTrackBlenderLoopingTime : public LLSettingsBlenderTimeDelta { public: LLTrackBlenderLoopingTime(const LLSettingsBase::ptr_t &target, const LLSettingsDay::ptr_t &day, S32 trackno, LLSettingsBase::Seconds cyclelength, LLSettingsBase::Seconds cycleoffset, LLSettingsBase::Seconds updateThreshold) : LLSettingsBlenderTimeDelta(target, LLSettingsBase::ptr_t(), LLSettingsBase::ptr_t(), LLSettingsBase::Seconds(1.0)), mDay(day), mTrackNo(0), mCycleLength(cyclelength), mCycleOffset(cycleoffset) { // must happen prior to getBoundingEntries call... mTrackNo = selectTrackNumber(trackno); LLSettingsBase::Seconds now(getAdjustedNow()); LLSettingsDay::TrackBound_t initial = getBoundingEntries(now); mInitial = (*initial.first).second; mFinal = (*initial.second).second; mBlendSpan = getSpanTime(initial); initializeTarget(now); setOnFinished([this](const LLSettingsBlender::ptr_t &){ onFinishedSpan(); }); } void switchTrack(S32 trackno, const LLSettingsBase::TrackPosition&) override { S32 use_trackno = selectTrackNumber(trackno); if (use_trackno == mTrackNo) { // results in no change return; } LLSettingsBase::ptr_t pstartsetting = mTarget->buildDerivedClone(); mTrackNo = use_trackno; LLSettingsBase::Seconds now = getAdjustedNow() + LLEnvironment::TRANSITION_ALTITUDE; LLSettingsDay::TrackBound_t bounds = getBoundingEntries(now); LLSettingsBase::ptr_t pendsetting = (*bounds.first).second->buildDerivedClone(); LLSettingsBase::TrackPosition targetpos = convert_time_to_position(now, mCycleLength) - (*bounds.first).first; LLSettingsBase::TrackPosition targetspan = get_wrapping_distance((*bounds.first).first, (*bounds.second).first); LLSettingsBase::BlendFactor blendf = calculateBlend(targetpos, targetspan); pendsetting->blend((*bounds.second).second, blendf); reset(pstartsetting, pendsetting, LLEnvironment::TRANSITION_ALTITUDE); } protected: S32 selectTrackNumber(S32 trackno) { if (trackno == 0) { // We are dealing with the water track. There is only ever one. return trackno; } for (S32 test = trackno; test != 0; --test) { // Find the track below the requested one with data. LLSettingsDay::CycleTrack_t &track = mDay->getCycleTrack(test); if (!track.empty()) return test; } return 1; } LLSettingsDay::TrackBound_t getBoundingEntries(LLSettingsBase::Seconds time) { LLSettingsDay::CycleTrack_t &wtrack = mDay->getCycleTrack(mTrackNo); LLSettingsBase::TrackPosition position = convert_time_to_position(time, mCycleLength); LLSettingsDay::TrackBound_t bounds = get_bounding_entries(wtrack, position); return bounds; } void initializeTarget(LLSettingsBase::Seconds time) { LLSettingsBase::BlendFactor blendf(convert_time_to_blend_factor(time, mCycleLength, mDay->getCycleTrack(mTrackNo))); blendf = llclamp(blendf, 0.0, 0.999); setTimeSpent(LLSettingsBase::Seconds(blendf * mBlendSpan)); setBlendFactor(blendf); } LLSettingsBase::Seconds getAdjustedNow() const { LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch()); return (now + mCycleOffset); } LLSettingsBase::Seconds getSpanTime(const LLSettingsDay::TrackBound_t &bounds) const { LLSettingsBase::Seconds span = mCycleLength * get_wrapping_distance((*bounds.first).first, (*bounds.second).first); if (span < MINIMUM_SPANLENGTH) // for very short spans set a minimum length. span = MINIMUM_SPANLENGTH; return span; } private: LLSettingsDay::ptr_t mDay; S32 mTrackNo; LLSettingsBase::Seconds mCycleLength; LLSettingsBase::Seconds mCycleOffset; void onFinishedSpan() { LLSettingsBase::Seconds adjusted_now = getAdjustedNow(); LLSettingsDay::TrackBound_t next = getBoundingEntries(adjusted_now); LLSettingsBase::Seconds nextspan = getSpanTime(next); reset((*next.first).second, (*next.second).second, nextspan); // Recalculate (reinitialize) position. Because: // - 'delta' from applyTimeDelta accumulates errors (probably should be fixed/changed to absolute time) // - freezes and lag can result in reset being called too late, so we need to add missed time // - occasional time corrections can happen // - some transition switches can happen outside applyTimeDelta thus causing 'desync' from 'delta' (can be fixed by getting rid of delta) initializeTarget(adjusted_now); } }; class LLEnvironmentPushDispatchHandler : public LLDispatchHandler { public: virtual bool operator()(const LLDispatcher *, const std::string& key, const LLUUID& invoice, const sparam_t& strings) override { LLSD message; sparam_t::const_iterator it = strings.begin(); if (it != strings.end()) { const std::string& llsdRaw = *it++; std::istringstream llsdData(llsdRaw); if (!LLSDSerialize::deserialize(message, llsdData, llsdRaw.length())) { LL_WARNS() << "LLEnvironmentPushDispatchHandler: Attempted to read parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; } } message[KEY_EXPERIENCEID] = invoice; // Object Name if (it != strings.end()) { message[KEY_OBJECTNAME] = *it++; } // parcel Name if (it != strings.end()) { message[KEY_PARCELNAME] = *it++; } message[KEY_COUNT] = 1; LLEnvironment::instance().handleEnvironmentPush(message); return true; } }; LLEnvironmentPushDispatchHandler environment_push_dispatch_handler; template class LLSettingsInjected : public SETTINGT { public: typedef std::shared_ptr > ptr_t; LLSettingsInjected(typename SETTINGT::ptr_t source) : SETTINGT(), mSource(source), mLastSourceHash(0), mLastHash(0) {} virtual ~LLSettingsInjected() {}; typename SETTINGT::ptr_t getSource() const { return this->mSource; } void setSource(const typename SETTINGT::ptr_t &source) { if (source.get() == this) // do not set a source to itself. return; this->mSource = source; this->setDirtyFlag(true); this->mLastSourceHash = 0; } virtual bool isDirty() const override { return SETTINGT::isDirty() || (this->mSource->isDirty()); } virtual bool isVeryDirty() const override { return SETTINGT::isVeryDirty() || (this->mSource->isVeryDirty()); } void injectSetting(const std::string keyname, LLSD value, LLUUID experience_id, F32Seconds transition) { if (transition > 0.1) { typename Injection::ptr_t injection = std::make_shared(transition, keyname, value, true, experience_id); mInjections.push_back(injection); std::stable_sort(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a, const typename Injection::ptr_t &b) { return a->mTimeRemaining < b->mTimeRemaining; }); } else { mOverrideValues[keyname] = value; mOverrideExps[keyname] = experience_id; this->setDirtyFlag(true); } } void removeInjection(const std::string keyname, LLUUID experience, LLSettingsBase::Seconds transition) { injections_t injections_buf; for (auto it = mInjections.begin(); it != mInjections.end(); it++) { if ((keyname.empty() || ((*it)->mKeyName == keyname)) && (experience.isNull() || (experience == (*it)->mExperience))) { if (transition != LLEnvironment::TRANSITION_INSTANT) { typename Injection::ptr_t injection = std::make_shared(transition, keyname, (*it)->mLastValue, false, LLUUID::null); injections_buf.push_front(injection); } } else { injections_buf.push_front(*it); } } mInjections.clear(); mInjections = injections_buf; for (auto itexp = mOverrideExps.begin(); itexp != mOverrideExps.end();) { if (experience.isNull() || ((*itexp).second == experience)) { if (transition != LLEnvironment::TRANSITION_INSTANT) { typename Injection::ptr_t injection = std::make_shared(transition, (*itexp).first, mOverrideValues[(*itexp).first], false, LLUUID::null); mInjections.push_front(injection); } mOverrideValues.erase((*itexp).first); mOverrideExps.erase(itexp++); } else ++itexp; } std::stable_sort(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a, const typename Injection::ptr_t &b) { return a->mTimeRemaining < b->mTimeRemaining; }); } void removeInjections(LLUUID experience_id, LLSettingsBase::Seconds transition) { removeInjection(std::string(), experience_id, transition); } void injectExperienceValues(LLSD values, LLUUID experience_id, typename LLSettingsBase::Seconds transition) { for (auto it = values.beginMap(); it != values.endMap(); ++it) { injectSetting((*it).first, (*it).second, experience_id, transition); } this->setDirtyFlag(true); } void applyInjections(LLSettingsBase::Seconds delta) { this->mSettings = this->mSource->getSettings(); for (auto ito = mOverrideValues.beginMap(); ito != mOverrideValues.endMap(); ++ito) { this->mSettings[(*ito).first] = (*ito).second; } const LLSettingsBase::stringset_t &slerps = this->getSlerpKeys(); const LLSettingsBase::stringset_t &skips = this->getSkipInterpolateKeys(); const LLSettingsBase::stringset_t &specials = this->getSpecialKeys(); typename injections_t::iterator it; for (it = mInjections.begin(); it != mInjections.end(); ++it) { std::string key_name = (*it)->mKeyName; LLSD value = this->mSettings[key_name]; LLSD target = (*it)->mValue; if ((*it)->mFirstTime) (*it)->mFirstTime = false; else (*it)->mTimeRemaining -= delta; typename LLSettingsBase::BlendFactor mix = 1.0f - ((*it)->mTimeRemaining.value() / (*it)->mTransition.value()); if (mix >= 1.0) { if ((*it)->mBlendIn) { mOverrideValues[key_name] = target; mOverrideExps[key_name] = (*it)->mExperience; this->mSettings[key_name] = target; } else { this->mSettings.erase(key_name); } } else if (specials.find(key_name) != specials.end()) { updateSpecial(*it, mix); } else if (skips.find(key_name) == skips.end()) { if (!(*it)->mBlendIn) mix = 1.0 - mix; (*it)->mLastValue = this->interpolateSDValue(key_name, value, target, this->getParameterMap(), mix, slerps); this->mSettings[key_name] = (*it)->mLastValue; } } size_t hash = this->getHash(); if (hash != mLastHash) { this->setDirtyFlag(true); mLastHash = hash; } it = mInjections.begin(); it = std::find_if(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a) { return a->mTimeRemaining > 0.0f; }); if (it != mInjections.begin()) { mInjections.erase(mInjections.begin(), mInjections.end()); } } bool hasInjections() const { return (!mInjections.empty() || (mOverrideValues.size() > 0)); } protected: struct Injection { Injection(typename LLSettingsBase::Seconds transition, const std::string &keyname, LLSD value, bool blendin, LLUUID experince, S32 index = -1) : mTransition(transition), mTimeRemaining(transition), mKeyName(keyname), mValue(value), mExperience(experince), mIndex(index), mBlendIn(blendin), mFirstTime(true) {} typename LLSettingsBase::Seconds mTransition; typename LLSettingsBase::Seconds mTimeRemaining; std::string mKeyName; LLSD mValue; LLSD mLastValue; LLUUID mExperience; S32 mIndex; bool mBlendIn; bool mFirstTime; typedef std::shared_ptr ptr_t; }; virtual void updateSettings() override { static LLFrameTimer timer; if (!this->mSource) return; // clears the dirty flag on this object. Need to prevent triggering // more calls into this updateSettings LLSettingsBase::updateSettings(); resetSpecial(); if (this->mSource->isDirty()) { this->mSource->updateSettings(); } typename LLSettingsBase::Seconds delta(timer.getElapsedTimeAndResetF32()); SETTINGT::updateSettings(); if (!mInjections.empty()) this->setDirtyFlag(true); } LLSettingsBase::stringset_t getSpecialKeys() const; void resetSpecial(); void updateSpecial(const typename Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix); private: typedef std::map key_to_expid_t; typedef std::deque injections_t; size_t mLastSourceHash; size_t mLastHash; typename SETTINGT::ptr_t mSource; injections_t mInjections; LLSD mOverrideValues; key_to_expid_t mOverrideExps; }; template<> LLSettingsBase::stringset_t LLSettingsInjected::getSpecialKeys() const { static LLSettingsBase::stringset_t specialSet; if (specialSet.empty()) { specialSet.insert(SETTING_BLOOM_TEXTUREID); specialSet.insert(SETTING_RAINBOW_TEXTUREID); specialSet.insert(SETTING_HALO_TEXTUREID); specialSet.insert(SETTING_CLOUD_TEXTUREID); specialSet.insert(SETTING_MOON_TEXTUREID); specialSet.insert(SETTING_SUN_TEXTUREID); specialSet.insert(SETTING_CLOUD_SHADOW); // due to being part of skips } return specialSet; } template<> LLSettingsBase::stringset_t LLSettingsInjected::getSpecialKeys() const { static stringset_t specialSet; if (specialSet.empty()) { specialSet.insert(SETTING_TRANSPARENT_TEXTURE); specialSet.insert(SETTING_NORMAL_MAP); } return specialSet; } template<> void LLSettingsInjected::resetSpecial() { mNextSunTextureId.setNull(); mNextMoonTextureId.setNull(); mNextCloudTextureId.setNull(); mNextBloomTextureId.setNull(); mNextRainbowTextureId.setNull(); mNextHaloTextureId.setNull(); setBlendFactor(0.0f); } template<> void LLSettingsInjected::resetSpecial() { mNextNormalMapID.setNull(); mNextTransparentTextureID.setNull(); setBlendFactor(0.0f); } template<> void LLSettingsInjected::updateSpecial(const typename LLSettingsInjected::Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix) { bool is_texture = true; if (injection->mKeyName == SETTING_SUN_TEXTUREID) { mNextSunTextureId = injection->mValue.asUUID(); } else if (injection->mKeyName == SETTING_MOON_TEXTUREID) { mNextMoonTextureId = injection->mValue.asUUID(); } else if (injection->mKeyName == SETTING_CLOUD_TEXTUREID) { mNextCloudTextureId = injection->mValue.asUUID(); } else if (injection->mKeyName == SETTING_BLOOM_TEXTUREID) { mNextBloomTextureId = injection->mValue.asUUID(); } else if (injection->mKeyName == SETTING_RAINBOW_TEXTUREID) { mNextRainbowTextureId = injection->mValue.asUUID(); } else if (injection->mKeyName == SETTING_HALO_TEXTUREID) { mNextHaloTextureId = injection->mValue.asUUID(); } else if (injection->mKeyName == LLSettingsSky::SETTING_CLOUD_SHADOW) { // Special case due to being texture dependent and part of skips is_texture = false; if (!injection->mBlendIn) mix = 1.0 - mix; stringset_t dummy; F64 value = this->mSettings[injection->mKeyName].asReal(); if (this->getCloudNoiseTextureId().isNull()) { value = 0; // there was no texture so start from zero coverage } // Ideally we need to check for texture in injection, but // in this case user is setting value explicitly, potentially // with different transitions, don't ignore it F64 result = lerp(value, injection->mValue.asReal(), mix); injection->mLastValue = LLSD::Real(result); this->mSettings[injection->mKeyName] = injection->mLastValue; } // Unfortunately I don't have a per texture blend factor. We'll just pick the one that is furthest along. if (is_texture && getBlendFactor() < mix) { setBlendFactor(mix); } } template<> void LLSettingsInjected::updateSpecial(const typename LLSettingsInjected::Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix) { if (injection->mKeyName == SETTING_NORMAL_MAP) { mNextNormalMapID = injection->mValue.asUUID(); } else if (injection->mKeyName == SETTING_TRANSPARENT_TEXTURE) { mNextTransparentTextureID = injection->mValue.asUUID(); } // Unfortunately I don't have a per texture blend factor. We'll just pick the one that is furthest along. if (getBlendFactor() < mix) { setBlendFactor(mix); } } typedef LLSettingsInjected LLSettingsInjectedSky; typedef LLSettingsInjected LLSettingsInjectedWater; //===================================================================== class DayInjection : public LLEnvironment::DayInstance { friend class InjectedTransition; public: typedef std::shared_ptr ptr_t; typedef std::weak_ptr wptr_t; DayInjection(LLEnvironment::EnvSelection_t env); virtual ~DayInjection(); virtual bool applyTimeDelta(const LLSettingsBase::Seconds& delta) override; void setInjectedDay(const LLSettingsDay::ptr_t &pday, LLUUID experience_id, LLSettingsBase::Seconds transition); void setInjectedSky(const LLSettingsSky::ptr_t &psky, LLUUID experience_id, LLSettingsBase::Seconds transition); void setInjectedWater(const LLSettingsWater::ptr_t &pwater, LLUUID experience_id, LLSettingsBase::Seconds transition); void injectSkySettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition); void injectWaterSettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition); void clearInjections(LLUUID experience_id, LLSettingsBase::Seconds transition_time); virtual void animate() override; LLEnvironment::DayInstance::ptr_t getBaseDayInstance() const { return mBaseDayInstance; } void setBaseDayInstance(const LLEnvironment::DayInstance::ptr_t &baseday); S32 countExperiencesActive() const { return mActiveExperiences.size(); } bool isOverriddenSky() const { return !mSkyExperience.isNull(); } bool isOverriddenWater() const { return !mWaterExperience.isNull(); } bool hasInjections() const; void testExperiencesOnParcel(S32 parcel_id); private: static void testExperiencesOnParcelCoro(wptr_t that, S32 parcel_id); void animateSkyChange(LLSettingsSky::ptr_t psky, LLSettingsBase::Seconds transition); void animateWaterChange(LLSettingsWater::ptr_t pwater, LLSettingsBase::Seconds transition); void onEnvironmentChanged(LLEnvironment::EnvSelection_t env); void onParcelChange(); void checkExperience(); LLEnvironment::DayInstance::ptr_t mBaseDayInstance; LLSettingsInjectedSky::ptr_t mInjectedSky; LLSettingsInjectedWater::ptr_t mInjectedWater; std::set mActiveExperiences; LLUUID mDayExperience; LLUUID mSkyExperience; LLUUID mWaterExperience; LLEnvironment::connection_t mEnvChangeConnection; boost::signals2::connection mParcelChangeConnection; }; class InjectedTransition : public LLEnvironment::DayTransition { public: InjectedTransition(const DayInjection::ptr_t &injection, const LLSettingsSky::ptr_t &skystart, const LLSettingsWater::ptr_t &waterstart, DayInstance::ptr_t &end, LLSettingsDay::Seconds time): LLEnvironment::DayTransition(skystart, waterstart, end, time), mInjection(injection) { } virtual ~InjectedTransition() { }; virtual void animate() override; protected: DayInjection::ptr_t mInjection; }; } //========================================================================= const F64Seconds LLEnvironment::TRANSITION_INSTANT(0.0f); const F64Seconds LLEnvironment::TRANSITION_FAST(1.0f); const F64Seconds LLEnvironment::TRANSITION_DEFAULT(5.0f); const F64Seconds LLEnvironment::TRANSITION_SLOW(10.0f); const F64Seconds LLEnvironment::TRANSITION_ALTITUDE(5.0f); const LLUUID LLEnvironment::KNOWN_SKY_SUNRISE("01e41537-ff51-2f1f-8ef7-17e4df760bfb"); const LLUUID LLEnvironment::KNOWN_SKY_MIDDAY("c46226b4-0e43-5a56-9708-d27ca1df3292"); const LLUUID LLEnvironment::KNOWN_SKY_LEGACY_MIDDAY("cef49723-0292-af49-9b14-9598a616b8a3"); const LLUUID LLEnvironment::KNOWN_SKY_SUNSET("084e26cd-a900-28e8-08d0-64a9de5c15e2"); const LLUUID LLEnvironment::KNOWN_SKY_MIDNIGHT("8a01b97a-cb20-c1ea-ac63-f7ea84ad0090"); const S32 LLEnvironment::NO_TRACK(-1); const S32 LLEnvironment::NO_VERSION(-3); // For viewer sided change, like ENV_LOCAL. -3 since -1 and -2 are taken by parcel initial server/viewer version const S32 LLEnvironment::VERSION_CLEANUP(-4); // for cleanups const F32 LLEnvironment::SUN_DELTA_YAW(F_PI); // 180deg const U32 LLEnvironment::DayInstance::NO_ANIMATE_SKY(0x01); const U32 LLEnvironment::DayInstance::NO_ANIMATE_WATER(0x02); std::string env_selection_to_string(LLEnvironment::EnvSelection_t sel) { #define RTNENUM(E) case LLEnvironment::E: return #E switch (sel){ RTNENUM(ENV_EDIT); RTNENUM(ENV_LOCAL); RTNENUM(ENV_PUSH); RTNENUM(ENV_PARCEL); RTNENUM(ENV_REGION); RTNENUM(ENV_DEFAULT); RTNENUM(ENV_END); RTNENUM(ENV_CURRENT); RTNENUM(ENV_NONE); default: return llformat("Unknown(%d)", sel); } #undef RTNENUM } //------------------------------------------------------------------------- LLEnvironment::LLEnvironment(): mCloudScrollDelta(), mCloudScrollPaused(false), mSelectedSky(), mSelectedWater(), mSelectedDay(), mSelectedEnvironment(LLEnvironment::ENV_LOCAL), mCurrentTrack(1), mEditorCounter(0), mShowSunBeacon(false), mShowMoonBeacon(false) { } void LLEnvironment::initSingleton() { LLSettingsSky::ptr_t p_default_sky = LLSettingsVOSky::buildDefaultSky(); LLSettingsWater::ptr_t p_default_water = LLSettingsVOWater::buildDefaultWater(); mCurrentEnvironment = std::make_shared(ENV_DEFAULT); mCurrentEnvironment->setSky(p_default_sky); mCurrentEnvironment->setWater(p_default_water); mEnvironments[ENV_DEFAULT] = mCurrentEnvironment; requestRegion(); if (!mParcelCallbackConnection.connected()) { mParcelCallbackConnection = gAgent.addParcelChangedCallback([this]() { onParcelChange(); }); //TODO: This frequently results in one more request than we need. It isn't breaking, but should be nicer. // We need to know new env version to fix this, without it we can only do full re-request // Happens: on updates, on opening LLFloaterRegionInfo, on region crossing if info floater is open mRegionUpdateCallbackConnection = LLRegionInfoModel::instance().setUpdateCallback([this]() { requestRegion(); }); mRegionChangeCallbackConnection = gAgent.addRegionChangedCallback([this]() { onRegionChange(); }); mPositionCallbackConnection = gAgent.whenPositionChanged([this](const LLVector3 &localpos, const LLVector3d &) { onAgentPositionHasChanged(localpos); }); } if (!gGenericDispatcher.isHandlerPresent(MESSAGE_PUSHENVIRONMENT)) { gGenericDispatcher.addHandler(MESSAGE_PUSHENVIRONMENT, &environment_push_dispatch_handler); } LLEventPumps::instance().obtain(PUMP_EXPERIENCE).stopListening(LISTENER_NAME); LLEventPumps::instance().obtain(PUMP_EXPERIENCE).listen(LISTENER_NAME, [this](LLSD message) { listenExperiencePump(message); return false; }); } void LLEnvironment::cleanupSingleton() { if (mParcelCallbackConnection.connected()) { mParcelCallbackConnection.disconnect(); mRegionUpdateCallbackConnection.disconnect(); mRegionChangeCallbackConnection.disconnect(); mPositionCallbackConnection.disconnect(); } LLEventPumps::instance().obtain(PUMP_EXPERIENCE).stopListening(LISTENER_NAME); } LLEnvironment::~LLEnvironment() { cleanupSingleton(); } bool LLEnvironment::canEdit() const { return true; } LLSettingsSky::ptr_t LLEnvironment::getCurrentSky() const { LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); if (!psky && mCurrentEnvironment->getEnvironmentSelection() >= ENV_EDIT) { for (int idx = 0; idx < ENV_END; ++idx) { if (mEnvironments[idx]->getSky()) { psky = mEnvironments[idx]->getSky(); break; } } } return psky; } LLSettingsWater::ptr_t LLEnvironment::getCurrentWater() const { LLSettingsWater::ptr_t pwater = mCurrentEnvironment->getWater(); if (!pwater && mCurrentEnvironment->getEnvironmentSelection() >= ENV_EDIT) { for (int idx = 0; idx < ENV_END; ++idx) { if (mEnvironments[idx]->getWater()) { pwater = mEnvironments[idx]->getWater(); break; } } } return pwater; } void LayerConfigToDensityLayer(const LLSD& layerConfig, DensityLayer& layerOut) { layerOut.constant_term = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM].asReal(); layerOut.exp_scale = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_SCALE_FACTOR].asReal(); layerOut.exp_term = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_TERM].asReal(); layerOut.linear_term = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_LINEAR_TERM].asReal(); layerOut.width = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH].asReal(); } void LLEnvironment::getAtmosphericModelSettings(AtmosphericModelSettings& settingsOut, const LLSettingsSky::ptr_t &psky) { settingsOut.m_skyBottomRadius = psky->getSkyBottomRadius(); settingsOut.m_skyTopRadius = psky->getSkyTopRadius(); settingsOut.m_sunArcRadians = psky->getSunArcRadians(); settingsOut.m_mieAnisotropy = psky->getMieAnisotropy(); LLSD rayleigh = psky->getRayleighConfigs(); settingsOut.m_rayleighProfile.clear(); for (LLSD::array_iterator itf = rayleigh.beginArray(); itf != rayleigh.endArray(); ++itf) { DensityLayer layer; LLSD& layerConfig = (*itf); LayerConfigToDensityLayer(layerConfig, layer); settingsOut.m_rayleighProfile.push_back(layer); } LLSD mie = psky->getMieConfigs(); settingsOut.m_mieProfile.clear(); for (LLSD::array_iterator itf = mie.beginArray(); itf != mie.endArray(); ++itf) { DensityLayer layer; LLSD& layerConfig = (*itf); LayerConfigToDensityLayer(layerConfig, layer); settingsOut.m_mieProfile.push_back(layer); } settingsOut.m_mieAnisotropy = psky->getMieAnisotropy(); LLSD absorption = psky->getAbsorptionConfigs(); settingsOut.m_absorptionProfile.clear(); for (LLSD::array_iterator itf = absorption.beginArray(); itf != absorption.endArray(); ++itf) { DensityLayer layer; LLSD& layerConfig = (*itf); LayerConfigToDensityLayer(layerConfig, layer); settingsOut.m_absorptionProfile.push_back(layer); } } bool LLEnvironment::canAgentUpdateParcelEnvironment() const { LLParcel *parcel(LLViewerParcelMgr::instance().getAgentOrSelectedParcel()); return canAgentUpdateParcelEnvironment(parcel); } bool LLEnvironment::canAgentUpdateParcelEnvironment(LLParcel *parcel) const { if (!parcel) return false; if (!LLEnvironment::instance().isExtendedEnvironmentEnabled()) return false; if (gAgent.isGodlike()) return true; if (!parcel->getRegionAllowEnvironmentOverride()) return false; return LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_ALLOW_ENVIRONMENT); } bool LLEnvironment::canAgentUpdateRegionEnvironment() const { if (gAgent.isGodlike()) return true; return gAgent.canManageEstate(); } bool LLEnvironment::isExtendedEnvironmentEnabled() const { return !gAgent.getRegionCapability("ExtEnvironment").empty(); } bool LLEnvironment::isInventoryEnabled() const { return (!gAgent.getRegionCapability("UpdateSettingsAgentInventory").empty() && !gAgent.getRegionCapability("UpdateSettingsTaskInventory").empty()); } void LLEnvironment::onRegionChange() { // if (gAgent.getRegionCapability("ExperienceQuery").empty()) // { // // for now environmental experiences do not survive region crossings clearExperienceEnvironment(LLUUID::null, TRANSITION_DEFAULT); // } LLViewerRegion* cur_region = gAgent.getRegion(); if (!cur_region) { return; } if (!cur_region->capabilitiesReceived()) { cur_region->setCapabilitiesReceivedCallback([](const LLUUID ®ion_id, LLViewerRegion* regionp) { LLEnvironment::instance().requestRegion(); }); return; } requestRegion(); } void LLEnvironment::onParcelChange() { S32 parcel_id(INVALID_PARCEL_ID); LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); if (parcel) { parcel_id = parcel->getLocalID(); } requestParcel(parcel_id); } //------------------------------------------------------------------------- F32 LLEnvironment::getCamHeight() const { return (mCurrentEnvironment->getSky()->getDomeOffset() * mCurrentEnvironment->getSky()->getDomeRadius()); } F32 LLEnvironment::getWaterHeight() const { LLViewerRegion* cur_region = gAgent.getRegion(); return cur_region ? cur_region->getWaterHeight() : DEFAULT_WATER_HEIGHT; } bool LLEnvironment::getIsSunUp() const { if (!mCurrentEnvironment || !mCurrentEnvironment->getSky()) return false; return mCurrentEnvironment->getSky()->getIsSunUp(); } bool LLEnvironment::getIsMoonUp() const { if (!mCurrentEnvironment || !mCurrentEnvironment->getSky()) return false; return mCurrentEnvironment->getSky()->getIsMoonUp(); } //------------------------------------------------------------------------- void LLEnvironment::setSelectedEnvironment(LLEnvironment::EnvSelection_t env, LLSettingsBase::Seconds transition, bool forced) { mSelectedEnvironment = env; updateEnvironment(transition, forced); LL_DEBUGS("ENVIRONMENT") << "Setting environment " << env_selection_to_string(env) << " with transition: " << transition << LL_ENDL; } bool LLEnvironment::hasEnvironment(LLEnvironment::EnvSelection_t env) { if ((env < ENV_EDIT) || (env >= ENV_DEFAULT) || (!mEnvironments[env])) { return false; } return true; } LLEnvironment::DayInstance::ptr_t LLEnvironment::getEnvironmentInstance(LLEnvironment::EnvSelection_t env, bool create /*= false*/) { DayInstance::ptr_t environment = mEnvironments[env]; if (create) { if (environment) environment = environment->clone(); else { if (env == ENV_PUSH) environment = std::make_shared(env); else environment = std::make_shared(env); } mEnvironments[env] = environment; } return environment; } void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, const LLSettingsDay::ptr_t &pday, LLSettingsDay::Seconds daylength, LLSettingsDay::Seconds dayoffset, S32 env_version) { if ((env < ENV_EDIT) || (env >= ENV_DEFAULT)) { LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; return; } logEnvironment(env, pday, env_version); DayInstance::ptr_t environment = getEnvironmentInstance(env, true); environment->clear(); environment->setDay(pday, daylength, dayoffset); environment->setSkyTrack(mCurrentTrack); environment->animate(); if (!mSignalEnvChanged.empty()) mSignalEnvChanged(env, env_version); } void LLEnvironment::setCurrentEnvironmentSelection(LLEnvironment::EnvSelection_t env) { mCurrentEnvironment->setEnvironmentSelection(env); } void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, LLEnvironment::fixedEnvironment_t fixed, S32 env_version) { if ((env < ENV_EDIT) || (env >= ENV_DEFAULT)) { LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; return; } bool reset_probes = false; DayInstance::ptr_t environment = getEnvironmentInstance(env, true); if (fixed.first) { logEnvironment(env, fixed.first, env_version); reset_probes = environment->setSky(fixed.first); environment->setFlags(DayInstance::NO_ANIMATE_SKY); } else if (!environment->getSky()) { if (mCurrentEnvironment->getEnvironmentSelection() != ENV_NONE) { // Note: This looks suspicious. Shouldn't we assign whole day if mCurrentEnvironment has whole day? // and then add water/sky on top // This looks like it will result in sky using single keyframe instead of whole day if day is present // when setting static water without static sky reset_probes = environment->setSky(mCurrentEnvironment->getSky()); environment->setFlags(DayInstance::NO_ANIMATE_SKY); } else { // Environment is not properly initialized yet, but we should have environment by this point DayInstance::ptr_t substitute = getEnvironmentInstance(ENV_PARCEL, true); if (!substitute || !substitute->getSky()) { substitute = getEnvironmentInstance(ENV_REGION, true); } if (!substitute || !substitute->getSky()) { substitute = getEnvironmentInstance(ENV_DEFAULT, true); } if (substitute && substitute->getSky()) { reset_probes = environment->setSky(substitute->getSky()); environment->setFlags(DayInstance::NO_ANIMATE_SKY); } else { LL_WARNS("ENVIRONMENT") << "Failed to assign substitute water/sky, environment is not properly initialized" << LL_ENDL; } } } if (fixed.second) { logEnvironment(env, fixed.second, env_version); environment->setWater(fixed.second); environment->setFlags(DayInstance::NO_ANIMATE_WATER); } else if (!environment->getWater()) { if (mCurrentEnvironment->getEnvironmentSelection() != ENV_NONE) { // Note: This looks suspicious. Shouldn't we assign whole day if mCurrentEnvironment has whole day? // and then add water/sky on top // This looks like it will result in water using single keyframe instead of whole day if day is present // when setting static sky without static water environment->setWater(mCurrentEnvironment->getWater()); environment->setFlags(DayInstance::NO_ANIMATE_WATER); } else { // Environment is not properly initialized yet, but we should have environment by this point DayInstance::ptr_t substitute = getEnvironmentInstance(ENV_PARCEL, true); if (!substitute || !substitute->getWater()) { substitute = getEnvironmentInstance(ENV_REGION, true); } if (!substitute || !substitute->getWater()) { substitute = getEnvironmentInstance(ENV_DEFAULT, true); } if (substitute && substitute->getWater()) { environment->setWater(substitute->getWater()); environment->setFlags(DayInstance::NO_ANIMATE_WATER); } else { LL_WARNS("ENVIRONMENT") << "Failed to assign substitute water/sky, environment is not properly initialized" << LL_ENDL; } } } if (reset_probes) { // the sky changed in a way that merits a reset of reflection probes gPipeline.mReflectionMapManager.reset(); } if (!mSignalEnvChanged.empty()) mSignalEnvChanged(env, env_version); } void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, const LLSettingsBase::ptr_t &settings, S32 env_version) { DayInstance::ptr_t environment = getEnvironmentInstance(env); if (env == ENV_DEFAULT) { LL_WARNS("ENVIRONMENT") << "Attempt to set default environment. Not allowed." << LL_ENDL; return; } if (!settings) { clearEnvironment(env); return; } if (settings->getSettingsType() == "daycycle") { LLSettingsDay::Seconds daylength(LLSettingsDay::DEFAULT_DAYLENGTH); LLSettingsDay::Seconds dayoffset(LLSettingsDay::DEFAULT_DAYOFFSET); if (environment) { daylength = environment->getDayLength(); dayoffset = environment->getDayOffset(); } setEnvironment(env, std::static_pointer_cast(settings), daylength, dayoffset); } else if (settings->getSettingsType() == "sky") { fixedEnvironment_t fixedenv(std::static_pointer_cast(settings), LLSettingsWater::ptr_t()); setEnvironment(env, fixedenv); } else if (settings->getSettingsType() == "water") { fixedEnvironment_t fixedenv(LLSettingsSky::ptr_t(), std::static_pointer_cast(settings)); setEnvironment(env, fixedenv); } } void LLEnvironment::setEnvironment(EnvSelection_t env, const LLUUID &assetId, S32 env_version) { setEnvironment(env, assetId, TRANSITION_DEFAULT, env_version); } void LLEnvironment::setEnvironment(EnvSelection_t env, const LLUUID &assetId, LLSettingsBase::Seconds transition, S32 env_version) { LLSettingsVOBase::getSettingsAsset(assetId, [this, env, env_version, transition](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) { onSetEnvAssetLoaded(env, asset_id, settings, transition, status, env_version); }); } void LLEnvironment::onSetEnvAssetLoaded(EnvSelection_t env, LLUUID asset_id, LLSettingsBase::ptr_t settings, LLSettingsBase::Seconds transition, S32 status, S32 env_version) { if (!settings || status) { LLSD args; args["NAME"] = asset_id.asString(); LLNotificationsUtil::add("FailedToFindSettings", args); LL_DEBUGS("ENVIRONMENT") << "Failed to find settings for " << env_selection_to_string(env) << ", asset_id: " << asset_id << LL_ENDL; return; } LL_DEBUGS("ENVIRONMENT") << "Loaded asset: " << asset_id << LL_ENDL; setEnvironment(env, settings); updateEnvironment(transition); } void LLEnvironment::clearEnvironment(LLEnvironment::EnvSelection_t env) { if ((env < ENV_EDIT) || (env >= ENV_DEFAULT)) { LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection." << LL_ENDL; return; } LL_DEBUGS("ENVIRONMENT") << "Cleaning environment " << env_selection_to_string(env) << LL_ENDL; mEnvironments[env].reset(); if (!mSignalEnvChanged.empty()) mSignalEnvChanged(env, VERSION_CLEANUP); } void LLEnvironment::logEnvironment(EnvSelection_t env, const LLSettingsBase::ptr_t &settings, S32 env_version) { LL_DEBUGS("ENVIRONMENT") << "Setting Day environment " << env_selection_to_string(env) << " with version(update type): " << env_version << LL_NEWLINE; // code between LL_DEBUGS and LL_ENDL won't execute unless log is enabled if (settings) { LLUUID asset_id = settings->getAssetId(); if (asset_id.notNull()) { LL_CONT << "Asset id: " << asset_id << LL_NEWLINE; } LLUUID id = settings->getId(); // Not in use? if (id.notNull()) { LL_CONT << "Settings id: " << id << LL_NEWLINE; } LL_CONT << "Name: " << settings->getName() << LL_NEWLINE << "Type: " << settings->getSettingsType() << LL_NEWLINE << "Flags: " << settings->getFlags(); // Not in use? } else { LL_CONT << "Empty settings!"; } LL_CONT << LL_ENDL; } LLSettingsDay::ptr_t LLEnvironment::getEnvironmentDay(LLEnvironment::EnvSelection_t env) { if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) { LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; return LLSettingsDay::ptr_t(); } DayInstance::ptr_t environment = getEnvironmentInstance(env); if (environment) return environment->getDayCycle(); return LLSettingsDay::ptr_t(); } LLSettingsDay::Seconds LLEnvironment::getEnvironmentDayLength(EnvSelection_t env) { if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) { LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; return LLSettingsDay::Seconds(0); } DayInstance::ptr_t environment = getEnvironmentInstance(env); if (environment) return environment->getDayLength(); return LLSettingsDay::Seconds(0); } LLSettingsDay::Seconds LLEnvironment::getEnvironmentDayOffset(EnvSelection_t env) { if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) { LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; return LLSettingsDay::Seconds(0); } DayInstance::ptr_t environment = getEnvironmentInstance(env); if (environment) return environment->getDayOffset(); return LLSettingsDay::Seconds(0); } LLEnvironment::fixedEnvironment_t LLEnvironment::getEnvironmentFixed(LLEnvironment::EnvSelection_t env, bool resolve) { if ((env == ENV_CURRENT) || resolve) { fixedEnvironment_t fixed; for (S32 idx = ((resolve) ? env : mSelectedEnvironment); idx < ENV_END; ++idx) { if (fixed.first && fixed.second) break; if (idx == ENV_EDIT) continue; // skip the edit environment. DayInstance::ptr_t environment = getEnvironmentInstance(static_cast(idx)); if (environment) { if (!fixed.first) fixed.first = environment->getSky(); if (!fixed.second) fixed.second = environment->getWater(); } } if (!fixed.first || !fixed.second) LL_WARNS("ENVIRONMENT") << "Can not construct complete fixed environment. Missing Sky and/or Water." << LL_ENDL; return fixed; } if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) { LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; return fixedEnvironment_t(); } DayInstance::ptr_t environment = getEnvironmentInstance(env); if (environment) return fixedEnvironment_t(environment->getSky(), environment->getWater()); return fixedEnvironment_t(); } LLEnvironment::DayInstance::ptr_t LLEnvironment::getSelectedEnvironmentInstance() { for (S32 idx = mSelectedEnvironment; idx < ENV_DEFAULT; ++idx) { if (mEnvironments[idx]) return mEnvironments[idx]; } return mEnvironments[ENV_DEFAULT]; } LLEnvironment::DayInstance::ptr_t LLEnvironment::getSharedEnvironmentInstance() { for (S32 idx = ENV_PARCEL; idx < ENV_DEFAULT; ++idx) { if (mEnvironments[idx]) return mEnvironments[idx]; } return mEnvironments[ENV_DEFAULT]; } void LLEnvironment::updateEnvironment(LLSettingsBase::Seconds transition, bool forced) { LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; DayInstance::ptr_t pinstance = getSelectedEnvironmentInstance(); if ((mCurrentEnvironment != pinstance) || forced) { if (transition != TRANSITION_INSTANT) { DayInstance::ptr_t trans = std::make_shared( mCurrentEnvironment->getSky(), mCurrentEnvironment->getWater(), pinstance, transition); trans->animate(); mCurrentEnvironment = trans; } else { mCurrentEnvironment = pinstance; } updateSettingsUniforms(); } } LLVector4 LLEnvironment::toCFR(const LLVector3 vec) const { LLVector4 vec_cfr(vec.mV[1], vec.mV[0], vec.mV[2], 0.0f); return vec_cfr; } LLVector4 LLEnvironment::toLightNorm(const LLVector3 vec) const { LLVector4 vec_ogl(vec.mV[1], vec.mV[2], vec.mV[0], 0.0f); return vec_ogl; } LLVector3 LLEnvironment::getLightDirection() const { LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); if (!psky) { return LLVector3(0, 0, 1); } return psky->getLightDirection(); } LLVector3 LLEnvironment::getSunDirection() const { LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); if (!psky) { return LLVector3(0, 0, 1); } return psky->getSunDirection(); } LLVector3 LLEnvironment::getMoonDirection() const { LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); if (!psky) { return LLVector3(0, 0, -1); } return psky->getMoonDirection(); } LLVector4 LLEnvironment::getLightDirectionCFR() const { LLVector3 light_direction = getLightDirection(); LLVector4 light_direction_cfr = toCFR(light_direction); return light_direction_cfr; } LLVector4 LLEnvironment::getSunDirectionCFR() const { LLVector3 light_direction = getSunDirection(); LLVector4 light_direction_cfr = toCFR(light_direction); return light_direction_cfr; } LLVector4 LLEnvironment::getMoonDirectionCFR() const { LLVector3 light_direction = getMoonDirection(); LLVector4 light_direction_cfr = toCFR(light_direction); return light_direction_cfr; } LLVector4 LLEnvironment::getClampedLightNorm() const { LLVector3 light_direction = getLightDirection(); if (light_direction.mV[2] < -0.1f) { light_direction.mV[2] = -0.1f; } return toLightNorm(light_direction); } LLVector4 LLEnvironment::getClampedSunNorm() const { LLVector3 light_direction = getSunDirection(); if (light_direction.mV[2] < -0.1f) { light_direction.mV[2] = -0.1f; } return toLightNorm(light_direction); } LLVector4 LLEnvironment::getClampedMoonNorm() const { LLVector3 light_direction = getMoonDirection(); if (light_direction.mV[2] < -0.1f) { light_direction.mV[2] = -0.1f; } return toLightNorm(light_direction); } LLVector4 LLEnvironment::getRotatedLightNorm() const { LLVector3 light_direction = getLightDirection(); light_direction *= LLQuaternion(-mLastCamYaw, LLVector3(0.f, 1.f, 0.f)); return toLightNorm(light_direction); } extern BOOL gCubeSnapshot; //------------------------------------------------------------------------- void LLEnvironment::update(const LLViewerCamera * cam) { LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; //LL_RECORD_BLOCK_TIME(FTM_ENVIRONMENT_UPDATE); //F32Seconds now(LLDate::now().secondsSinceEpoch()); if (!gCubeSnapshot) { static LLFrameTimer timer; F32Seconds delta(timer.getElapsedTimeAndResetF32()); { DayInstance::ptr_t keeper = mCurrentEnvironment; // make sure the current environment does not go away until applyTimeDelta is done. mCurrentEnvironment->applyTimeDelta(delta); } // update clouds, sun, and general updateCloudScroll(); // cache this for use in rotating the rotated light vec for shader param updates later... mLastCamYaw = cam->getYaw() + SUN_DELTA_YAW; } updateSettingsUniforms(); // *TODO: potential optimization - this block may only need to be // executed some of the time. For example for water shaders only. { LLViewerShaderMgr::shader_iter shaders_iter, end_shaders; end_shaders = LLViewerShaderMgr::instance()->endShaders(); for (shaders_iter = LLViewerShaderMgr::instance()->beginShaders(); shaders_iter != end_shaders; ++shaders_iter) { if ((shaders_iter->mProgramObject != 0) && (gPipeline.canUseWindLightShaders() || shaders_iter->mShaderGroup == LLGLSLShader::SG_WATER)) { shaders_iter->mUniformsDirty = TRUE; } } } } void LLEnvironment::updateCloudScroll() { // This is a function of the environment rather than the sky, since it should // persist through sky transitions. static LLTimer s_cloud_timer; F64 delta_t = s_cloud_timer.getElapsedTimeAndResetF64(); if (mCurrentEnvironment->getSky() && !mCloudScrollPaused) { LLVector2 rate = mCurrentEnvironment->getSky()->getCloudScrollRate(); if (rate.isExactlyZero()) { mCloudScrollDelta.setZero(); } else { LLVector2 cloud_delta = static_cast(delta_t) * (mCurrentEnvironment->getSky()->getCloudScrollRate()) / 100.0; mCloudScrollDelta += cloud_delta; } } } // static void LLEnvironment::updateGLVariablesForSettings(LLShaderUniforms* uniforms, const LLSettingsBase::ptr_t &psetting) { LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER; for (int i = 0; i < LLGLSLShader::SG_COUNT; ++i) { uniforms[i].clear(); } LLShaderUniforms* shader = &uniforms[LLGLSLShader::SG_ANY]; //_WARNS("RIDER") << "----------------------------------------------------------------" << LL_ENDL; LLSettingsBase::parammapping_t params = psetting->getParameterMap(); for (auto &it: params) { LLSD value; // legacy first since it contains ambient color and we prioritize value from legacy, see getAmbientColor() if (psetting->mSettings.has(LLSettingsSky::SETTING_LEGACY_HAZE) && psetting->mSettings[LLSettingsSky::SETTING_LEGACY_HAZE].has(it.first)) { value = psetting->mSettings[LLSettingsSky::SETTING_LEGACY_HAZE][it.first]; } else if (psetting->mSettings.has(it.first)) { value = psetting->mSettings[it.first]; } else { // We need to reset shaders, use defaults value = it.second.getDefaultValue(); } LLSD::Type setting_type = value.type(); stop_glerror(); switch (setting_type) { case LLSD::TypeInteger: shader->uniform1i(it.second.getShaderKey(), value.asInteger()); //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL; break; case LLSD::TypeReal: shader->uniform1f(it.second.getShaderKey(), value.asReal()); //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL; break; case LLSD::TypeBoolean: shader->uniform1i(it.second.getShaderKey(), value.asBoolean() ? 1 : 0); //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL; break; case LLSD::TypeArray: { LLVector4 vect4(value); if (gCubeSnapshot && !gPipeline.mReflectionMapManager.isRadiancePass()) { // maximize and remove tinting if this is an irradiance map render pass and the parameter feeds into the sky background color auto max_vec = [](LLVector4 col) { LLColor3 color(col); F32 h, s, l; color.calcHSL(&h, &s, &l); col.mV[0] = col.mV[1] = col.mV[2] = l; return col; }; switch (it.second.getShaderKey()) { case LLShaderMgr::BLUE_HORIZON: case LLShaderMgr::BLUE_DENSITY: vect4 = max_vec(vect4); break; } } //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << vect4 << LL_ENDL; shader->uniform3fv(it.second.getShaderKey(), LLVector3(vect4.mV) ); break; } // case LLSD::TypeMap: // case LLSD::TypeString: // case LLSD::TypeUUID: // case LLSD::TypeURI: // case LLSD::TypeBinary: // case LLSD::TypeDate: default: break; } } //_WARNS("RIDER") << "----------------------------------------------------------------" << LL_ENDL; psetting->applySpecial(uniforms); } void LLEnvironment::updateShaderUniforms(LLGLSLShader* shader) { LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER; // apply uniforms that should be applied to all shaders mSkyUniforms[LLGLSLShader::SG_ANY].apply(shader); mWaterUniforms[LLGLSLShader::SG_ANY].apply(shader); // apply uniforms specific to the given shader's shader group auto group = shader->mShaderGroup; mSkyUniforms[group].apply(shader); mWaterUniforms[group].apply(shader); } void LLEnvironment::updateSettingsUniforms() { if (mCurrentEnvironment->getWater()) { updateGLVariablesForSettings(mWaterUniforms, mCurrentEnvironment->getWater()); } else { LL_WARNS("ENVIRONMENT") << "Failed to update GL variable for water settings, environment is not properly set" << LL_ENDL; } if (mCurrentEnvironment->getSky()) { updateGLVariablesForSettings(mSkyUniforms, mCurrentEnvironment->getSky()); } else { LL_WARNS("ENVIRONMENT") << "Failed to update GL variable for sky settings, environment is not properly set" << LL_ENDL; } } void LLEnvironment::recordEnvironment(S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envinfo, LLSettingsBase::Seconds transition) { if (!gAgent.getRegion()) { return; } // mRegionId id can be null, no specification as to why and if it's valid so check valid ids only if (gAgent.getRegion()->getRegionID() != envinfo->mRegionId && envinfo->mRegionId.notNull()) { LL_INFOS("ENVIRONMENT") << "Requested environmend region id: " << envinfo->mRegionId << " agent is on: " << gAgent.getRegion()->getRegionID() << LL_ENDL; return; } if (envinfo->mParcelId == INVALID_PARCEL_ID) { // the returned info applies to an entire region. if (!envinfo->mDayCycle) { clearEnvironment(ENV_PARCEL); setEnvironment(ENV_REGION, LLSettingsDay::GetDefaultAssetId(), TRANSITION_DEFAULT, envinfo->mEnvVersion); updateEnvironment(); } else if (envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_WATER) || envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_GROUND_LEVEL)) { LL_WARNS("ENVIRONMENT") << "Invalid day cycle for region" << LL_ENDL; clearEnvironment(ENV_PARCEL); setEnvironment(ENV_REGION, LLSettingsDay::GetDefaultAssetId(), TRANSITION_DEFAULT, envinfo->mEnvVersion); updateEnvironment(); } else { mTrackAltitudes = envinfo->mAltitudes; // update track selection based on new altitudes mCurrentTrack = calculateSkyTrackForAltitude(gAgent.getPositionAgent().mV[VZ]); setEnvironment(ENV_REGION, envinfo->mDayCycle, envinfo->mDayLength, envinfo->mDayOffset, envinfo->mEnvVersion); } LL_DEBUGS("ENVIRONMENT") << "Altitudes set to {" << mTrackAltitudes[0] << ", "<< mTrackAltitudes[1] << ", " << mTrackAltitudes[2] << ", " << mTrackAltitudes[3] << LL_ENDL; } else { LLParcel *parcel = LLViewerParcelMgr::instance().getAgentParcel(); LL_DEBUGS("ENVIRONMENT") << "Have parcel environment #" << envinfo->mParcelId << LL_ENDL; if (parcel && (parcel->getLocalID() != parcel_id)) { LL_DEBUGS("ENVIRONMENT") << "Requested parcel #" << parcel_id << " agent is on " << parcel->getLocalID() << LL_ENDL; return; } if (!envinfo->mDayCycle) { LL_DEBUGS("ENVIRONMENT") << "Clearing environment on parcel #" << parcel_id << LL_ENDL; clearEnvironment(ENV_PARCEL); } else if (envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_WATER) || envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_GROUND_LEVEL)) { LL_WARNS("ENVIRONMENT") << "Invalid day cycle for parcel #" << parcel_id << LL_ENDL; clearEnvironment(ENV_PARCEL); } else { setEnvironment(ENV_PARCEL, envinfo->mDayCycle, envinfo->mDayLength, envinfo->mDayOffset, envinfo->mEnvVersion); } } updateEnvironment(transition); } void LLEnvironment::adjustRegionOffset(F32 adjust) { if (isExtendedEnvironmentEnabled()) { LL_WARNS("ENVIRONMENT") << "Attempt to adjust region offset on EEP region. Legacy regions only." << LL_ENDL; } if (mEnvironments[ENV_REGION]) { F32 day_length = mEnvironments[ENV_REGION]->getDayLength(); F32 day_offset = mEnvironments[ENV_REGION]->getDayOffset(); F32 day_adjustment = adjust * day_length; day_offset += day_adjustment; if (day_offset < 0.0f) day_offset = day_length + day_offset; mEnvironments[ENV_REGION]->setDayOffset(LLSettingsBase::Seconds(day_offset)); } } //========================================================================= void LLEnvironment::requestRegion(environment_apply_fn cb) { requestParcel(INVALID_PARCEL_ID, cb); } void LLEnvironment::updateRegion(const LLSettingsDay::ptr_t &pday, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) { updateParcel(INVALID_PARCEL_ID, pday, day_length, day_offset, altitudes, cb); } void LLEnvironment::updateRegion(const LLUUID &asset_id, std::string display_name, S32 track_num, S32 day_length, S32 day_offset, U32 flags, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) { if (!isExtendedEnvironmentEnabled()) { LL_WARNS("ENVIRONMENT") << "attempt to apply asset id to region not supporting it." << LL_ENDL; LLNotificationsUtil::add("NoEnvironmentSettings"); return; } updateParcel(INVALID_PARCEL_ID, asset_id, display_name, track_num, day_length, day_offset, flags, altitudes, cb); } void LLEnvironment::updateRegion(const LLSettingsSky::ptr_t &psky, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) { updateParcel(INVALID_PARCEL_ID, psky, day_length, day_offset, altitudes, cb); } void LLEnvironment::updateRegion(const LLSettingsWater::ptr_t &pwater, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) { updateParcel(INVALID_PARCEL_ID, pwater, day_length, day_offset, altitudes, cb); } void LLEnvironment::resetRegion(environment_apply_fn cb) { resetParcel(INVALID_PARCEL_ID, cb); } void LLEnvironment::requestParcel(S32 parcel_id, environment_apply_fn cb) { if (!isExtendedEnvironmentEnabled()) { /*TODO: When EEP is live on the entire grid, this can go away. */ if (parcel_id == INVALID_PARCEL_ID) { if (!cb) { LLSettingsBase::Seconds transition = LLViewerParcelMgr::getInstance()->getTeleportInProgress() ? TRANSITION_FAST : TRANSITION_DEFAULT; cb = [this, transition](S32 pid, EnvironmentInfo::ptr_t envinfo) { clearEnvironment(ENV_PARCEL); recordEnvironment(pid, envinfo, transition); }; } LLEnvironmentRequest::initiate(cb); } else if (cb) cb(parcel_id, EnvironmentInfo::ptr_t()); return; } if (!cb) { LLSettingsBase::Seconds transition = LLViewerParcelMgr::getInstance()->getTeleportInProgress() ? TRANSITION_FAST : TRANSITION_DEFAULT; cb = [this, transition](S32 pid, EnvironmentInfo::ptr_t envinfo) { recordEnvironment(pid, envinfo, transition); }; } std::string coroname = LLCoros::instance().launch("LLEnvironment::coroRequestEnvironment", [this, parcel_id, cb]() { coroRequestEnvironment(parcel_id, cb); }); } void LLEnvironment::updateParcel(S32 parcel_id, const LLUUID &asset_id, std::string display_name, S32 track_num, S32 day_length, S32 day_offset, U32 flags, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) { UpdateInfo::ptr_t updates(std::make_shared(asset_id, display_name, day_length, day_offset, altitudes, flags)); std::string coroname = LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment", [this, parcel_id, track_num, updates, cb]() { coroUpdateEnvironment(parcel_id, track_num, updates, cb); }); } void LLEnvironment::onUpdateParcelAssetLoaded(LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, S32 parcel_id, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes) { if (status) { LL_WARNS("ENVIRONMENT") << "Unable to get settings asset with id " << asset_id << "!" << LL_ENDL; LLNotificationsUtil::add("FailedToLoadSettingsApply"); return; } LLSettingsDay::ptr_t pday; if (settings->getSettingsType() == "daycycle") pday = std::static_pointer_cast(settings); else { pday = createDayCycleFromEnvironment( (parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, settings); } if (!pday) { LL_WARNS("ENVIRONMENT") << "Unable to construct day around " << asset_id << "!" << LL_ENDL; LLNotificationsUtil::add("FailedToBuildSettingsDay"); return; } updateParcel(parcel_id, pday, day_length, day_offset, altitudes); } void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsSky::ptr_t &psky, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) { LLSettingsDay::ptr_t pday = createDayCycleFromEnvironment((parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, psky); pday->setFlag(psky->getFlags()); updateParcel(parcel_id, pday, day_length, day_offset, altitudes, cb); } void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsWater::ptr_t &pwater, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) { LLSettingsDay::ptr_t pday = createDayCycleFromEnvironment((parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, pwater); pday->setFlag(pwater->getFlags()); updateParcel(parcel_id, pday, day_length, day_offset, altitudes, cb); } void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsDay::ptr_t &pday, S32 track_num, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) { UpdateInfo::ptr_t updates(std::make_shared(pday, day_length, day_offset, altitudes)); std::string coroname = LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment", [this, parcel_id, track_num, updates, cb]() { coroUpdateEnvironment(parcel_id, track_num, updates, cb); }); } void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsDay::ptr_t &pday, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) { updateParcel(parcel_id, pday, NO_TRACK, day_length, day_offset, altitudes, cb); } void LLEnvironment::resetParcel(S32 parcel_id, environment_apply_fn cb) { std::string coroname = LLCoros::instance().launch("LLEnvironment::coroResetEnvironment", [this, parcel_id, cb]() { coroResetEnvironment(parcel_id, NO_TRACK, cb); }); } void LLEnvironment::coroRequestEnvironment(S32 parcel_id, LLEnvironment::environment_apply_fn apply) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); std::string url = gAgent.getRegionCapability("ExtEnvironment"); if (url.empty()) return; LL_DEBUGS("ENVIRONMENT") << "Requesting for parcel_id=" << parcel_id << LL_ENDL; if (parcel_id != INVALID_PARCEL_ID) { std::stringstream query; query << "?parcelid=" << parcel_id; url += query.str(); } LLSD result = httpAdapter->getAndSuspend(httpRequest, url); // results that come back may contain the new settings LLSD httpResults = result["http_result"]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) { LL_WARNS("ENVIRONMENT") << "Couldn't retrieve environment settings for " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL; } else if (LLApp::isExiting()) { return; } else { LLSD environment = result[KEY_ENVIRONMENT]; if (environment.isDefined() && apply) { EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment); apply(parcel_id, envinfo); } } } void LLEnvironment::coroUpdateEnvironment(S32 parcel_id, S32 track_no, UpdateInfo::ptr_t updates, environment_apply_fn apply) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); std::string url = gAgent.getRegionCapability("ExtEnvironment"); if (url.empty()) return; LLSD body(LLSD::emptyMap()); body[KEY_ENVIRONMENT] = LLSD::emptyMap(); if (track_no == NO_TRACK) { // day length and offset are only applicable if we are addressing the entire day cycle. if (updates->mDayLength > 0) body[KEY_ENVIRONMENT][KEY_DAYLENGTH] = updates->mDayLength; if (updates->mDayOffset > 0) body[KEY_ENVIRONMENT][KEY_DAYOFFSET] = updates->mDayOffset; if ((parcel_id == INVALID_PARCEL_ID) && (updates->mAltitudes.size() == 3)) { // only test for altitude changes if we are changing the region. body[KEY_ENVIRONMENT][KEY_TRACKALTS] = LLSD::emptyArray(); for (S32 i = 0; i < 3; ++i) { body[KEY_ENVIRONMENT][KEY_TRACKALTS][i] = updates->mAltitudes[i]; } } } if (updates->mDayp) body[KEY_ENVIRONMENT][KEY_DAYCYCLE] = updates->mDayp->getSettings(); else if (!updates->mSettingsAsset.isNull()) { body[KEY_ENVIRONMENT][KEY_DAYASSET] = updates->mSettingsAsset; if (!updates->mDayName.empty()) body[KEY_ENVIRONMENT][KEY_DAYNAME] = updates->mDayName; } body[KEY_ENVIRONMENT][KEY_FLAGS] = LLSD::Integer(updates->mFlags); //_WARNS("ENVIRONMENT") << "Body = " << body << LL_ENDL; if ((parcel_id != INVALID_PARCEL_ID) || (track_no != NO_TRACK)) { std::stringstream query; query << "?"; if (parcel_id != INVALID_PARCEL_ID) { query << "parcelid=" << parcel_id; if (track_no != NO_TRACK) query << "&"; } if (track_no != NO_TRACK) { query << "trackno=" << track_no; } url += query.str(); } LLSD result = httpAdapter->putAndSuspend(httpRequest, url, body); // results that come back may contain the new settings LLSD notify; LLSD httpResults = result["http_result"]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if ((!status) || !result["success"].asBoolean()) { LL_WARNS("ENVIRONMENT") << "Couldn't update Windlight settings for " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL; notify = LLSD::emptyMap(); std::string reason = result["message"].asString(); if (reason.empty()) { notify["FAIL_REASON"] = status.toString(); } else { notify["FAIL_REASON"] = reason; } } else if (LLApp::isExiting()) { return; } else { LLSD environment = result[KEY_ENVIRONMENT]; if (environment.isDefined() && apply) { EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment); apply(parcel_id, envinfo); } } if (!notify.isUndefined()) { LLNotificationsUtil::add("WLRegionApplyFail", notify); //LLEnvManagerNew::instance().onRegionSettingsApplyResponse(false); } } void LLEnvironment::coroResetEnvironment(S32 parcel_id, S32 track_no, environment_apply_fn apply) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); std::string url = gAgent.getRegionCapability("ExtEnvironment"); if (url.empty()) return; if ((parcel_id != INVALID_PARCEL_ID) || (track_no != NO_TRACK)) { std::stringstream query; query << "?"; if (parcel_id != INVALID_PARCEL_ID) { query << "parcelid=" << parcel_id; if (track_no != NO_TRACK) query << "&"; } if (track_no != NO_TRACK) { query << "trackno=" << track_no; } url += query.str(); } LLSD result = httpAdapter->deleteAndSuspend(httpRequest, url); // results that come back may contain the new settings LLSD notify; LLSD httpResults = result["http_result"]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if ((!status) || !result["success"].asBoolean()) { LL_WARNS("ENVIRONMENT") << "Couldn't reset Windlight settings in " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL; notify = LLSD::emptyMap(); std::string reason = result["message"].asString(); if (reason.empty()) { notify["FAIL_REASON"] = status.toString(); } else { notify["FAIL_REASON"] = reason; } } else if (LLApp::isExiting()) { return; } else { LLSD environment = result[KEY_ENVIRONMENT]; if (environment.isDefined() && apply) { EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment); apply(parcel_id, envinfo); } } if (!notify.isUndefined()) { LLNotificationsUtil::add("WLRegionApplyFail", notify); //LLEnvManagerNew::instance().onRegionSettingsApplyResponse(false); } } //========================================================================= LLEnvironment::EnvironmentInfo::EnvironmentInfo(): mParcelId(INVALID_PARCEL_ID), mRegionId(), mDayLength(0), mDayOffset(0), mDayHash(0), mDayCycle(), mAltitudes({ { 0.0, 0.0, 0.0, 0.0 } }), mIsDefault(false), mIsLegacy(false), mDayCycleName(), mNameList(), mEnvVersion(INVALID_PARCEL_ENVIRONMENT_VERSION) { } LLEnvironment::EnvironmentInfo::ptr_t LLEnvironment::EnvironmentInfo::extract(LLSD environment) { ptr_t pinfo = std::make_shared(); pinfo->mIsDefault = environment.has(KEY_ISDEFAULT) ? environment[KEY_ISDEFAULT].asBoolean() : true; pinfo->mParcelId = environment.has(KEY_PARCELID) ? environment[KEY_PARCELID].asInteger() : INVALID_PARCEL_ID; pinfo->mRegionId = environment.has(KEY_REGIONID) ? environment[KEY_REGIONID].asUUID() : LLUUID::null; pinfo->mIsLegacy = false; if (environment.has(KEY_TRACKALTS)) { for (int idx = 0; idx < 3; idx++) { pinfo->mAltitudes[idx+1] = environment[KEY_TRACKALTS][idx].asReal(); } pinfo->mAltitudes[0] = 0; } if (environment.has(KEY_DAYCYCLE)) { pinfo->mDayCycle = LLSettingsVODay::buildFromEnvironmentMessage(environment[KEY_DAYCYCLE]); pinfo->mDayLength = LLSettingsDay::Seconds(environment.has(KEY_DAYLENGTH) ? environment[KEY_DAYLENGTH].asInteger() : -1); pinfo->mDayOffset = LLSettingsDay::Seconds(environment.has(KEY_DAYOFFSET) ? environment[KEY_DAYOFFSET].asInteger() : -1); pinfo->mDayHash = environment.has(KEY_DAYHASH) ? environment[KEY_DAYHASH].asInteger() : 0; } else { pinfo->mDayLength = LLEnvironment::instance().getEnvironmentDayLength(ENV_REGION); pinfo->mDayOffset = LLEnvironment::instance().getEnvironmentDayOffset(ENV_REGION); } if (environment.has(KEY_DAYASSET)) { pinfo->mAssetId = environment[KEY_DAYASSET].asUUID(); } if (environment.has(KEY_DAYNAMES)) { LLSD daynames = environment[KEY_DAYNAMES]; if (daynames.isArray()) { pinfo->mDayCycleName.clear(); for (S32 index = 0; index < pinfo->mNameList.size(); ++index) { pinfo->mNameList[index] = daynames[index].asString(); } } else if (daynames.isString()) { for (std::string &name: pinfo->mNameList) { name.clear(); } pinfo->mDayCycleName = daynames.asString(); } } else if (pinfo->mDayCycle) { pinfo->mDayCycleName = pinfo->mDayCycle->getName(); } if (environment.has(KEY_ENVVERSION)) { LLSD version = environment[KEY_ENVVERSION]; pinfo->mEnvVersion = version.asInteger(); } else { // can be used for region, but versions should be same pinfo->mEnvVersion = pinfo->mIsDefault ? UNSET_PARCEL_ENVIRONMENT_VERSION : INVALID_PARCEL_ENVIRONMENT_VERSION; } return pinfo; } LLEnvironment::EnvironmentInfo::ptr_t LLEnvironment::EnvironmentInfo::extractLegacy(LLSD legacy) { if (!legacy.isArray() || !legacy[0].has("regionID")) { LL_WARNS("ENVIRONMENT") << "Invalid legacy settings for environment: " << legacy << LL_ENDL; return ptr_t(); } ptr_t pinfo = std::make_shared(); pinfo->mIsDefault = false; pinfo->mParcelId = INVALID_PARCEL_ID; pinfo->mRegionId = legacy[0]["regionID"].asUUID(); pinfo->mIsLegacy = true; pinfo->mDayLength = LLSettingsDay::DEFAULT_DAYLENGTH; pinfo->mDayOffset = LLSettingsDay::DEFAULT_DAYOFFSET; pinfo->mDayCycle = LLSettingsVODay::buildFromLegacyMessage(pinfo->mRegionId, legacy[1], legacy[2], legacy[3]); if (pinfo->mDayCycle) pinfo->mDayHash = pinfo->mDayCycle->getHash(); pinfo->mAltitudes[0] = 0; pinfo->mAltitudes[2] = 10001; pinfo->mAltitudes[3] = 10002; pinfo->mAltitudes[4] = 10003; return pinfo; } //========================================================================= LLSettingsWater::ptr_t LLEnvironment::createWaterFromLegacyPreset(const std::string filename, LLSD &messages) { std::string name(gDirUtilp->getBaseFileName(filename, true)); std::string path(gDirUtilp->getDirName(filename)); LLSettingsWater::ptr_t water = LLSettingsVOWater::buildFromLegacyPresetFile(name, path, messages); if (!water) { messages["NAME"] = name; messages["FILE"] = filename; } return water; } LLSettingsSky::ptr_t LLEnvironment::createSkyFromLegacyPreset(const std::string filename, LLSD &messages) { std::string name(gDirUtilp->getBaseFileName(filename, true)); std::string path(gDirUtilp->getDirName(filename)); LLSettingsSky::ptr_t sky = LLSettingsVOSky::buildFromLegacyPresetFile(name, path, messages); if (!sky) { messages["NAME"] = name; messages["FILE"] = filename; } return sky; } LLSettingsDay::ptr_t LLEnvironment::createDayCycleFromLegacyPreset(const std::string filename, LLSD &messages) { std::string name(gDirUtilp->getBaseFileName(filename, true)); std::string path(gDirUtilp->getDirName(filename)); LLSettingsDay::ptr_t day = LLSettingsVODay::buildFromLegacyPresetFile(name, path, messages); if (!day) { messages["NAME"] = name; messages["FILE"] = filename; } return day; } LLSettingsDay::ptr_t LLEnvironment::createDayCycleFromEnvironment(EnvSelection_t env, LLSettingsBase::ptr_t settings) { std::string type(settings->getSettingsType()); if (type == "daycycle") return std::static_pointer_cast(settings); if ((env != ENV_PARCEL) && (env != ENV_REGION)) { LL_WARNS("ENVIRONMENT") << "May only create from parcel or region environment." << LL_ENDL; return LLSettingsDay::ptr_t(); } LLSettingsDay::ptr_t day = this->getEnvironmentDay(env); if (!day && (env == ENV_PARCEL)) { day = this->getEnvironmentDay(ENV_REGION); } if (!day) { LL_WARNS("ENVIRONMENT") << "Could not retrieve existing day settings." << LL_ENDL; return LLSettingsDay::ptr_t(); } day = day->buildClone(); if (type == "sky") { for (S32 idx = 1; idx < LLSettingsDay::TRACK_MAX; ++idx) day->clearCycleTrack(idx); day->setSettingsAtKeyframe(settings, 0.0f, 1); } else if (type == "water") { day->clearCycleTrack(LLSettingsDay::TRACK_WATER); day->setSettingsAtKeyframe(settings, 0.0f, LLSettingsDay::TRACK_WATER); } return day; } void LLEnvironment::onAgentPositionHasChanged(const LLVector3 &localpos) { S32 trackno = calculateSkyTrackForAltitude(localpos.mV[VZ]); if (trackno == mCurrentTrack) return; mCurrentTrack = trackno; LLViewerRegion* cur_region = gAgent.getRegion(); if (!cur_region || !cur_region->capabilitiesReceived()) { // Environment not ready, environment will be updated later, don't cause 'blend' yet. // But keep mCurrentTrack updated in case we won't get new altitudes for some reason return; } for (S32 env = ENV_LOCAL; env < ENV_DEFAULT; ++env) { if (mEnvironments[env]) mEnvironments[env]->setSkyTrack(mCurrentTrack); } } S32 LLEnvironment::calculateSkyTrackForAltitude(F64 altitude) { auto it = std::find_if_not(mTrackAltitudes.begin(), mTrackAltitudes.end(), [altitude](F32 test) { return altitude > test; }); if (it == mTrackAltitudes.begin()) return 1; else if (it == mTrackAltitudes.end()) return 4; return std::min(static_cast(std::distance(mTrackAltitudes.begin(), it)), 4); } //------------------------------------------------------------------------- void LLEnvironment::handleEnvironmentPush(LLSD &message) { // Log the experience message LLExperienceLog::instance().handleExperienceMessage(message); std::string action = message[KEY_ACTION].asString(); LLUUID experience_id = message[KEY_EXPERIENCEID].asUUID(); LLSD action_data = message[KEY_ACTIONDATA]; F32 transition_time = action_data[KEY_TRANSITIONTIME].asReal(); //TODO: Check here that the viewer thinks the experience is still valid. if (action == ACTION_CLEARENVIRONMENT) { handleEnvironmentPushClear(experience_id, action_data, transition_time); } else if (action == ACTION_PUSHFULLENVIRONMENT) { handleEnvironmentPushFull(experience_id, action_data, transition_time); } else if (action == ACTION_PUSHPARTIALENVIRONMENT) { handleEnvironmentPushPartial(experience_id, action_data, transition_time); } else { LL_WARNS("ENVIRONMENT", "GENERICMESSAGES") << "Unknown environment push action '" << action << "'" << LL_ENDL; } } void LLEnvironment::handleEnvironmentPushClear(LLUUID experience_id, LLSD &message, F32 transition) { clearExperienceEnvironment(experience_id, LLSettingsBase::Seconds(transition)); } void LLEnvironment::handleEnvironmentPushFull(LLUUID experience_id, LLSD &message, F32 transition) { LLUUID asset_id(message[KEY_ASSETID].asUUID()); setExperienceEnvironment(experience_id, asset_id, LLSettingsBase::Seconds(transition)); } void LLEnvironment::handleEnvironmentPushPartial(LLUUID experience_id, LLSD &message, F32 transition) { LLSD settings(message["settings"]); if (settings.isUndefined()) return; setExperienceEnvironment(experience_id, settings, LLSettingsBase::Seconds(transition)); } void LLEnvironment::clearExperienceEnvironment(LLUUID experience_id, LLSettingsBase::Seconds transition_time) { DayInjection::ptr_t injection = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH)); if (injection) { injection->clearInjections(experience_id, transition_time); } } void LLEnvironment::setSharedEnvironment() { clearEnvironment(LLEnvironment::ENV_LOCAL); setSelectedEnvironment(LLEnvironment::ENV_LOCAL); updateEnvironment(); } void LLEnvironment::setExperienceEnvironment(LLUUID experience_id, LLUUID asset_id, F32 transition_time) { LLSettingsVOBase::getSettingsAsset(asset_id, [this, experience_id, transition_time](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) { onSetExperienceEnvAssetLoaded(experience_id, settings, transition_time, status); }); } void LLEnvironment::onSetExperienceEnvAssetLoaded(LLUUID experience_id, LLSettingsBase::ptr_t settings, F32 transition_time, S32 status) { DayInjection::ptr_t environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH)); bool updateenvironment(false); if (!settings || status) { LLSD args; args["NAME"] = experience_id.asString(); LLNotificationsUtil::add("FailedToFindSettings", args); return; } if (!environment) { environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH, true)); updateenvironment = true; } if (settings->getSettingsType() == "daycycle") { environment->setInjectedDay(std::static_pointer_cast(settings), experience_id, LLSettingsBase::Seconds(transition_time)); } else if (settings->getSettingsType() == "sky") { environment->setInjectedSky(std::static_pointer_cast(settings), experience_id, LLSettingsBase::Seconds(transition_time)); } else if (settings->getSettingsType() == "water") { environment->setInjectedWater(std::static_pointer_cast(settings), experience_id, LLSettingsBase::Seconds(transition_time)); } if (updateenvironment) updateEnvironment(TRANSITION_INSTANT, true); } void LLEnvironment::setExperienceEnvironment(LLUUID experience_id, LLSD data, F32 transition_time) { LLSD sky(data["sky"]); LLSD water(data["water"]); if (sky.isUndefined() && water.isUndefined()) { clearExperienceEnvironment(experience_id, LLSettingsBase::Seconds(transition_time)); return; } DayInjection::ptr_t environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH)); bool updateenvironment(false); if (!environment) { environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH, true)); updateenvironment = true; } if (!sky.isUndefined()) { environment->injectSkySettings(sky, experience_id, LLSettingsBase::Seconds(transition_time)); } if (!water.isUndefined()) { environment->injectWaterSettings(water, experience_id, LLSettingsBase::Seconds(transition_time)); } if (updateenvironment) updateEnvironment(TRANSITION_INSTANT, true); } void LLEnvironment::listenExperiencePump(const LLSD &message) { LLUUID experience_id = message["experience"]; LLSD data = message[experience_id.asString()]; std::string permission(data["permission"].asString()); if ((permission == "Forget") || (permission == "Block")) { clearExperienceEnvironment(experience_id, (permission == "Block") ? TRANSITION_INSTANT : TRANSITION_FAST); } } //========================================================================= LLEnvironment::DayInstance::DayInstance(EnvSelection_t env) : mDayCycle(), mSky(), mWater(), mDayLength(LLSettingsDay::DEFAULT_DAYLENGTH), mDayOffset(LLSettingsDay::DEFAULT_DAYOFFSET), mBlenderSky(), mBlenderWater(), mInitialized(false), mSkyTrack(1), mEnv(env), mAnimateFlags(0) { } LLEnvironment::DayInstance::ptr_t LLEnvironment::DayInstance::clone() const { ptr_t environment = std::make_shared(mEnv); environment->mDayCycle = mDayCycle; environment->mSky = mSky; environment->mWater = mWater; environment->mDayLength = mDayLength; environment->mDayOffset = mDayOffset; environment->mBlenderSky = mBlenderSky; environment->mBlenderWater = mBlenderWater; environment->mInitialized = mInitialized; environment->mSkyTrack = mSkyTrack; environment->mAnimateFlags = mAnimateFlags; return environment; } bool LLEnvironment::DayInstance::applyTimeDelta(const LLSettingsBase::Seconds& delta) { LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; ptr_t keeper(shared_from_this()); // makes sure that this does not go away while it is being worked on. bool changed(false); if (!mInitialized) initialize(); if (mBlenderSky) changed |= mBlenderSky->applyTimeDelta(delta); if (mBlenderWater) changed |= mBlenderWater->applyTimeDelta(delta); return changed; } void LLEnvironment::DayInstance::setDay(const LLSettingsDay::ptr_t &pday, LLSettingsDay::Seconds daylength, LLSettingsDay::Seconds dayoffset) { mInitialized = false; mAnimateFlags = 0; mDayCycle = pday; mDayLength = daylength; mDayOffset = dayoffset; mBlenderSky.reset(); mBlenderWater.reset(); mSky = LLSettingsVOSky::buildDefaultSky(); mWater = LLSettingsVOWater::buildDefaultWater(); animate(); } bool LLEnvironment::DayInstance::setSky(const LLSettingsSky::ptr_t &psky) { mInitialized = false; bool changed = psky == nullptr || mSky == nullptr || mSky->getHash() != psky->getHash(); bool different_sky = mSky != psky; mSky = psky; mSky->mReplaced |= different_sky; mSky->update(); mBlenderSky.reset(); if (gAtmosphere) { AtmosphericModelSettings settings; LLEnvironment::getAtmosphericModelSettings(settings, psky); gAtmosphere->configureAtmosphericModel(settings); } return changed; } void LLEnvironment::DayInstance::setWater(const LLSettingsWater::ptr_t &pwater) { mInitialized = false; bool different_water = mWater != pwater; mWater = pwater; mWater->mReplaced |= different_water; mWater->update(); mBlenderWater.reset(); } void LLEnvironment::DayInstance::initialize() { mInitialized = true; if (!mWater) mWater = LLSettingsVOWater::buildDefaultWater(); if (!mSky) mSky = LLSettingsVOSky::buildDefaultSky(); } void LLEnvironment::DayInstance::clear() { mDayCycle.reset(); mSky.reset(); mWater.reset(); mDayLength = LLSettingsDay::DEFAULT_DAYLENGTH; mDayOffset = LLSettingsDay::DEFAULT_DAYOFFSET; mBlenderSky.reset(); mBlenderWater.reset(); mSkyTrack = 1; } void LLEnvironment::DayInstance::setSkyTrack(S32 trackno) { mSkyTrack = trackno; if (mBlenderSky) { mBlenderSky->switchTrack(trackno, 0.0); } } void LLEnvironment::DayInstance::setBlenders(const LLSettingsBlender::ptr_t &skyblend, const LLSettingsBlender::ptr_t &waterblend) { mBlenderSky = skyblend; mBlenderWater = waterblend; } LLSettingsBase::TrackPosition LLEnvironment::DayInstance::getProgress() const { LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch()); now += mDayOffset; if ((mDayLength <= 0) || !mDayCycle) return -1.0f; // no actual day cycle. return convert_time_to_position(now, mDayLength); } LLSettingsBase::TrackPosition LLEnvironment::DayInstance::secondsToKeyframe(LLSettingsDay::Seconds seconds) { return convert_time_to_position(seconds, mDayLength); } void LLEnvironment::DayInstance::animate() { LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch()); now += mDayOffset; if (!mDayCycle) return; if (!(mAnimateFlags & NO_ANIMATE_WATER)) { LLSettingsDay::CycleTrack_t &wtrack = mDayCycle->getCycleTrack(0); if (wtrack.empty()) { mWater.reset(); mBlenderWater.reset(); } else { mWater = LLSettingsVOWater::buildDefaultWater(); mBlenderWater = std::make_shared(mWater, mDayCycle, 0, mDayLength, mDayOffset, DEFAULT_UPDATE_THRESHOLD); } } if (!(mAnimateFlags & NO_ANIMATE_SKY)) { // sky, initialize to track 1 LLSettingsDay::CycleTrack_t &track = mDayCycle->getCycleTrack(1); if (track.empty()) { mSky.reset(); mBlenderSky.reset(); } else { mSky = LLSettingsVOSky::buildDefaultSky(); mBlenderSky = std::make_shared(mSky, mDayCycle, 1, mDayLength, mDayOffset, DEFAULT_UPDATE_THRESHOLD); mBlenderSky->switchTrack(mSkyTrack, 0.0); } } } //------------------------------------------------------------------------- LLEnvironment::DayTransition::DayTransition(const LLSettingsSky::ptr_t &skystart, const LLSettingsWater::ptr_t &waterstart, LLEnvironment::DayInstance::ptr_t &end, LLSettingsDay::Seconds time) : DayInstance(ENV_NONE), mStartSky(skystart), mStartWater(waterstart), mNextInstance(end), mTransitionTime(time) { } bool LLEnvironment::DayTransition::applyTimeDelta(const LLSettingsBase::Seconds& delta) { bool changed(false); changed = mNextInstance->applyTimeDelta(delta); changed |= DayInstance::applyTimeDelta(delta); return changed; } void LLEnvironment::DayTransition::animate() { mNextInstance->animate(); mWater = mStartWater->buildClone(); mBlenderWater = std::make_shared(mWater, mStartWater, mNextInstance->getWater(), mTransitionTime); mBlenderWater->setOnFinished( [this](LLSettingsBlender::ptr_t blender) { mBlenderWater.reset(); if (!mBlenderSky && !mBlenderWater) LLEnvironment::instance().mCurrentEnvironment = mNextInstance; else setWater(mNextInstance->getWater()); }); // pause probe updates and reset reflection maps on sky change gPipeline.mReflectionMapManager.pause(); gPipeline.mReflectionMapManager.reset(); mSky = mStartSky->buildClone(); mBlenderSky = std::make_shared(mSky, mStartSky, mNextInstance->getSky(), mTransitionTime); mBlenderSky->setOnFinished( [this](LLSettingsBlender::ptr_t blender) { mBlenderSky.reset(); // resume reflection probe updates gPipeline.mReflectionMapManager.resume(); if (!mBlenderSky && !mBlenderWater) LLEnvironment::instance().mCurrentEnvironment = mNextInstance; else setSky(mNextInstance->getSky()); }); } void LLEnvironment::saveToSettings() { std::string user_dir = gDirUtilp->getLindenUserDir(); if (user_dir.empty()) { // not logged in return; } bool has_data = false; if (gSavedSettings.getBOOL("EnvironmentPersistAcrossLogin")) { DayInstance::ptr_t environment = getEnvironmentInstance(ENV_LOCAL); if (environment) { // Environment is 'layered'. No data in ENV_LOCAL means we are using parcel/region // Store local environment for next session LLSD env_data; LLSettingsDay::ptr_t day = environment->getDayCycle(); if (day) { const std::string name = day->getName(); const LLUUID asset_id = day->getAssetId(); if (asset_id.notNull()) { // just save the id env_data["day_id"] = asset_id; env_data["day_length"] = LLSD::Integer(environment->getDayLength()); env_data["day_offset"] = LLSD::Integer(environment->getDayOffset()); has_data = true; } else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME) { // This setting was created locally and was not saved // The only option is to save the whole thing env_data["day_llsd"] = day->getSettings(); env_data["day_length"] = LLSD::Integer(environment->getDayLength()); env_data["day_offset"] = LLSD::Integer(environment->getDayOffset()); has_data = true; } } LLSettingsSky::ptr_t sky = environment->getSky(); if ((environment->getFlags() & DayInstance::NO_ANIMATE_SKY) && sky) { const std::string name = sky->getName(); const LLUUID asset_id = sky->getAssetId(); if (asset_id.notNull()) { // just save the id env_data["sky_id"] = asset_id; has_data = true; } else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME) { // This setting was created locally and was not saved // The only option is to save the whole thing env_data["sky_llsd"] = sky->getSettings(); has_data = true; } has_data = true; } LLSettingsWater::ptr_t water = environment->getWater(); if ((environment->getFlags() & DayInstance::NO_ANIMATE_WATER) && water) { const std::string name = water->getName(); const LLUUID asset_id = water->getAssetId(); if (asset_id.notNull()) { // just save the id env_data["water_id"] = asset_id; has_data = true; } else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME) { // This setting was created locally and was not saved // The only option is to save the whole thing env_data["water_llsd"] = water->getSettings(); has_data = true; } } std::string user_filepath = user_dir + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE; llofstream out(user_filepath.c_str(), std::ios_base::out | std::ios_base::binary); if (out.good()) { LLSDSerialize::toBinary(env_data, out); out.close(); } else { LL_WARNS("ENVIRONMENT") << "Unable to open " << user_filepath << " for output." << LL_ENDL; } } } if (!has_data) { LLFile::remove(user_dir + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE, ENOENT); } } void LLEnvironment::loadSkyWaterFromSettings(const LLSD &env_data, bool &valid, bool &assets_present) { if (env_data.has("sky_id")) { // causes asset loaded callback and an update setEnvironment(ENV_LOCAL, env_data["sky_id"].asUUID()); valid = true; assets_present = true; } else if (env_data.has("sky_llsd")) { LLSettingsSky::ptr_t sky = LLSettingsVOSky::buildSky(env_data["sky_llsd"]); setEnvironment(ENV_LOCAL, sky); valid = true; } if (env_data.has("water_id")) { // causes asset loaded callback and an update setEnvironment(ENV_LOCAL, env_data["water_id"].asUUID()); valid = true; assets_present = true; } else if (env_data.has("water_llsd")) { LLSettingsWater::ptr_t sky = LLSettingsVOWater::buildWater(env_data["water_llsd"]); setEnvironment(ENV_LOCAL, sky); valid = true; } } bool LLEnvironment::loadFromSettings() { if (!gSavedSettings.getBOOL("EnvironmentPersistAcrossLogin")) { return false; } std::string user_path = gDirUtilp->getLindenUserDir(); if (user_path.empty()) { LL_WARNS("ENVIRONMENT") << "Can't load previous environment, Environment was initialized before user logged in" << LL_ENDL; return false; } std::string user_filepath(user_path + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE); if (!gDirUtilp->fileExists(user_filepath)) { // No previous environment return false; } LLSD env_data; llifstream file(user_filepath.c_str(), std::ios_base::in | std::ios_base::binary); if (file.is_open()) { LLSDSerialize::fromBinary(env_data, file, LLSDSerialize::SIZE_UNLIMITED); if (env_data.isUndefined()) { LL_WARNS("ENVIRONMENT") << "error loading " << user_filepath << LL_ENDL; return false; } else { LL_INFOS("ENVIRONMENT") << "Loaded previous session environment from: " << user_filepath << LL_ENDL; } file.close(); } else { LL_INFOS("ENVIRONMENT") << "Unable to open previous session environment file " << user_filepath << LL_ENDL; } if (!env_data.isMap() || (env_data.size() == 0)) { LL_DEBUGS("ENVIRONMENT") << "Empty map loaded from: " << user_filepath << LL_ENDL; return false; } bool valid = false; bool has_assets = false; if (env_data.has("day_id")) { LLUUID assetId = env_data["day_id"].asUUID(); LLSettingsVOBase::getSettingsAsset(assetId, [this, env_data](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) { // Day should be always applied first, // otherwise it will override sky or water that was set earlier // so wait for asset to load before applying sky/water onSetEnvAssetLoaded(ENV_LOCAL, asset_id, settings, TRANSITION_DEFAULT, status, NO_VERSION); bool valid = false, has_assets = false; loadSkyWaterFromSettings(env_data, valid, has_assets); if (!has_assets && valid) { // Settings were loaded from file without having an asset, needs update // otherwise update will be done by asset callback updateEnvironment(TRANSITION_DEFAULT, true); } }); // bail early, everything have to be done at callback return true; } else if (env_data.has("day_llsd")) { S32 length = env_data["day_length"].asInteger(); S32 offset = env_data["day_offset"].asInteger(); LLSettingsDay::ptr_t pday = LLSettingsVODay::buildDay(env_data["day_llsd"]); setEnvironment(ENV_LOCAL, pday, LLSettingsDay::Seconds(length), LLSettingsDay::Seconds(offset)); valid = true; } loadSkyWaterFromSettings(env_data, valid, has_assets); if (valid && !has_assets) { // Settings were loaded from file without having an asset, needs update // otherwise update will be done by asset callback updateEnvironment(TRANSITION_DEFAULT, true); } return valid; } void LLEnvironment::saveBeaconsState() { if (mEditorCounter == 0) { mShowSunBeacon = gSavedSettings.getBOOL("sunbeacon"); mShowMoonBeacon = gSavedSettings.getBOOL("moonbeacon"); } ++mEditorCounter; } void LLEnvironment::revertBeaconsState() { --mEditorCounter; if (mEditorCounter == 0) { gSavedSettings.setBOOL("sunbeacon", mShowSunBeacon && gSavedSettings.getBOOL("sunbeacon")); gSavedSettings.setBOOL("moonbeacon", mShowMoonBeacon && gSavedSettings.getBOOL("moonbeacon")); } } //========================================================================= LLTrackBlenderLoopingManual::LLTrackBlenderLoopingManual(const LLSettingsBase::ptr_t &target, const LLSettingsDay::ptr_t &day, S32 trackno) : LLSettingsBlender(target, LLSettingsBase::ptr_t(), LLSettingsBase::ptr_t()), mDay(day), mTrackNo(trackno), mPosition(0.0) { LLSettingsDay::TrackBound_t initial = getBoundingEntries(mPosition); if (initial.first != mEndMarker) { // No frames in track mInitial = (*initial.first).second; mFinal = (*initial.second).second; LLSD initSettings = mInitial->getSettings(); mTarget->replaceSettings(initSettings); } } LLSettingsBase::BlendFactor LLTrackBlenderLoopingManual::setPosition(const LLSettingsBase::TrackPosition& position) { mPosition = llclamp(position, 0.0f, 1.0f); LLSettingsDay::TrackBound_t bounds = getBoundingEntries(mPosition); if (bounds.first == mEndMarker) { // No frames in track. return 0.0; } mInitial = (*bounds.first).second; mFinal = (*bounds.second).second; F64 spanLength = getSpanLength(bounds); F64 spanPos = ((mPosition < (*bounds.first).first) ? (mPosition + 1.0) : mPosition) - (*bounds.first).first; if (spanPos > spanLength) { // we are clamping position to 0-1 and spanLength is 1 // so don't account for case of spanPos == spanLength spanPos = fmod(spanPos, spanLength); } F64 blendf = spanPos / spanLength; return LLSettingsBlender::setBlendFactor(blendf); } void LLTrackBlenderLoopingManual::switchTrack(S32 trackno, const LLSettingsBase::TrackPosition& position) { mTrackNo = trackno; LLSettingsBase::TrackPosition useposition = (position < 0.0) ? mPosition : position; setPosition(useposition); } LLSettingsDay::TrackBound_t LLTrackBlenderLoopingManual::getBoundingEntries(F64 position) { LLSettingsDay::CycleTrack_t &wtrack = mDay->getCycleTrack(mTrackNo); mEndMarker = wtrack.end(); LLSettingsDay::TrackBound_t bounds = get_bounding_entries(wtrack, position); return bounds; } F64 LLTrackBlenderLoopingManual::getSpanLength(const LLSettingsDay::TrackBound_t &bounds) const { return get_wrapping_distance((*bounds.first).first, (*bounds.second).first); } //========================================================================= namespace { DayInjection::DayInjection(LLEnvironment::EnvSelection_t env): LLEnvironment::DayInstance(env), mBaseDayInstance(), mInjectedSky(), mInjectedWater(), mActiveExperiences(), mDayExperience(), mSkyExperience(), mWaterExperience(), mEnvChangeConnection(), mParcelChangeConnection() { mInjectedSky = std::make_shared(LLEnvironment::instance().getCurrentSky()); mInjectedWater = std::make_shared(LLEnvironment::instance().getCurrentWater()); mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance(); mSky = mInjectedSky; mWater = mInjectedWater; mEnvChangeConnection = LLEnvironment::instance().setEnvironmentChanged([this](LLEnvironment::EnvSelection_t env, S32) { onEnvironmentChanged(env); }); mParcelChangeConnection = gAgent.addParcelChangedCallback([this]() { onParcelChange(); }); } DayInjection::~DayInjection() { if (mEnvChangeConnection.connected()) mEnvChangeConnection.disconnect(); if (mParcelChangeConnection.connected()) mParcelChangeConnection.disconnect(); } bool DayInjection::applyTimeDelta(const LLSettingsBase::Seconds& delta) { bool changed(false); if (mBaseDayInstance) changed |= mBaseDayInstance->applyTimeDelta(delta); mInjectedSky->applyInjections(delta); mInjectedWater->applyInjections(delta); changed |= LLEnvironment::DayInstance::applyTimeDelta(delta); if (changed) { mInjectedSky->setDirtyFlag(true); mInjectedWater->setDirtyFlag(true); } mInjectedSky->update(); mInjectedWater->update(); if (!hasInjections()) { // There are no injections being managed. This should really go away. LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); } return changed; } void DayInjection::setBaseDayInstance(const LLEnvironment::DayInstance::ptr_t &baseday) { mBaseDayInstance = baseday; if (mSkyExperience.isNull()) mInjectedSky->setSource(mBaseDayInstance->getSky()); if (mWaterExperience.isNull()) mInjectedWater->setSource(mBaseDayInstance->getWater()); } bool DayInjection::hasInjections() const { return (!mSkyExperience.isNull() || !mWaterExperience.isNull() || !mDayExperience.isNull() || mBlenderSky || mBlenderWater || mInjectedSky->hasInjections() || mInjectedWater->hasInjections()); } void DayInjection::testExperiencesOnParcel(S32 parcel_id) { LLCoros::instance().launch("DayInjection::testExperiencesOnParcel", [this, parcel_id]() { DayInjection::testExperiencesOnParcelCoro(std::static_pointer_cast(this->shared_from_this()), parcel_id); }); } void DayInjection::setInjectedDay(const LLSettingsDay::ptr_t &pday, LLUUID experience_id, LLSettingsBase::Seconds transition) { mSkyExperience = experience_id; mWaterExperience = experience_id; mDayExperience = experience_id; mBaseDayInstance = mBaseDayInstance->clone(); mBaseDayInstance->setEnvironmentSelection(LLEnvironment::ENV_NONE); mBaseDayInstance->setDay(pday, mBaseDayInstance->getDayLength(), mBaseDayInstance->getDayOffset()); animateSkyChange(mBaseDayInstance->getSky(), transition); animateWaterChange(mBaseDayInstance->getWater(), transition); mActiveExperiences.insert(experience_id); } void DayInjection::setInjectedSky(const LLSettingsSky::ptr_t &psky, LLUUID experience_id, LLSettingsBase::Seconds transition) { mSkyExperience = experience_id; mActiveExperiences.insert(experience_id); checkExperience(); animateSkyChange(psky, transition); } void DayInjection::setInjectedWater(const LLSettingsWater::ptr_t &pwater, LLUUID experience_id, LLSettingsBase::Seconds transition) { mWaterExperience = experience_id; mActiveExperiences.insert(experience_id); checkExperience(); animateWaterChange(pwater, transition); } void DayInjection::injectSkySettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition) { mInjectedSky->injectExperienceValues(settings, experience_id, transition); mActiveExperiences.insert(experience_id); } void DayInjection::injectWaterSettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition) { mInjectedWater->injectExperienceValues(settings, experience_id, transition); mActiveExperiences.insert(experience_id); } void DayInjection::clearInjections(LLUUID experience_id, LLSettingsBase::Seconds transition_time) { if ((experience_id.isNull() && !mDayExperience.isNull()) || (experience_id == mDayExperience)) { mDayExperience.setNull(); if (mSkyExperience == experience_id) mSkyExperience.setNull(); if (mWaterExperience == experience_id) mWaterExperience.setNull(); mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance(); if (mSkyExperience.isNull()) animateSkyChange(mBaseDayInstance->getSky(), transition_time); if (mWaterExperience.isNull()) animateWaterChange(mBaseDayInstance->getWater(), transition_time); } if ((experience_id.isNull() && !mSkyExperience.isNull()) || (experience_id == mSkyExperience)) { mSkyExperience.setNull(); animateSkyChange(mBaseDayInstance->getSky(), transition_time); } if ((experience_id.isNull() && !mWaterExperience.isNull()) || (experience_id == mWaterExperience)) { mWaterExperience.setNull(); animateWaterChange(mBaseDayInstance->getWater(), transition_time); } mInjectedSky->removeInjections(experience_id, transition_time); mInjectedWater->removeInjections(experience_id, transition_time); if (experience_id.isNull()) mActiveExperiences.clear(); else mActiveExperiences.erase(experience_id); if ((transition_time == LLEnvironment::TRANSITION_INSTANT) && (countExperiencesActive() == 0)) { // Only do this if instant and there are no other experiences injecting values. // (otherwise will be handled after transition) LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); } } void DayInjection::testExperiencesOnParcelCoro(wptr_t that, S32 parcel_id) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("testExperiencesOnParcelCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); std::string url = gAgent.getRegionCapability("ExperienceQuery"); if (url.empty()) { LL_WARNS("ENVIRONMENT") << "No experience query cap." << LL_ENDL; return; // no checking in this region. } { ptr_t thatlock(that); std::stringstream fullurl; if (!thatlock) return; fullurl << url << "?"; fullurl << "parcelid=" << parcel_id; for (auto it = thatlock->mActiveExperiences.begin(); it != thatlock->mActiveExperiences.end(); ++it) { if (it != thatlock->mActiveExperiences.begin()) fullurl << ","; else fullurl << "&experiences="; fullurl << (*it).asString(); } url = fullurl.str(); } LLSD result = httpAdapter->getAndSuspend(httpRequest, url); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) { LL_WARNS() << "Unable to retrieve experience status for parcel." << LL_ENDL; return; } { LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); if (!parcel) return; if (parcel_id != parcel->getLocalID()) { // Agent no longer on queried parcel. return; } } LLSD experiences = result["experiences"]; { ptr_t thatlock(that); if (!thatlock) return; for (LLSD::map_iterator itr = experiences.beginMap(); itr != experiences.endMap(); ++itr) { if (!((*itr).second.asBoolean())) thatlock->clearInjections(LLUUID((*itr).first), LLEnvironment::TRANSITION_FAST); } } } void DayInjection::animateSkyChange(LLSettingsSky::ptr_t psky, LLSettingsBase::Seconds transition) { if (mInjectedSky.get() == psky.get()) { // An attempt to animate to itself... don't do it. return; } if (transition == LLEnvironment::TRANSITION_INSTANT) { mBlenderSky.reset(); mInjectedSky->setSource(psky); } else { LLSettingsSky::ptr_t start_sky(mInjectedSky->getSource()->buildClone()); LLSettingsSky::ptr_t target_sky(start_sky->buildClone()); mInjectedSky->setSource(target_sky); // clear reflection probes and pause updates during sky change gPipeline.mReflectionMapManager.pause(); gPipeline.mReflectionMapManager.reset(); mBlenderSky = std::make_shared(target_sky, start_sky, psky, transition); mBlenderSky->setOnFinished( [this, psky](LLSettingsBlender::ptr_t blender) { mBlenderSky.reset(); mInjectedSky->setSource(psky); // resume updating reflection probes when done animating sky gPipeline.mReflectionMapManager.resume(); setSky(mInjectedSky); if (!mBlenderWater && (countExperiencesActive() == 0)) { LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); } }); } } void DayInjection::animateWaterChange(LLSettingsWater::ptr_t pwater, LLSettingsBase::Seconds transition) { if (mInjectedWater.get() == pwater.get()) { // An attempt to animate to itself. Bad idea. return; } if (transition == LLEnvironment::TRANSITION_INSTANT) { mBlenderWater.reset(); mInjectedWater->setSource(pwater); } else { LLSettingsWater::ptr_t start_Water(mInjectedWater->getSource()->buildClone()); LLSettingsWater::ptr_t scratch_Water(start_Water->buildClone()); mInjectedWater->setSource(scratch_Water); mBlenderWater = std::make_shared(scratch_Water, start_Water, pwater, transition); mBlenderWater->setOnFinished( [this, pwater](LLSettingsBlender::ptr_t blender) { mBlenderWater.reset(); mInjectedWater->setSource(pwater); setWater(mInjectedWater); if (!mBlenderSky && (countExperiencesActive() == 0)) { LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); } }); } } void DayInjection::onEnvironmentChanged(LLEnvironment::EnvSelection_t env) { if (env >= LLEnvironment::ENV_PARCEL) { LLEnvironment::EnvSelection_t base_env(mBaseDayInstance->getEnvironmentSelection()); LLEnvironment::DayInstance::ptr_t nextbase = LLEnvironment::instance().getSharedEnvironmentInstance(); if ((base_env == LLEnvironment::ENV_NONE) || (nextbase == mBaseDayInstance) || (!mSkyExperience.isNull() && !mWaterExperience.isNull())) { // base instance completely overridden, or not changed no transition will happen return; } LL_WARNS("PUSHENV", "ENVIRONMENT") << "Underlying environment has changed (" << env << ")! Base env is type " << base_env << LL_ENDL; LLEnvironment::DayInstance::ptr_t trans = std::make_shared(std::static_pointer_cast(shared_from_this()), mBaseDayInstance->getSky(), mBaseDayInstance->getWater(), nextbase, LLEnvironment::TRANSITION_DEFAULT); trans->animate(); setBaseDayInstance(trans); } } void DayInjection::onParcelChange() { S32 parcel_id(INVALID_PARCEL_ID); LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); if (!parcel) return; parcel_id = parcel->getLocalID(); testExperiencesOnParcel(parcel_id); } void DayInjection::checkExperience() { if ((!mDayExperience.isNull()) && (mSkyExperience != mDayExperience) && (mWaterExperience != mDayExperience)) { // There was a day experience but we've replaced it with a water and a sky experience. mDayExperience.setNull(); mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance(); } } void DayInjection::animate() { } void InjectedTransition::animate() { mNextInstance->animate(); if (!mInjection->isOverriddenSky()) { mSky = mStartSky->buildClone(); mBlenderSky = std::make_shared(mSky, mStartSky, mNextInstance->getSky(), mTransitionTime); mBlenderSky->setOnFinished( [this](LLSettingsBlender::ptr_t blender) { mBlenderSky.reset(); if (!mBlenderSky && !mBlenderSky) mInjection->setBaseDayInstance(mNextInstance); else mInjection->mInjectedSky->setSource(mNextInstance->getSky()); }); } else { mSky = mInjection->getSky(); mBlenderSky.reset(); } if (!mInjection->isOverriddenWater()) { mWater = mStartWater->buildClone(); mBlenderWater = std::make_shared(mWater, mStartWater, mNextInstance->getWater(), mTransitionTime); mBlenderWater->setOnFinished( [this](LLSettingsBlender::ptr_t blender) { mBlenderWater.reset(); if (!mBlenderSky && !mBlenderWater) mInjection->setBaseDayInstance(mNextInstance); else mInjection->mInjectedWater->setSource(mNextInstance->getWater()); }); } else { mWater = mInjection->getWater(); mBlenderWater.reset(); } } }