//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "scatterSky.h" #include "core/stream/bitStream.h" #include "console/consoleTypes.h" #include "console/engineAPI.h" #include "sim/netConnection.h" #include "math/util/sphereMesh.h" #include "math/mathUtils.h" #include "math/util/matrixSet.h" #include "scene/sceneRenderState.h" #include "lighting/lightInfo.h" #include "gfx/sim/gfxStateBlockData.h" #include "gfx/gfxTransformSaver.h" #include "gfx/gfxDrawUtil.h" #include "gfx/sim/cubemapData.h" #include "materials/shaderData.h" #include "materials/materialManager.h" #include "materials/baseMatInstance.h" #include "materials/sceneData.h" #include "environment/timeOfDay.h" ConsoleDocClass( ScatterSky, "@brief Represents both the sun and sky for scenes with a dynamic time of day.\n\n" "%ScatterSky renders as a dome shaped mesh which is camera relative and always overhead. " "It is intended to be part of the background of your scene and renders before all " "other objects types.\n\n" "%ScatterSky is designed for outdoor scenes which need to transition fluidly " "between radically different times of day. It will respond to time changes " "originating from a TimeOfDay object or the elevation field can be directly " "adjusted.\n\n" "During day, %ScatterSky uses atmosphereic sunlight scattering " "aproximations to generate a sky gradient and sun corona. It also calculates " "the fog color, ambient color, and sun color, which are used for scene " "lighting. This is user controlled by fields within the ScatterSky group.\n\n" "During night, %ScatterSky supports can transition to a night sky cubemap and " "moon sprite. The user can control this and night time colors used for scene " "lighting with fields within the Night group.\n\n" "A scene with a ScatterSky should not have any other sky or sun objects " "as it already fulfills both roles.\n\n" "%ScatterSky is intended to be used with CloudLayer and TimeOfDay as part of " "a scene with dynamic lighting. Having a %ScatterSky without a changing " "time of day would unnecessarily give up artistic control compared and fillrate " "compared to a SkyBox + Sun setup.\n\n" "@ingroup Atmosphere" ); IMPLEMENT_CO_NETOBJECT_V1(ScatterSky); const F32 ScatterSky::smEarthRadius = (6378.0f * 1000.0f); const F32 ScatterSky::smAtmosphereRadius = 200000.0f; const F32 ScatterSky::smViewerHeight = 1.0f; ScatterSky::ScatterSky() { mPrimCount = 0; mVertCount = 0; // Rayleigh scattering constant. mRayleighScattering = 0.0035f; mRayleighScattering4PI = mRayleighScattering * 4.0f * M_PI_F; // Mie scattering constant. mMieScattering = 0.0045f; mMieScattering4PI = mMieScattering * 4.0f * M_PI_F; // Overall scatter scalar. mSkyBrightness = 25.0f; // The Mie phase asymmetry factor. mMiePhaseAssymetry = -0.75f; mSphereInnerRadius = 1.0f; mSphereOuterRadius = 1.0f * 1.025f; mScale = 1.0f / (mSphereOuterRadius - mSphereInnerRadius); // 650 nm for red // 570 nm for green // 475 nm for blue mWavelength.set( 0.650f, 0.570f, 0.475f, 0 ); mWavelength4[0] = mPow(mWavelength[0], 4.0f); mWavelength4[1] = mPow(mWavelength[1], 4.0f); mWavelength4[2] = mPow(mWavelength[2], 4.0f); mRayleighScaleDepth = 0.25f; mMieScaleDepth = 0.1f; mAmbientColor.set( 0, 0, 0, 1.0f ); mAmbientScale.set( 1.0f, 1.0f, 1.0f, 1.0f ); mSunColor.set( 0, 0, 0, 1.0f ); mSunScale = LinearColorF::WHITE; mFogColor.set( 0, 0, 0, 1.0f ); mFogScale = LinearColorF::WHITE; mExposure = 1.0f; mNightInterpolant = 0; mZOffset = 0.0f; mShader = NULL; mTimeOfDay = 0; mSunAzimuth = 0.0f; mSunElevation = 35.0f; mMoonAzimuth = 0.0f; mMoonElevation = 45.0f; mBrightness = 1.0f; mCastShadows = true; mStaticRefreshFreq = 8; mDynamicRefreshFreq = 8; mDirty = true; mLight = LightManager::createLightInfo(); mLight->setType( LightInfo::Vector ); mFlareData = NULL; mFlareState.clear(); mFlareScale = 1.0f; mMoonEnabled = true; mMoonScale = 0.2f; mMoonTint.set( 0.192157f, 0.192157f, 0.192157f, 1.0f ); MathUtils::getVectorFromAngles( mMoonLightDir, 0.0f, 45.0f ); mMoonLightDir.normalize(); mMoonLightDir = -mMoonLightDir; mNightCubemap = NULL; mNightColor.set( 0.0196078f, 0.0117647f, 0.109804f, 1.0f ); mNightFogColor = mNightColor; mUseNightCubemap = false; mNightCubemapName = StringTable->EmptyString(); mSunSize = 1.0f; INIT_ASSET(MoonMat); mMoonMatInst = NULL; mNetFlags.set( Ghostable | ScopeAlways ); mTypeMask |= EnvironmentObjectType | LightObjectType | StaticObjectType; _generateSkyPoints(); mMatrixSet = reinterpret_cast(dMalloc_aligned(sizeof(MatrixSet), 16)); constructInPlace(mMatrixSet); mColorizeAmt = 0; mColorize.set(0,0,0); } ScatterSky::~ScatterSky() { SAFE_DELETE( mLight ); SAFE_DELETE( mMoonMatInst ); dFree_aligned(mMatrixSet); } bool ScatterSky::onAdd() { PROFILE_SCOPE(ScatterSky_onAdd); // onNewDatablock for the server is called here // for the client it is called in unpackUpdate if ( !Parent::onAdd() ) return false; if ( isClientObject() ) TimeOfDay::getTimeOfDayUpdateSignal().notify( this, &ScatterSky::_updateTimeOfDay ); setGlobalBounds(); resetWorldBox(); addToScene(); if ( isClientObject() ) { _initMoon(); Sim::findObject( mNightCubemapName, mNightCubemap ); } return true; } void ScatterSky::onRemove() { removeFromScene(); if ( isClientObject() ) TimeOfDay::getTimeOfDayUpdateSignal().remove( this, &ScatterSky::_updateTimeOfDay ); Parent::onRemove(); } void ScatterSky::_conformLights() { _initCurves(); F32 val = mCurves[0].getVal( mTimeOfDay ); mNightInterpolant = 1.0f - val; VectorF lightDirection; F32 brightness; // Build the light direction from the azimuth and elevation. F32 yaw = mDegToRad(mClampF(mSunAzimuth,0,359)); F32 pitch = mDegToRad(mClampF(mSunElevation,-360,+360)); MathUtils::getVectorFromAngles(lightDirection, yaw, pitch); lightDirection.normalize(); mSunDir = -lightDirection; yaw = mDegToRad(mClampF(mMoonAzimuth,0,359)); pitch = mDegToRad(mClampF(mMoonElevation,-360,+360)); MathUtils::getVectorFromAngles( mMoonLightDir, yaw, pitch ); mMoonLightDir.normalize(); mMoonLightDir = -mMoonLightDir; brightness = mCurves[2].getVal( mTimeOfDay ); if ( mNightInterpolant >= 1.0f ) lightDirection = -mMoonLightDir; mLight->setDirection( -lightDirection ); mLight->setBrightness( brightness * mBrightness ); mLightDir = lightDirection; // Have to do interpolation // after the light direction is set // otherwise the sun color will be invalid. _interpolateColors(); mLight->setAmbient( mAmbientColor ); mLight->setColor( mSunColor ); mLight->setCastShadows( mCastShadows ); mLight->setStaticRefreshFreq(mStaticRefreshFreq); mLight->setDynamicRefreshFreq(mDynamicRefreshFreq); FogData fog = getSceneManager()->getFogData(); fog.color = mFogColor; getSceneManager()->setFogData( fog ); } void ScatterSky::submitLights( LightManager *lm, bool staticLighting ) { if ( mDirty ) { _conformLights(); mDirty = false; } // The sun is a special light and needs special registration. lm->setSpecialLight( LightManager::slSunLightType, mLight ); } void ScatterSky::setAzimuth( F32 azimuth ) { mSunAzimuth = azimuth; mDirty = true; setMaskBits( TimeMask ); } void ScatterSky::setElevation( F32 elevation ) { mSunElevation = elevation; while( elevation < 0 ) elevation += 360.0f; while( elevation >= 360.0f ) elevation -= 360.0f; mTimeOfDay = elevation / 180.0f; mDirty = true; setMaskBits( TimeMask ); } void ScatterSky::inspectPostApply() { mDirty = true; setMaskBits( 0xFFFFFFFF ); } void ScatterSky::initPersistFields() { addGroup( "ScatterSky", "Only azimuth and elevation are networked fields. To trigger a full update of all other fields use the applyChanges ConsoleMethod." ); addField( "skyBrightness", TypeF32, Offset( mSkyBrightness, ScatterSky ), "Global brightness and intensity applied to the sky and objects in the level." ); addField( "sunSize", TypeF32, Offset( mSunSize, ScatterSky ), "Affects the size of the sun's disk." ); addField( "colorizeAmount", TypeF32, Offset( mColorizeAmt, ScatterSky ), "Controls how much the alpha component of colorize brigthens the sky. Setting to 0 returns default behavior." ); addField( "colorize", TypeColorF, Offset( mColorize, ScatterSky ), "Tints the sky the color specified, the alpha controls the brigthness. The brightness is multipled by the value of colorizeAmt." ); addField( "rayleighScattering", TypeF32, Offset( mRayleighScattering, ScatterSky ), "Controls how blue the atmosphere is during the day." ); addField( "sunScale", TypeColorF, Offset( mSunScale, ScatterSky ), "Modulates the directional color of sunlight." ); addField( "ambientScale", TypeColorF, Offset( mAmbientScale, ScatterSky ), "Modulates the ambient color of sunlight." ); addField( "fogScale", TypeColorF, Offset( mFogScale, ScatterSky ), "Modulates the fog color. Note that this overrides the LevelInfo.fogColor " "property, so you should not use LevelInfo.fogColor if the level contains " "a ScatterSky object." ); addField( "exposure", TypeF32, Offset( mExposure, ScatterSky ), "Controls the contrast of the sky and sun during daytime." ); addField( "zOffset", TypeF32, Offset( mZOffset, ScatterSky ), "Offsets the scatterSky to avoid canvas rendering. Use 5000 or greater for the initial adjustment" ); endGroup( "ScatterSky" ); addGroup( "Orbit" ); addProtectedField( "azimuth", TypeF32, Offset( mSunAzimuth, ScatterSky ), &ScatterSky::ptSetAzimuth, &defaultProtectedGetFn, "The horizontal angle of the sun measured clockwise from the positive Y world axis. This field is networked." ); addProtectedField( "elevation", TypeF32, Offset( mSunElevation, ScatterSky ), &ScatterSky::ptSetElevation, &defaultProtectedGetFn, "The elevation angle of the sun above or below the horizon. This field is networked." ); addField( "moonAzimuth", TypeF32, Offset( mMoonAzimuth, ScatterSky ), "The horizontal angle of the moon measured clockwise from the positive Y world axis. This is not animated by time or networked." ); addField( "moonElevation", TypeF32, Offset( mMoonElevation, ScatterSky ), "The elevation angle of the moon above or below the horizon. This is not animated by time or networked." ); endGroup( "Orbit" ); // We only add the basic lighting options that all lighting // systems would use... the specific lighting system options // are injected at runtime by the lighting system itself. addGroup( "Lighting" ); addField( "castShadows", TypeBool, Offset( mCastShadows, ScatterSky ), "Enables/disables shadows cast by objects due to ScatterSky light." ); addField("staticRefreshFreq", TypeS32, Offset(mStaticRefreshFreq, ScatterSky), "static shadow refresh rate (milliseconds)"); addField("dynamicRefreshFreq", TypeS32, Offset(mDynamicRefreshFreq, ScatterSky), "dynamic shadow refresh rate (milliseconds)"); addField( "brightness", TypeF32, Offset( mBrightness, ScatterSky ), "The brightness of the ScatterSky's light object." ); endGroup( "Lighting" ); addGroup( "Misc" ); addField( "flareType", TYPEID< LightFlareData >(), Offset( mFlareData, ScatterSky ), "Datablock for the flare produced by the ScatterSky." ); addField( "flareScale", TypeF32, Offset( mFlareScale, ScatterSky ), "Changes the size and intensity of the flare." ); endGroup( "Misc" ); addGroup( "Night" ); addField( "nightColor", TypeColorF, Offset( mNightColor, ScatterSky ), "The ambient color during night. Also used for the sky color if useNightCubemap is false." ); addField( "nightFogColor", TypeColorF, Offset( mNightFogColor, ScatterSky ), "The fog color during night." ); addField( "moonEnabled", TypeBool, Offset( mMoonEnabled, ScatterSky ), "Enable or disable rendering of the moon sprite during night." ); INITPERSISTFIELD_MATERIALASSET(MoonMat, ScatterSky, "Material for the moon sprite."); addField( "moonScale", TypeF32, Offset( mMoonScale, ScatterSky ), "Controls size the moon sprite renders, specified as a fractional amount of the screen height." ); addField( "moonLightColor", TypeColorF, Offset( mMoonTint, ScatterSky ), "Color of light cast by the directional light during night." ); addField( "useNightCubemap", TypeBool, Offset( mUseNightCubemap, ScatterSky ), "Transition to the nightCubemap during night. If false we use nightColor." ); addField( "nightCubemap", TypeCubemapName, Offset( mNightCubemapName, ScatterSky ), "Cubemap visible during night." ); endGroup( "Night" ); // Now inject any light manager specific fields. LightManager::initLightFields(); Parent::initPersistFields(); } U32 ScatterSky::packUpdate(NetConnection *con, U32 mask, BitStream *stream) { U32 retMask = Parent::packUpdate(con, mask, stream); if ( stream->writeFlag( mask & TimeMask ) ) { stream->write( mSunAzimuth ); stream->write( mSunElevation ); } if ( stream->writeFlag( mask & UpdateMask ) ) { stream->write( mRayleighScattering ); mRayleighScattering4PI = mRayleighScattering * 4.0f * M_PI_F; stream->write( mRayleighScattering4PI ); stream->write( mMieScattering ); mMieScattering4PI = mMieScattering * 4.0f * M_PI_F; stream->write( mMieScattering4PI ); stream->write( mSunSize ); stream->write( mSkyBrightness ); stream->write( mMiePhaseAssymetry ); stream->write( mSphereInnerRadius ); stream->write( mSphereOuterRadius ); stream->write( mScale ); stream->write( mWavelength ); stream->write( mWavelength4[0] ); stream->write( mWavelength4[1] ); stream->write( mWavelength4[2] ); stream->write( mRayleighScaleDepth ); stream->write( mMieScaleDepth ); stream->write( mNightColor ); stream->write( mNightFogColor ); stream->write( mAmbientScale ); stream->write( mSunScale ); stream->write( mFogScale ); stream->write( mColorizeAmt ); stream->write( mColorize ); stream->write( mExposure ); stream->write( mZOffset ); stream->write( mBrightness ); stream->writeFlag( mCastShadows ); stream->write(mStaticRefreshFreq); stream->write(mDynamicRefreshFreq); stream->write( mFlareScale ); if ( stream->writeFlag( mFlareData ) ) { stream->writeRangedU32( mFlareData->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); } stream->writeFlag( mMoonEnabled ); PACK_ASSET(con, MoonMat); stream->write( mMoonScale ); stream->write( mMoonTint ); stream->writeFlag( mUseNightCubemap ); stream->writeString( mNightCubemapName ); stream->write( mMoonAzimuth ); stream->write( mMoonElevation ); mLight->packExtended( stream ); } return retMask; } void ScatterSky::unpackUpdate(NetConnection *con, BitStream *stream) { Parent::unpackUpdate(con, stream); if ( stream->readFlag() ) // TimeMask { F32 temp = 0; stream->read( &temp ); setAzimuth( temp ); stream->read( &temp ); setElevation( temp ); } if ( stream->readFlag() ) // UpdateMask { stream->read( &mRayleighScattering ); stream->read( &mRayleighScattering4PI ); stream->read( &mMieScattering ); stream->read( &mMieScattering4PI ); stream->read( &mSunSize ); stream->read( &mSkyBrightness ); stream->read( &mMiePhaseAssymetry ); stream->read( &mSphereInnerRadius ); stream->read( &mSphereOuterRadius ); stream->read( &mScale ); LinearColorF tmpColor( 0, 0, 0 ); stream->read( &tmpColor ); stream->read( &mWavelength4[0] ); stream->read( &mWavelength4[1] ); stream->read( &mWavelength4[2] ); stream->read( &mRayleighScaleDepth ); stream->read( &mMieScaleDepth ); stream->read( &mNightColor ); stream->read( &mNightFogColor ); stream->read( &mAmbientScale ); stream->read( &mSunScale ); stream->read( &mFogScale ); F32 colorizeAmt; stream->read( &colorizeAmt ); if(mColorizeAmt != colorizeAmt) { mColorizeAmt = colorizeAmt; mShader = NULL; //forces shader refresh } stream->read( &mColorize ); if ( tmpColor != mWavelength ) { mWavelength = tmpColor; mWavelength4[0] = mPow(mWavelength[0], 4.0f); mWavelength4[1] = mPow(mWavelength[1], 4.0f); mWavelength4[2] = mPow(mWavelength[2], 4.0f); } stream->read( &mExposure ); stream->read( &mZOffset ); stream->read( &mBrightness ); mCastShadows = stream->readFlag(); stream->read(&mStaticRefreshFreq); stream->read(&mDynamicRefreshFreq); stream->read( &mFlareScale ); if ( stream->readFlag() ) { SimObjectId id = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); LightFlareData *datablock = NULL; if ( Sim::findObject( id, datablock ) ) mFlareData = datablock; else { con->setLastError( "ScatterSky::unpackUpdate() - invalid LightFlareData!" ); mFlareData = NULL; } } else mFlareData = NULL; mMoonEnabled = stream->readFlag(); UNPACK_ASSET(con, MoonMat); stream->read( &mMoonScale ); stream->read( &mMoonTint ); mUseNightCubemap = stream->readFlag(); mNightCubemapName = stream->readSTString(); stream->read( &mMoonAzimuth ); stream->read( &mMoonElevation ); mLight->unpackExtended( stream ); if ( isProperlyAdded() ) { mDirty = true; _initMoon(); Sim::findObject( mNightCubemapName, mNightCubemap ); } } } void ScatterSky::prepRenderImage( SceneRenderState *state ) { // Only render into diffuse and reflect passes. if( !state->isDiffusePass() && !state->isReflectPass() ) return; // Regular sky render instance. RenderPassManager* renderPass = state->getRenderPass(); ObjectRenderInst *ri = renderPass->allocInst(); ri->renderDelegate.bind( this, &ScatterSky::_render ); ri->type = RenderPassManager::RIT_Sky; ri->defaultKey = 15; ri->defaultKey2 = 0; renderPass->addInst(ri); // Debug render instance. /* if ( Con::getBoolVariable( "$ScatterSky::debug", false ) ) { ri = state->getRenderPass()->allocInst(); ri->renderDelegate.bind( this, &ScatterSky::_debugRender ); ri->type = RenderPassManager::RIT_Editor; state->getRenderPass()->addInst( ri ); } */ // Light flare effect render instance. if ( mFlareData && mNightInterpolant != 1.0f ) { mFlareState.fullBrightness = mBrightness; mFlareState.scale = mFlareScale; mFlareState.lightInfo = mLight; Point3F lightPos = state->getDiffuseCameraPosition() - state->getFarPlane() * mLight->getDirection() * 0.9f; mFlareState.lightMat.identity(); mFlareState.lightMat.setPosition( lightPos ); F32 dist = ( lightPos - state->getDiffuseCameraPosition( ) ).len( ); F32 coronaScale = 0.5f; F32 screenRadius = GFX->getViewport( ).extent.y * coronaScale * 0.5f; mFlareState.worldRadius = screenRadius * dist / state->getWorldToScreenScale( ).y; mFlareData->prepRender( state, &mFlareState ); } // Render instances for Night effects. if ( mNightInterpolant <= 0.0f ) return; // Render instance for Moon sprite. if ( mMoonEnabled && mMoonMatInst ) { mMatrixSet->setSceneView(GFX->getWorldMatrix()); mMatrixSet->setSceneProjection(GFX->getProjectionMatrix()); mMatrixSet->setWorld(GFX->getWorldMatrix()); ObjectRenderInst *moonRI = renderPass->allocInst(); moonRI->renderDelegate.bind( this, &ScatterSky::_renderMoon ); moonRI->type = RenderPassManager::RIT_Sky; // Render after sky objects and before CloudLayer! moonRI->defaultKey = 10; moonRI->defaultKey2 = 0; renderPass->addInst(moonRI); } } bool ScatterSky::_initShader() { ShaderData *shaderData; if ( !Sim::findObject( "ScatterSkyShaderData", shaderData ) ) { Con::warnf( "ScatterSky::_initShader - failed to locate shader ScatterSkyShaderData!" ); return false; } Vector macros; if ( mColorizeAmt ) macros.push_back( GFXShaderMacro( "USE_COLORIZE" ) ); mShader = shaderData->getShader( macros ); if ( !mShader ) return false; if ( mStateBlock.isNull() ) { GFXStateBlockData *data = NULL; if ( !Sim::findObject( "ScatterSkySBData", data ) ) Con::warnf( "ScatterSky::_initShader - failed to locate ScatterSkySBData!" ); else mStateBlock = GFX->createStateBlock( data->getState() ); } if ( !mStateBlock ) return false; mShaderConsts = mShader->allocConstBuffer(); mModelViewProjSC = mShader->getShaderConstHandle( "$modelView" ); // Camera height, cam height squared, scale and scale over depth. mMiscSC = mShader->getShaderConstHandle( "$misc" ); // Inner and out radius, and inner and outer radius squared. mSphereRadiiSC = mShader->getShaderConstHandle( "$sphereRadii" ); // Rayleigh sun brightness, mie sun brightness and 4 * PI * coefficients. mScatteringCoefficientsSC = mShader->getShaderConstHandle( "$scatteringCoeffs" ); mCamPosSC = mShader->getShaderConstHandle( "$camPos" ); mLightDirSC = mShader->getShaderConstHandle( "$lightDir" ); mSunDirSC = mShader->getShaderConstHandle( "$sunDir" ); mNightColorSC = mShader->getShaderConstHandle( "$nightColor" ); mInverseWavelengthSC = mShader->getShaderConstHandle( "$invWaveLength" ); mNightInterpolantAndExposureSC = mShader->getShaderConstHandle( "$nightInterpAndExposure" ); mUseCubemapSC = mShader->getShaderConstHandle( "$useCubemap" ); mColorizeSC = mShader->getShaderConstHandle( "$colorize" ); return true; } void ScatterSky::clearVectors() { tmpVertices.clear(); vertsVec.clear(); } void ScatterSky::addVertex(Point3F vert) { vertsVec.push_back(vert.x); vertsVec.push_back(vert.y); vertsVec.push_back(vert.z); } void ScatterSky::BuildFinalVert() { U32 count = vertsVec.size(); U32 i, j; for (i = 0, j = 0; i < count; i += 3, j += 2) { FinalVertexData temp; temp.pos.set(Point3F(vertsVec[i], vertsVec[i + 1], vertsVec[i + 2])); finalVertData.push_back(temp); } } void ScatterSky::_initVBIB() { U32 rings = 18; U32 height = 9; U32 radius = 10; F32 x, y, z, xy; // vertex position F32 ringStep = M_2PI / rings; F32 heightStep = M_HALFPI / height; // M_PI for full sphere. F32 ringAng, heightAng; //clear vecs clearVectors(); for (U32 i = 0; i <= height; ++i) { heightAng = M_PI / 2 - (F32)i * heightStep; xy = radius * mCos(heightAng); z = radius * mSin(heightAng); for (U32 j = 0; j <= rings; ++j) { SphereVertex vert; ringAng = j * ringStep; x = xy * mCos(ringAng); y = xy * mSin(ringAng); vert.pos.set(Point3F(x, y, z)); tmpVertices.push_back(vert); } } SphereVertex v1, v2, v3, v4; U32 vi1, vi2 = 0; for (U32 i = 0; i < height; ++i) { vi1 = i * (rings + 1); vi2 = (i + 1) * (rings + 1); for (U32 j = 0; j < rings; ++j, ++vi1, ++vi2) { v1 = tmpVertices[vi1]; v2 = tmpVertices[vi2]; v3 = tmpVertices[vi1 + 1]; v4 = tmpVertices[vi2 + 1]; // 1st = triangle. if (i == 0) { // verts for tri. addVertex(v1.pos); addVertex(v2.pos); addVertex(v4.pos); } /* UNCOMMENT WHEN FULL SPHERE else if (i == (height - 1)) { // verts for tri. addVertex(v1.pos); addVertex(v2.pos); addVertex(v3.pos); }*/ else { // verts for quad. addVertex(v1.pos); addVertex(v2.pos); addVertex(v3.pos); addVertex(v3.pos); addVertex(v4.pos); addVertex(v2.pos); } } } BuildFinalVert(); // Vertex Buffer... mVertCount = finalVertData.size(); mPrimCount = mVertCount / 3; mVB.set( GFX, mVertCount, GFXBufferTypeStatic ); GFXVertexP *pVert = mVB.lock(); if(!pVert) return; for ( U32 i = 0; i < mVertCount; i++ ) { pVert->point.set(finalVertData[i].pos); pVert->point.normalize(); pVert->point *= 200000.0f; pVert++; } mVB.unlock(); // Primitive Buffer... mPrimBuffer.set( GFX, mVertCount, mPrimCount, GFXBufferTypeStatic ); U16 *pIdx = NULL; mPrimBuffer.lock(&pIdx); U32 curIdx = 0; for ( U32 i = 0, k = 0; i < mPrimCount; i++, k+=3 ) { pIdx[curIdx] = k; curIdx++; pIdx[curIdx] = k + 1; curIdx++; pIdx[curIdx] = k + 2; curIdx++; } mPrimBuffer.unlock(); } void ScatterSky::_initMoon() { if ( isServerObject() ) return; if ( mMoonMatInst ) SAFE_DELETE( mMoonMatInst ); if (mMoonMatAsset.notNull()) { mMoonMatInst = MATMGR->createMatInstance(mMoonMatAsset->getMaterialDefinitionName(), MATMGR->getDefaultFeatures(), getGFXVertexFormat()); } } void ScatterSky::_initCurves() { if ( mCurves->getSampleCount() > 0 ) return; // Takes time of day (0-2) and returns // the night interpolant (0-1) day/night factor. // moonlight = 0, sunlight > 0 mCurves[0].clear(); mCurves[0].addPoint( 0.0f, 0.5f );// Sunrise mCurves[0].addPoint( 0.025f, 1.0f );// mCurves[0].addPoint( 0.975f, 1.0f );// mCurves[0].addPoint( 1.0f, 0.5f );//Sunset mCurves[0].addPoint( 1.02f, 0.0f );//Sunlight ends mCurves[0].addPoint( 1.98f, 0.0f );//Sunlight begins mCurves[0].addPoint( 2.0f, 0.5f );// Sunrise // Takes time of day (0-2) and returns mieScattering factor // Regulates the size of the sun's disk mCurves[1].clear(); mCurves[1].addPoint( 0.0f, 0.0006f ); mCurves[1].addPoint( 0.01f, 0.00035f ); mCurves[1].addPoint( 0.03f, 0.00023f ); mCurves[1].addPoint( 0.1f, 0.00022f ); mCurves[1].addPoint( 0.2f, 0.00043f ); mCurves[1].addPoint( 0.3f, 0.00062f ); mCurves[1].addPoint( 0.4f, 0.0008f ); mCurves[1].addPoint( 0.5f, 0.00086f );// High noon mCurves[1].addPoint( 0.6f, 0.0008f ); mCurves[1].addPoint( 0.7f, 0.00062f ); mCurves[1].addPoint( 0.8f, 0.00043f ); mCurves[1].addPoint( 0.9f, 0.00022f ); mCurves[1].addPoint( 0.97f, 0.00023f ); mCurves[1].addPoint( 0.99f, 0.00035f ); mCurves[1].addPoint( 1.0f, 0.0006f ); mCurves[1].addPoint( 2.0f, 0.0006f ); // Takes time of day and returns brightness // Controls sunlight and moonlight brightness mCurves[2].clear(); mCurves[2].addPoint( 0.0f, 0.2f );// Sunrise mCurves[2].addPoint( 0.1f, 1.0f ); mCurves[2].addPoint( 0.9f, 1.0f );// Sunset mCurves[2].addPoint( 1.008f, 0.0f );//Adjust end of sun's reflection mCurves[2].addPoint( 1.02001f, 0.0f ); mCurves[2].addPoint( 1.05f, 0.5f );// Turn brightness up for moonlight mCurves[2].addPoint( 1.93f, 0.5f ); mCurves[2].addPoint( 1.97999f, 0.0f );// No brightness when sunlight starts mCurves[2].addPoint( 1.992f, 0.0f );//Adjust start of sun's reflection mCurves[2].addPoint( 2.0f, 0.2f ); // Sunrise // Interpolation of day/night color sets // 0/1 ambient/nightcolor // 0 = day colors only anytime // 1 = night colors only anytime // between 0 and 1 renders both color sets anytime mCurves[3].clear(); mCurves[3].addPoint( 0.0f, 0.8f );//Sunrise mCurves[3].addPoint( 0.1f, 0.0f ); mCurves[3].addPoint( 0.99f, 0.0f ); mCurves[3].addPoint( 1.0f, 0.8f );// Sunset mCurves[3].addPoint( 1.01999f, 1.0f );// mCurves[3].addPoint( 1.98001f, 1.0f );// Sunlight begins with full night colors mCurves[3].addPoint( 2.0f, 0.8f ); //Sunrise // Takes time of day (0-2) and returns smoothing factor // Interpolates between mMoonTint color and mNightColor mCurves[4].clear(); mCurves[4].addPoint( 0.0f, 1.0f ); mCurves[4].addPoint( 0.96f, 1.0f ); mCurves[4].addPoint( 1.01999f, 0.5f ); mCurves[4].addPoint( 1.02001f, 0.5f ); mCurves[4].addPoint( 1.08f, 1.0f ); mCurves[4].addPoint( 1.92f, 1.0f ); mCurves[4].addPoint( 1.97999f, 0.5f ); mCurves[4].addPoint( 1.98001f, 0.5f ); mCurves[4].addPoint( 2.0f, 1.0f ); } void ScatterSky::_updateTimeOfDay( TimeOfDay *timeOfDay, F32 time ) { setElevation( timeOfDay->getElevationDegrees() ); setAzimuth( timeOfDay->getAzimuthDegrees() ); } void ScatterSky::_render( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat ) { if ( overrideMat || (!mShader && !_initShader()) ) return; GFXTransformSaver saver; if ( mVB.isNull() || mPrimBuffer.isNull() ) _initVBIB(); GFX->setShader( mShader ); GFX->setShaderConstBuffer( mShaderConsts ); Point4F sphereRadii( mSphereOuterRadius, mSphereOuterRadius * mSphereOuterRadius, mSphereInnerRadius, mSphereInnerRadius * mSphereInnerRadius ); Point4F scatteringCoeffs( mRayleighScattering * mSkyBrightness, mRayleighScattering4PI, mMieScattering * mSkyBrightness, mMieScattering4PI ); Point4F invWavelength( 1.0f / mWavelength4[0], 1.0f / mWavelength4[1], 1.0f / mWavelength4[2], 1.0f ); Point3F camPos( 0, 0, smViewerHeight ); Point4F miscParams( camPos.z, camPos.z * camPos.z, mScale, mScale / mRayleighScaleDepth ); Frustum frust = state->getCameraFrustum(); frust.setFarDist( smEarthRadius + smAtmosphereRadius ); MatrixF proj( true ); frust.getProjectionMatrix( &proj ); Point3F camPos2 = state->getCameraPosition(); MatrixF xfm(true); xfm.setPosition(Point3F( camPos2.x, camPos2.y, mZOffset) ); GFX->multWorld(xfm); MatrixF xform(proj);//GFX->getProjectionMatrix()); xform *= GFX->getViewMatrix(); xform *= GFX->getWorldMatrix(); mShaderConsts->setSafe( mModelViewProjSC, xform ); mShaderConsts->setSafe( mMiscSC, miscParams ); mShaderConsts->setSafe( mSphereRadiiSC, sphereRadii ); mShaderConsts->setSafe( mScatteringCoefficientsSC, scatteringCoeffs ); mShaderConsts->setSafe( mCamPosSC, camPos ); mShaderConsts->setSafe( mLightDirSC, mLightDir ); mShaderConsts->setSafe( mSunDirSC, mSunDir ); mShaderConsts->setSafe( mNightColorSC, mNightColor ); mShaderConsts->setSafe( mInverseWavelengthSC, invWavelength ); mShaderConsts->setSafe( mNightInterpolantAndExposureSC, Point2F( mExposure, mNightInterpolant ) ); mShaderConsts->setSafe( mColorizeSC, mColorize*mColorizeAmt ); if ( GFXDevice::getWireframe() ) { GFXStateBlockDesc desc( mStateBlock->getDesc() ); desc.setFillModeWireframe(); GFX->setStateBlockByDesc( desc ); } else GFX->setStateBlock( mStateBlock ); if ( mUseNightCubemap && mNightCubemap ) { mShaderConsts->setSafe( mUseCubemapSC, 1.0f ); if ( !mNightCubemap->mCubemap ) mNightCubemap->createMap(); GFX->setCubeTexture( 0, mNightCubemap->mCubemap ); } else { GFX->setCubeTexture( 0, NULL ); mShaderConsts->setSafe( mUseCubemapSC, 0.0f ); } GFX->setPrimitiveBuffer( mPrimBuffer ); GFX->setVertexBuffer( mVB ); GFX->drawIndexedPrimitive( GFXTriangleList, 0, 0, mVertCount, 0, mPrimCount ); } void ScatterSky::_debugRender( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat ) { GFXStateBlockDesc desc; desc.fillMode = GFXFillSolid; desc.setBlend( false, GFXBlendOne, GFXBlendZero ); desc.setZReadWrite( false, false ); GFXStateBlockRef sb = GFX->GFX->createStateBlock( desc ); GFX->setStateBlock( sb ); PrimBuild::begin( GFXLineStrip, mSkyPoints.size() ); PrimBuild::color3i( 255, 0, 255 ); for ( U32 i = 0; i < mSkyPoints.size(); i++ ) { Point3F pnt = mSkyPoints[i]; pnt.normalize(); pnt *= 500; pnt += state->getCameraPosition(); PrimBuild::vertex3fv( pnt ); } PrimBuild::end(); } void ScatterSky::_renderMoon( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat ) { if ( !mMoonMatInst ) return; Point3F moonlightPosition = state->getCameraPosition() - /*mLight->getDirection()*/ mMoonLightDir * state->getFarPlane() * 0.9f; F32 dist = (moonlightPosition - state->getCameraPosition()).len(); // worldRadius = screenRadius * dist / worldToScreen // screenRadius = worldRadius / dist * worldToScreen // F32 screenRadius = GFX->getViewport().extent.y * mMoonScale * 0.5f; F32 worldRadius = screenRadius * dist / state->getWorldToScreenScale().y; // Calculate Billboard Radius (in world units) to be constant, independent of distance. // Takes into account distance, viewport size, and specified size in editor F32 BBRadius = worldRadius; mMatrixSet->restoreSceneViewProjection(); if ( state->isReflectPass() ) mMatrixSet->setProjection( state->getSceneManager()->getNonClipProjection() ); mMatrixSet->setWorld( MatrixF::Identity ); // Initialize points with basic info Point3F points[4]; points[0] = Point3F( -BBRadius, 0.0, -BBRadius); points[1] = Point3F( -BBRadius, 0.0, BBRadius); points[2] = Point3F( BBRadius, 0.0, -BBRadius); points[3] = Point3F( BBRadius, 0.0, BBRadius); static const Point2F sCoords[4] = { Point2F( 0.0f, 0.0f ), Point2F( 0.0f, 1.0f ), Point2F( 1.0f, 0.0f ), Point2F( 1.0f, 1.0f ) }; // Get info we need to adjust points const MatrixF &camView = state->getCameraTransform(); // Finalize points for(S32 i = 0; i < 4; i++) { // align with camera camView.mulV(points[i]); // offset points[i] += moonlightPosition; } // Vertex color. LinearColorF moonVertColor( 1.0f, 1.0f, 1.0f, mNightInterpolant ); // Copy points to buffer. GFXVertexBufferHandle< GFXVertexPCT > vb; vb.set( GFX, 4, GFXBufferTypeVolatile ); GFXVertexPCT *pVert = vb.lock(); if(!pVert) return; for ( S32 i = 0; i < 4; i++ ) { pVert->color.set( moonVertColor.toColorI()); pVert->point.set( points[i] ); pVert->texCoord.set( sCoords[i].x, sCoords[i].y ); pVert++; } vb.unlock(); // Setup SceneData struct. SceneData sgData; sgData.wireframe = GFXDevice::getWireframe(); sgData.visibility = 1.0f; // Draw it while ( mMoonMatInst->setupPass( state, sgData ) ) { mMoonMatInst->setTransforms( *mMatrixSet, state ); mMoonMatInst->setSceneInfo( state, sgData ); GFX->setVertexBuffer( vb ); GFX->drawPrimitive( GFXTriangleStrip, 0, 2 ); } } void ScatterSky::_generateSkyPoints() { U32 rings=60, segments=20;//rings=160, segments=20; Point3F tmpPoint( 0, 0, 0 ); // Establish constants used in sphere generation. F32 deltaRingAngle = ( M_PI_F / (F32)(rings * 2) ); F32 deltaSegAngle = ( 2.0f * M_PI_F / (F32)segments ); // Generate the group of rings for the sphere. for( S32 ring = 0; ring < 2; ring++ ) { F32 r0 = mSin( ring * deltaRingAngle ); F32 y0 = mCos( ring * deltaRingAngle ); // Generate the group of segments for the current ring. for( S32 seg = 0; seg < segments + 1 ; seg++ ) { F32 x0 = r0 * sinf( seg * deltaSegAngle ); F32 z0 = r0 * cosf( seg * deltaSegAngle ); tmpPoint.set( x0, z0, y0 ); tmpPoint.normalizeSafe(); tmpPoint.x *= smEarthRadius + smAtmosphereRadius; tmpPoint.y *= smEarthRadius + smAtmosphereRadius; tmpPoint.z *= smEarthRadius + smAtmosphereRadius; tmpPoint.z -= smEarthRadius; if ( ring == 1 ) mSkyPoints.push_back( tmpPoint ); } } } void ScatterSky::_interpolateColors() { mFogColor.set( 0, 0, 0, 0 ); mAmbientColor.set( 0, 0, 0, 0 ); mSunColor.set( 0, 0, 0, 0 ); _getFogColor( &mFogColor ); _getAmbientColor( &mAmbientColor ); _getSunColor( &mSunColor ); mAmbientColor *= mAmbientScale; mSunColor *= mSunScale; mFogColor *= mFogScale; mMieScattering = (mCurves[1].getVal( mTimeOfDay) * mSunSize ); //Scale the size of the sun's disk LinearColorF moonTemp = mMoonTint; LinearColorF nightTemp = mNightColor; moonTemp.interpolate( mNightColor, mMoonTint, mCurves[4].getVal( mTimeOfDay ) ); nightTemp.interpolate( mMoonTint, mNightColor, mCurves[4].getVal( mTimeOfDay ) ); mFogColor.interpolate( mFogColor, mNightFogColor, mCurves[3].getVal( mTimeOfDay ) );//mNightInterpolant ); mFogColor.alpha = 1.0f; mAmbientColor.interpolate( mAmbientColor, mNightColor, mCurves[3].getVal( mTimeOfDay ) );//mNightInterpolant ); mSunColor.interpolate( mSunColor, mMoonTint, mCurves[3].getVal( mTimeOfDay ) );//mNightInterpolant ); } void ScatterSky::_getSunColor( LinearColorF *outColor ) { PROFILE_SCOPE( ScatterSky_GetSunColor ); U32 count = 0; LinearColorF tmpColor( 0, 0, 0 ); VectorF tmpVec( 0, 0, 0 ); tmpVec = mLightDir; tmpVec.x *= smEarthRadius + smAtmosphereRadius; tmpVec.y *= smEarthRadius + smAtmosphereRadius; tmpVec.z *= smEarthRadius + smAtmosphereRadius; tmpVec.z -= smAtmosphereRadius; for ( U32 i = 0; i < 10; i++ ) { _getColor( tmpVec, &tmpColor ); (*outColor) += tmpColor; tmpVec.x += (smEarthRadius * 0.5f) + (smAtmosphereRadius * 0.5f); count++; } if ( count > 0 ) (*outColor) /= count; } void ScatterSky::_getAmbientColor( LinearColorF *outColor ) { PROFILE_SCOPE( ScatterSky_GetAmbientColor ); LinearColorF tmpColor( 0, 0, 0, 0 ); U32 count = 0; // Disable mieScattering for purposes of calculating the ambient color. F32 oldMieScattering = mMieScattering; mMieScattering = 0.0f; for ( U32 i = 0; i < mSkyPoints.size(); i++ ) { Point3F pnt( mSkyPoints[i] ); _getColor( pnt, &tmpColor ); (*outColor) += tmpColor; count++; } if ( count > 0 ) (*outColor) /= count; mMieScattering = oldMieScattering; } void ScatterSky::_getFogColor( LinearColorF *outColor ) { PROFILE_SCOPE( ScatterSky_GetFogColor ); VectorF scatterPos( 0, 0, 0 ); F32 sunBrightness = mSkyBrightness; mSkyBrightness *= 0.25f; F32 yaw = 0, pitch = 0, originalYaw = 0; VectorF fwd( 0, 1.0f, 0 ); MathUtils::getAnglesFromVector( fwd, yaw, pitch ); originalYaw = yaw; pitch = mDegToRad( 10.0f ); LinearColorF tmpColor( 0, 0, 0 ); U32 i = 0; for ( i = 0; i < 10; i++ ) { MathUtils::getVectorFromAngles( scatterPos, yaw, pitch ); scatterPos.x *= smEarthRadius + smAtmosphereRadius; scatterPos.y *= smEarthRadius + smAtmosphereRadius; scatterPos.z *= smEarthRadius + smAtmosphereRadius; scatterPos.y -= smEarthRadius; _getColor( scatterPos, &tmpColor ); (*outColor) += tmpColor; if ( i <= 5 ) yaw += mDegToRad( 5.0f ); else { originalYaw += mDegToRad( -5.0f ); yaw = originalYaw; } yaw = mFmod( yaw, M_2PI_F ); } if ( i > 0 ) (*outColor) /= i; mSkyBrightness = sunBrightness; } F32 ScatterSky::_vernierScale( F32 fCos ) { F32 x = 1.0 - fCos; return 0.25f * exp( -0.00287f + x * (0.459f + x * (3.83f + x * ((-6.80f + (x * 5.25f))))) ); } F32 ScatterSky::_getMiePhase( F32 fCos, F32 fCos2, F32 g, F32 g2) { return 1.5f * ((1.0f - g2) / (2.0f + g2)) * (1.0f + fCos2) / mPow(mFabs(1.0f + g2 - 2.0f*g*fCos), 1.5f); } F32 ScatterSky::_getRayleighPhase( F32 fCos2 ) { return 0.75 + 0.75 * fCos2; } void ScatterSky::_getColor( const Point3F &pos, LinearColorF *outColor ) { PROFILE_SCOPE( ScatterSky_GetColor ); F32 scaleOverScaleDepth = mScale / mRayleighScaleDepth; F32 rayleighBrightness = mRayleighScattering * mSkyBrightness; F32 mieBrightness = mMieScattering * mSkyBrightness; Point3F invWaveLength( 1.0f / mWavelength4[0], 1.0f / mWavelength4[1], 1.0f / mWavelength4[2] ); Point3F v3Pos = pos / 6378000.0f; v3Pos.z += mSphereInnerRadius; Point3F newCamPos( 0, 0, smViewerHeight ); VectorF v3Ray = v3Pos - newCamPos; F32 fFar = v3Ray.len(); v3Ray / fFar; v3Ray.normalizeSafe(); Point3F v3Start = newCamPos; F32 fDepth = mExp( scaleOverScaleDepth * (mSphereInnerRadius - smViewerHeight ) ); F32 fStartAngle = mDot( v3Ray, v3Start ); F32 fStartOffset = fDepth * _vernierScale( fStartAngle ); F32 fSampleLength = fFar / 2.0f; F32 fScaledLength = fSampleLength * mScale; VectorF v3SampleRay = v3Ray * fSampleLength; Point3F v3SamplePoint = v3Start + v3SampleRay * 0.5f; Point3F v3FrontColor( 0, 0, 0 ); for ( U32 i = 0; i < 2; i++ ) { F32 fHeight = v3SamplePoint.len(); fDepth = mExp( scaleOverScaleDepth * (mSphereInnerRadius - smViewerHeight) ); F32 fLightAngle = mDot( mLightDir, v3SamplePoint ) / fHeight; F32 fCameraAngle = mDot( v3Ray, v3SamplePoint ) / fHeight; F32 fScatter = (fStartOffset + fDepth * ( _vernierScale( fLightAngle ) - _vernierScale( fCameraAngle ) )); Point3F v3Attenuate( 0, 0, 0 ); F32 tmp = mExp( -fScatter * (invWaveLength[0] * mRayleighScattering4PI + mMieScattering4PI) ); v3Attenuate.x = tmp; tmp = mExp( -fScatter * (invWaveLength[1] * mRayleighScattering4PI + mMieScattering4PI) ); v3Attenuate.y = tmp; tmp = mExp( -fScatter * (invWaveLength[2] * mRayleighScattering4PI + mMieScattering4PI) ); v3Attenuate.z = tmp; v3FrontColor += v3Attenuate * (fDepth * fScaledLength); v3SamplePoint += v3SampleRay; } Point3F mieColor = v3FrontColor * mieBrightness; Point3F rayleighColor = v3FrontColor * (invWaveLength * rayleighBrightness); Point3F v3Direction = newCamPos - v3Pos; v3Direction.normalize(); F32 fCos = mDot( mLightDir, v3Direction ) / v3Direction.len(); F32 fCos2 = fCos * fCos; F32 g = -0.991f; F32 g2 = g * g; F32 miePhase = _getMiePhase( fCos, fCos2, g, g2 ); Point3F color = rayleighColor + (miePhase * mieColor); LinearColorF tmp( color.x, color.y, color.z, color.y ); Point3F expColor( 0, 0, 0 ); expColor.x = 1.0f - exp(-mExposure * color.x); expColor.y = 1.0f - exp(-mExposure * color.y); expColor.z = 1.0f - exp(-mExposure * color.z); tmp.set( expColor.x, expColor.y, expColor.z, 1.0f ); if ( !tmp.isClamped() ) { F32 len = expColor.len(); if ( len > 0 ) expColor /= len; } outColor->set( expColor.x, expColor.y, expColor.z, 1.0f ); } // Static protected field set methods bool ScatterSky::ptSetElevation( void *object, const char *index, const char *data ) { ScatterSky *sky = static_cast( object ); F32 val = dAtof( data ); sky->setElevation( val ); // we already set the field return false; } bool ScatterSky::ptSetAzimuth( void *object, const char *index, const char *data ) { ScatterSky *sky = static_cast( object ); F32 val = dAtof( data ); sky->setAzimuth( val ); // we already set the field return false; } void ScatterSky::_onSelected() { #ifdef TORQUE_DEBUG // Enable debug rendering on the light. if( isClientObject() ) mLight->enableDebugRendering( true ); #endif Parent::_onSelected(); } void ScatterSky::_onUnselected() { #ifdef TORQUE_DEBUG // Disable debug rendering on the light. if( isClientObject() ) mLight->enableDebugRendering( false ); #endif Parent::_onUnselected(); } // ConsoleMethods DefineEngineMethod( ScatterSky, applyChanges, void, (),, "Apply a full network update of all fields to all clients." ) { object->inspectPostApply(); }