// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdpch.h" // #include "nel/3d/u_instance_material.h" #include "nel/3d/u_particle_system_instance.h" #include "nel/3d/u_instance.h" // #include "nel/misc/path.h" #include "nel/misc/file.h" // #include "sky_object.h" #include "sky.h" using namespace NLMISC; using namespace NL3D; //////////////////////////// // CSkyObject::CColorInfo // //////////////////////////// // ************************************************************************************************* void CSkyObject::CColorInfo::init(const CSkyObjectSheet::CColorInfoSheet &ci, std::map &bitmapByName, std::vector &builtBitmaps) { Mode = ci.Mode; bool alreadyBuilt; Map = buildSharedBitmap(ci.MapName, bitmapByName, builtBitmaps, alreadyBuilt); } // ************************************************************************************************* CRGBA CSkyObject::CColorInfo::computeColor(float dayPart, float weatherLevel, CRGBA fogColor) { switch(Mode) { case Unused: return CRGBA(0, 0, 0, 0); case FogColor: return fogColor; case BitmapColor: clamp(dayPart, 0.f, 1.f); clamp(weatherLevel, 0.f, 1.f); if (!Map) return CRGBA(0, 0, 0, 0); return Map->getColor(dayPart, weatherLevel, true, false); case BitmapColorModulatedByFogColor: { clamp(dayPart, 0.f, 1.f); clamp(weatherLevel, 0.f, 1.f); if (!Map) return CRGBA(0, 0, 0, 0); CRGBA result = Map->getColor(dayPart, weatherLevel, true, false); result.modulateFromColor(result, fogColor); return result; } break; default: nlassert(0); // unknwon type break; } return CRGBA(0, 0, 0, 0); } // ************************************************************************************************* //////////////////////////////////// // CSkyObject::CColorGradientInfo // //////////////////////////////////// void CSkyObject::CColorGradientInfo::init(const CSkyObjectSheet::CColorGradientInfoSheet &cgis, std::map &bitmapByName, std::vector &builtBitmaps) { TargetTextureStage = cgis.TargetTextureStage; WeatherToGradient.resize(cgis.WeatherToGradient.size()); CBitmap *lastSeenBitmap = NULL; for(uint k = 0; k < cgis.WeatherToGradient.size(); ++k) { bool alreadyBuilt; WeatherToGradient[k] = buildSharedBitmap(cgis.WeatherToGradient[k], bitmapByName, builtBitmaps, alreadyBuilt); if (WeatherToGradient[k]) { if (!WeatherToGradient[k]->convertToType(CBitmap::RGBA)) { // can't use bitmap.. WeatherToGradient[k] = NULL; // don't do a delete here because it'is 'builtBitmaps' that has ownership } else { if (!alreadyBuilt) { // rotate the bitmap because it is faster to blit a row than a column WeatherToGradient[k]->rot90CCW(); WeatherToGradient[k]->flipV(); } if (lastSeenBitmap) { if (WeatherToGradient[k]->getWidth() != lastSeenBitmap->getWidth() || WeatherToGradient[k]->getHeight() != lastSeenBitmap->getHeight() ) { nlwarning("All bitmaps must have the same size in the gradient"); } } lastSeenBitmap = WeatherToGradient[k]; } } } } /* static void dumpGrad(CBitmap &bm) { CRGBA *pixs = (CRGBA *) &bm.getPixels(0)[0]; for(uint k = 0; k < bm.getWidth(); ++k) { nlinfo("(r, g, b, a) = (%d, %d, %d, %d)", pixs[k].R, pixs[k].G, pixs[k].B, pixs[k].A); } } */ // ************************************************************************************************* void CSkyObject::CColorGradientInfo::setup(NL3D::UInstance instance, float dayPart, float weatherLevel, CBitmap &gradientCache, CBitmap &gradientCacheBlurred) { if (instance.empty()) return; if (WeatherToGradient.empty()) return; clamp(dayPart, 0.f, 1.f); clamp(weatherLevel, 0.f, 1.f); // takes 2 closest bitmaps to do the blend uint bm0Index = std::min(uint(weatherLevel * WeatherToGradient.size()), uint(WeatherToGradient.size() - 1)); uint bm1Index = std::min(uint(weatherLevel * WeatherToGradient.size() + 1), uint(WeatherToGradient.size() - 1)); CBitmap *bm0 = WeatherToGradient[bm0Index]; CBitmap *bm1 = WeatherToGradient[bm1Index]; if (!bm1 && !bm0) return; if (!bm1) bm1 = bm0; if (!bm0) bm0 = bm1; // make sure that both bitmap have the same size if (bm0->getWidth() != bm1->getWidth() || bm0->getHeight() != bm1->getHeight()) return; // extract the 2 slices before to blend uint slice0 = (uint) (dayPart * bm0->getHeight()); uint slice1 = (uint) (dayPart * bm0->getHeight() + 1) % bm0->getHeight(); //nlinfo("slice0 = %d", slice0); Slice0[0].resize(bm0->getWidth(), 1); Slice0[1].resize(bm0->getWidth(), 1); Slice0[0].blit(*bm0, 0, slice0, bm0->getWidth(), 1, 0, 0); Slice0[1].blit(*bm0, 0, slice1, bm0->getWidth(), 1, 0, 0); Slice0[0].blend(Slice0[0], Slice0[1], (uint) (256 * fmodf(dayPart * bm0->getHeight(), 1.f)), true); Slice1[0].resize(bm0->getWidth(), 1); Slice1[1].resize(bm0->getWidth(), 1); Slice1[0].blit(*bm1, 0, slice0, bm1->getWidth(), 1, 0, 0); Slice1[1].blit(*bm1, 0, slice1, bm1->getWidth(), 1, 0, 0); Slice1[0].blend(Slice1[0], Slice1[1], (uint) (256 * fmodf(dayPart * bm0->getHeight(), 1.f)), true); Slice0[0].blend(Slice0[0], Slice1[0], (uint) (256 * fmodf(weatherLevel * WeatherToGradient.size(), 1.f)), true); // see if blended result differs from cache, if so, update texture of the instance const uint32 *newGrad = (uint32 *) &Slice0[0].getPixels()[0]; if (gradientCache.getPixels(0).empty()) { gradientCache.resize(bm1->getWidth() , 1, CBitmap::RGBA); } const uint32 *oldGrad = (uint32 *) &gradientCache.getPixels()[0]; nlassert(gradientCache.getWidth() == Slice0[0].getWidth()); if (!std::equal(newGrad, newGrad + bm0->getWidth(), oldGrad)) { // update the cache gradientCache.swap(Slice0[0]); // build a blurred version of the gradient cache (improve quality of gradient) if (gradientCacheBlurred.getWidth() != gradientCache.getWidth() || gradientCacheBlurred.getHeight() != gradientCache.getHeight()) { gradientCacheBlurred.resize(gradientCache.getWidth(), 1); } nlassert(gradientCacheBlurred.PixelFormat == gradientCache.PixelFormat); CRGBA *destPix = (CRGBA *) &gradientCacheBlurred.getPixels()[0]; const CRGBA *srcPix = (const CRGBA *) &gradientCache.getPixels(0)[0]; *destPix++ = *srcPix ++; const CRGBA *lastSrcPix = srcPix + Slice0[0].getWidth() - 2; while (srcPix != lastSrcPix) { destPix->R = (uint8) (((uint16) srcPix[- 1].R + (uint16) srcPix->R + (uint16) srcPix[1].R) * (256 / 3) >> 8); destPix->G = (uint8) (((uint16) srcPix[- 1].G + (uint16) srcPix->G + (uint16) srcPix[1].G) * (256 / 3) >> 8); destPix->B = (uint8) (((uint16) srcPix[- 1].B + (uint16) srcPix->B + (uint16) srcPix[1].B) * (256 / 3) >> 8); destPix->A = (uint8) (((uint16) srcPix[- 1].A + (uint16) srcPix->A + (uint16) srcPix[1].A) * (256 / 3) >> 8); ++ destPix; ++ srcPix; } *destPix++ = *srcPix ++; // set the new texture uint numMaterials = instance.getNumMaterials(); for(uint k = 0; k < numMaterials; ++k) { // do a free ccw rotate by swapping height & width (because height is 1) instance.getMaterial(k).setTextureMem(TargetTextureStage, &gradientCacheBlurred.getPixels()[0], gradientCacheBlurred.getWidth() * gradientCacheBlurred.getHeight() * sizeof(uint32), false, false, gradientCacheBlurred.getHeight(), gradientCacheBlurred.getWidth()); // clamp on v coordinate instance.getMaterial(k).setWrapT(TargetTextureStage, NL3D::UInstanceMaterial::Clamp); } } } // ************************************************************************************************* //////////////// // CSkyObject // //////////////// void CSkyObject::init(const CSkyObjectSheet::CVersionSheet &sheet, NL3D::UInstance instance, std::map &bitmapByName, std::vector &builtBitmaps, bool visibleInMainScene, bool visibleInEnvMap ) { if (instance.empty()) return; Instance = instance; // set display priority instance.setTransparencyPriority(sheet.TransparencyPriority); PS.cast(Instance); // DiffuseColor.init(sheet.DiffuseColor, bitmapByName, builtBitmaps); ParticleEmitters.init(sheet.ParticleEmitters, bitmapByName, builtBitmaps); for(uint k = 0; k < SKY_MAX_NUM_STAGE; ++k) { ConstantColor[k].init(sheet.ConstantColor[k], bitmapByName, builtBitmaps); bool alreadyBuilt; if (!sheet.OffsetUBitmap[k].empty()) { OffsetUBitmap[k] = buildSharedBitmap(sheet.OffsetUBitmap[k], bitmapByName, builtBitmaps, alreadyBuilt); } if (!sheet.OffsetVBitmap[k].empty()) { OffsetVBitmap[k] = buildSharedBitmap(sheet.OffsetVBitmap[k], bitmapByName, builtBitmaps, alreadyBuilt); } } ColorGradient.init(sheet.ColorGradient, bitmapByName, builtBitmaps); RefColor = sheet.RefColor; std::copy(sheet.TexPanner, sheet.TexPanner + SKY_MAX_NUM_STAGE, TexPanner); std::copy(sheet.OffsetFactor, sheet.OffsetFactor + SKY_MAX_NUM_STAGE, OffsetFactor); for(uint k = 0; k < SKY_MAX_NUM_FX_USER_PARAMS; ++k) { if (!sheet.FXUserParamBitmap[k].empty()) { bool alreadyBuilt; FXUserParams[k] = buildSharedBitmap(sheet.FXUserParamBitmap[k], bitmapByName, builtBitmaps, alreadyBuilt); } } Name = sheet.ShapeName; VisibleInMainScene = visibleInMainScene; VisibleInEnvMap = visibleInEnvMap; } // ************************************************************************************************* bool CSkyObject::setup(const CClientDate &date, const CClientDate &animationDate, float numHoursInDay, float weatherLevel, CRGBA fogColor, bool envMapScene) { if (Instance.empty()) return false; Active = true; nlassert(numHoursInDay > 0.f); float dayPart = date.Hour / numHoursInDay; clamp(dayPart, 0.f, 1.f); clamp(weatherLevel, 0.f, 1.f); if (DiffuseColor.Mode != Unused) { CRGBA newDiffuseColor = DiffuseColor.computeColor(dayPart, weatherLevel, fogColor); if (newDiffuseColor != LastDiffuseColor) { // is it a particle system if (!PS.empty()) { PS.setUserColor(newDiffuseColor); // PS are hiden / shown, so must unfreeze hrc. (adding an isntance group causes hrc to be frozen) PS.unfreezeHRC(); } else { // set diffuse color for each material with normal shader uint numMaterials = Instance.getNumMaterials(); for(uint k = 0; k < numMaterials; ++k) { UInstanceMaterial im = Instance.getMaterial(k); if (im.isLighted()) { Instance.getMaterial(k).setDiffuse(newDiffuseColor); } else { Instance.getMaterial(k).setColor(newDiffuseColor); } } // set mean color for other objects (lens flares...) Instance.setMeanColor(newDiffuseColor); } LastDiffuseColor = newDiffuseColor; } if (RefColor == DiffuseColorRef) // if this is the ref color, then the object is not visible if alpha is 0 { if (newDiffuseColor.A == 0) { Active = false; } } } // is it a particle system if (ParticleEmitters.Mode != Unused) { CRGBA newParticleEmittersColor = ParticleEmitters.computeColor(dayPart, weatherLevel, fogColor); if (newParticleEmittersColor != LastParticleEmittersColor) { if (!PS.empty()) { // emitters are on is any of the components is not 0 PS.activateEmitters(newParticleEmittersColor != CRGBA::Black); } LastParticleEmittersColor = newParticleEmittersColor; } if (RefColor == ParticleEmittersColorRef) // if this is the ref color, then the object is not visible if alpha is 0 { if (LastParticleEmittersColor == CRGBA::Black) { if (!PS.hasParticles()) // can deactivate PS only when all particles are off { Active = false; } } } } uint numMaterials = Instance.getNumMaterials(); for(uint k = 0; k < SKY_MAX_NUM_STAGE; ++k) { if (ConstantColor[k].Mode != Unused) { CRGBA newConstantColor = ConstantColor[k].computeColor(dayPart, weatherLevel, fogColor); if (newConstantColor != LastConstantColor[k]) { for(uint l = 0; l < numMaterials; ++l) { Instance.getMaterial(l).setConstantColor(k, newConstantColor); } LastConstantColor[k] = newConstantColor; } if (RefColor == (TSkyRefColor) (ConstantColor0Ref + k)) { if (newConstantColor.A == 0) { Active = false; } } } } bool draw = Active; if (envMapScene && !VisibleInEnvMap) { draw = false; } else if (!envMapScene && !VisibleInMainScene) { draw = false; } if (draw) { Instance.show(); Instance.unfreezeHRC(); } else { Instance.hide(); Instance.freezeHRC(); } double animTime = animationDate.Hour + (double) animationDate.Day * (double) numHoursInDay; if (PS.empty()) { //////////////////// // gradient setup // //////////////////// ColorGradient.setup(Instance, dayPart, weatherLevel, GradientCache, GradientCacheBlurred); /////////////////////// // tex panning setup // /////////////////////// for(uint k = 0; k < SKY_MAX_NUM_STAGE; ++k) { if (TexPanner[k].U != 0.f || TexPanner[k].V != 0.f || OffsetUBitmap[k] != NULL || OffsetVBitmap[k] != NULL ) { //nlinfo("global date = %f", animTime); // there's tex panning for that stage double u = TexPanner[k].U * animTime; u = fmod(u, 1); double v = TexPanner[k].V * animTime; v = fmod(v, 1); CVector offset((float) u, (float) v, 0.f); // apply scaling if needed if (OffsetUBitmap[k]) { offset.x += OffsetFactor[k].U * OffsetUBitmap[k]->getColor(dayPart, weatherLevel, true, false).R; } if (OffsetVBitmap[k]) { offset.y += OffsetFactor[k].V * OffsetVBitmap[k]->getColor(dayPart, weatherLevel, true, false).R; } CMatrix mat; mat.setPos(offset); for(uint l = 0; l < numMaterials; ++l) { Instance.getMaterial(l).enableUserTexMat(k); Instance.getMaterial(l).setUserTexMat(k, mat); } } } } else { // user params setup for(uint k = 0; k < SKY_MAX_NUM_FX_USER_PARAMS; ++k) { if (FXUserParams[k]) { CRGBA color = FXUserParams[k]->getColor(dayPart, weatherLevel, true, false); PS.setUserParam(k, color.R / 255.f); } } } return Active; } // ************************************************************************************************* CSkyObject::~CSkyObject() { }