//----------------------------------------------------------------------------- // 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 "environment/sun.h" #include "gfx/bitmap/gBitmap.h" #include "math/mathIO.h" #include "core/stream/bitStream.h" #include "console/consoleTypes.h" #include "console/engineAPI.h" #include "scene/sceneManager.h" #include "math/mathUtils.h" #include "lighting/lightInfo.h" #include "lighting/lightManager.h" #include "scene/sceneRenderState.h" #include "renderInstance/renderPassManager.h" #include "sim/netConnection.h" #include "environment/timeOfDay.h" #include "gfx/gfxTransformSaver.h" #include "materials/materialManager.h" #include "materials/baseMatInstance.h" #include "materials/sceneData.h" #include "math/util/matrixSet.h" IMPLEMENT_CO_NETOBJECT_V1(Sun); ConsoleDocClass( Sun, "@brief A global light affecting your entire scene and optionally renders a corona effect.\n\n" "Sun is both the directional and ambient light for your entire scene.\n\n" "@ingroup Atmosphere" ); //----------------------------------------------------------------------------- Sun::Sun() { mNetFlags.set(Ghostable | ScopeAlways); mTypeMask = EnvironmentObjectType | LightObjectType | StaticObjectType; mLightColor.set(0.7f, 0.7f, 0.7f); mLightAmbient.set(0.3f, 0.3f, 0.3f); mBrightness = 1.0f; mSunAzimuth = 0.0f; mSunElevation = 35.0f; mCastShadows = true; mStaticRefreshFreq = 250; mDynamicRefreshFreq = 8; mAnimateSun = false; mTotalTime = 0.0f; mCurrTime = 0.0f; mStartAzimuth = 0.0f; mEndAzimuth = 0.0f; mStartElevation = 0.0f; mEndElevation = 0.0f; mLight = LightManager::createLightInfo(); mLight->setType( LightInfo::Vector ); mFlareData = NULL; mFlareState.clear(); mFlareScale = 1.0f; mCoronaEnabled = true; mCoronaScale = 0.5f; mCoronaTint.set( 1.0f, 1.0f, 1.0f, 1.0f ); mCoronaUseLightColor = true; mCoronaMatInst = NULL; INIT_ASSET(CoronaMaterial); mMatrixSet = reinterpret_cast(dMalloc_aligned(sizeof(MatrixSet), 16)); constructInPlace(mMatrixSet); mCoronaWorldRadius = 0.0f; mLightWorldPos = Point3F::Zero; } Sun::~Sun() { SAFE_DELETE( mLight ); SAFE_DELETE( mCoronaMatInst ); dFree_aligned(mMatrixSet); } bool Sun::onAdd() { if ( !Parent::onAdd() ) return false; // Register as listener to TimeOfDay update events TimeOfDay::getTimeOfDayUpdateSignal().notify( this, &Sun::_updateTimeOfDay ); // Make this thing have a global bounds so that its // always returned from spatial light queries. setGlobalBounds(); resetWorldBox(); setRenderTransform( mObjToWorld ); addToScene(); _initCorona(); // Update the light parameters. _conformLights(); setProcessTick( true ); return true; } void Sun::onRemove() { TimeOfDay::getTimeOfDayUpdateSignal().remove( this, &Sun::_updateTimeOfDay ); removeFromScene(); Parent::onRemove(); } void Sun::initPersistFields() { addGroup( "Orbit" ); addField( "azimuth", TypeF32, Offset( mSunAzimuth, Sun ), "The horizontal angle of the sun measured clockwise from the positive Y world axis." ); addField( "elevation", TypeF32, Offset( mSunElevation, Sun ), "The elevation angle of the sun above or below the horizon." ); 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( "color", TypeColorF, Offset( mLightColor, Sun ), "Color shading applied to surfaces in direct contact with light source."); addField( "ambient", TypeColorF, Offset( mLightAmbient, Sun ), "Color shading applied to surfaces not " "in direct contact with light source, such as in the shadows or interiors."); addField( "brightness", TypeF32, Offset( mBrightness, Sun ), "Adjust the Sun's global contrast/intensity"); addField( "castShadows", TypeBool, Offset( mCastShadows, Sun ), "Enables/disables shadows cast by objects due to Sun light"); //addField("staticRefreshFreq", TypeS32, Offset(mStaticRefreshFreq, Sun), "static shadow refresh rate (milliseconds)"); //addField("dynamicRefreshFreq", TypeS32, Offset(mDynamicRefreshFreq, Sun), "dynamic shadow refresh rate (milliseconds)"); endGroup( "Lighting" ); addGroup( "Corona" ); addField( "coronaEnabled", TypeBool, Offset( mCoronaEnabled, Sun ), "Enable or disable rendering of the corona sprite." ); INITPERSISTFIELD_MATERIALASSET(CoronaMaterial, Sun, "Material for the corona sprite."); addField( "coronaScale", TypeF32, Offset( mCoronaScale, Sun ), "Controls size the corona sprite renders, specified as a fractional amount of the screen height." ); addField( "coronaTint", TypeColorF, Offset( mCoronaTint, Sun ), "Modulates the corona sprite color ( if coronaUseLightColor is false )." ); addField( "coronaUseLightColor", TypeBool, Offset( mCoronaUseLightColor, Sun ), "Modulate the corona sprite color by the color of the light ( overrides coronaTint )." ); endGroup( "Corona" ); addGroup( "Misc" ); addField( "flareType", TYPEID< LightFlareData >(), Offset( mFlareData, Sun ), "Datablock for the flare produced by the Sun" ); addField( "flareScale", TypeF32, Offset( mFlareScale, Sun ), "Changes the size and intensity of the flare." ); endGroup( "Misc" ); // Now inject any light manager specific fields. LightManager::initLightFields(); Parent::initPersistFields(); } void Sun::inspectPostApply() { _conformLights(); setMaskBits(UpdateMask); } U32 Sun::packUpdate(NetConnection *conn, U32 mask, BitStream *stream ) { U32 retMask = Parent::packUpdate( conn, mask, stream ); if ( stream->writeFlag( mask & UpdateMask ) ) { stream->write( mSunAzimuth ); stream->write( mSunElevation ); stream->write( mLightColor ); stream->write( mLightAmbient ); 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( mCoronaEnabled ); PACK_ASSET(conn, CoronaMaterial); stream->write( mCoronaScale ); stream->write( mCoronaTint ); stream->writeFlag( mCoronaUseLightColor ); mLight->packExtended( stream ); } return retMask; } void Sun::unpackUpdate( NetConnection *conn, BitStream *stream ) { Parent::unpackUpdate( conn, stream ); if ( stream->readFlag() ) // UpdateMask { stream->read( &mSunAzimuth ); stream->read( &mSunElevation ); stream->read( &mLightColor ); stream->read( &mLightAmbient ); 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 { conn->setLastError( "Sun::unpackUpdate() - invalid LightFlareData!" ); mFlareData = NULL; } } else mFlareData = NULL; mCoronaEnabled = stream->readFlag(); UNPACK_ASSET(conn, CoronaMaterial); stream->read( &mCoronaScale ); stream->read( &mCoronaTint ); mCoronaUseLightColor = stream->readFlag(); mLight->unpackExtended( stream ); } if ( isProperlyAdded() ) { _initCorona(); _conformLights(); } } void Sun::submitLights( LightManager *lm, bool staticLighting ) { // The sun is a special light and needs special registration. lm->setSpecialLight( LightManager::slSunLightType, mLight ); } void Sun::advanceTime( F32 timeDelta ) { if (mAnimateSun) { if (mCurrTime >= mTotalTime) { mAnimateSun = false; mCurrTime = 0.0f; } else { mCurrTime += timeDelta; F32 fract = mCurrTime / mTotalTime; F32 inverse = 1.0f - fract; F32 newAzimuth = mStartAzimuth * inverse + mEndAzimuth * fract; F32 newElevation = mStartElevation * inverse + mEndElevation * fract; if (newAzimuth > 360.0f) newAzimuth -= 360.0f; if (newElevation > 360.0f) newElevation -= 360.0f; setAzimuth(newAzimuth); setElevation(newElevation); } } } void Sun::prepRenderImage( SceneRenderState *state ) { // Only render into diffuse and reflect passes. if( !state->isDiffusePass() && !state->isReflectPass() ) return; mLightWorldPos = state->getCameraPosition() - state->getFarPlane() * mLight->getDirection() * 0.9f; F32 dist = ( mLightWorldPos - state->getCameraPosition() ).len(); F32 screenRadius = GFX->getViewport().extent.y * mCoronaScale * 0.5f; mCoronaWorldRadius = screenRadius * dist / state->getWorldToScreenScale().y; // Render instance for Corona effect. if ( mCoronaEnabled && mCoronaMatInst ) { mMatrixSet->setSceneProjection( GFX->getProjectionMatrix() ); mMatrixSet->setSceneView( GFX->getViewMatrix() ); mMatrixSet->setWorld( GFX->getWorldMatrix() ); ObjectRenderInst *ri = state->getRenderPass()->allocInst(); ri->renderDelegate.bind( this, &Sun::_renderCorona ); ri->type = RenderPassManager::RIT_Sky; // Render after sky objects and before CloudLayer! ri->defaultKey = 5; ri->defaultKey2 = 0; state->getRenderPass()->addInst( ri ); } // LightFlareData handles rendering flare effects. if ( mFlareData ) { mFlareState.fullBrightness = mBrightness; mFlareState.scale = mFlareScale; mFlareState.lightInfo = mLight; mFlareState.worldRadius = mCoronaWorldRadius; mFlareState.lightMat.identity(); mFlareState.lightMat.setPosition( mLightWorldPos ); mFlareData->prepRender( state, &mFlareState ); } } void Sun::setAzimuth( F32 azimuth ) { mSunAzimuth = azimuth; _conformLights(); setMaskBits( UpdateMask ); // TODO: Break out the masks to save bandwidth! } void Sun::setElevation( F32 elevation ) { mSunElevation = elevation; _conformLights(); setMaskBits( UpdateMask ); // TODO: Break out the masks to save some space! } void Sun::setColor( const LinearColorF &color ) { mLightColor = color; _conformLights(); setMaskBits( UpdateMask ); // TODO: Break out the masks to save some space! } void Sun::animate( F32 duration, F32 startAzimuth, F32 endAzimuth, F32 startElevation, F32 endElevation ) { mAnimateSun = true; mCurrTime = 0.0f; mTotalTime = duration; mStartAzimuth = startAzimuth; mEndAzimuth = endAzimuth; mStartElevation = startElevation; mEndElevation = endElevation; } void Sun::_conformLights() { // Build the light direction from the azimuth and elevation. F32 yaw = mDegToRad(mClampF(mSunAzimuth,0,359)); F32 pitch = mDegToRad(mClampF(mSunElevation,-360,+360)); VectorF lightDirection; MathUtils::getVectorFromAngles(lightDirection, yaw, pitch); lightDirection.normalize(); mLight->setDirection( -lightDirection ); mLight->setBrightness( mBrightness ); // Now make sure the colors are within range. mLightColor.clamp(); mLight->setColor( mLightColor ); mLightAmbient.clamp(); mLight->setAmbient( mLightAmbient ); // Optimization... disable shadows if the ambient and // directional color are the same. bool castShadows = mLightColor != mLightAmbient && mCastShadows; mLight->setCastShadows( castShadows ); mLight->setStaticRefreshFreq(mStaticRefreshFreq); mLight->setDynamicRefreshFreq(mDynamicRefreshFreq); } void Sun::_initCorona() { if ( isServerObject() ) return; SAFE_DELETE( mCoronaMatInst ); if (mCoronaMaterialAsset.notNull()) { mCoronaMatInst = MATMGR->createMatInstance(mCoronaMaterialAsset->getMaterialDefinitionName(), MATMGR->getDefaultFeatures(), getGFXVertexFormat()); } } void Sun::_renderCorona( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat ) { // 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 = mCoronaWorldRadius; 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] += mLightWorldPos; } LinearColorF vertColor; if ( mCoronaUseLightColor ) vertColor = mLightColor; else vertColor = mCoronaTint; 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( vertColor.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 ( mCoronaMatInst->setupPass( state, sgData ) ) { mCoronaMatInst->setTransforms( *mMatrixSet, state ); mCoronaMatInst->setSceneInfo( state, sgData ); GFX->setVertexBuffer( vb ); GFX->drawPrimitive( GFXTriangleStrip, 0, 2 ); } } void Sun::_updateTimeOfDay( TimeOfDay *timeOfDay, F32 time ) { setElevation( timeOfDay->getElevationDegrees() ); setAzimuth( timeOfDay->getAzimuthDegrees() ); } void Sun::_onSelected() { #ifdef TORQUE_DEBUG // Enable debug rendering on the light. if( isClientObject() ) mLight->enableDebugRendering( true ); #endif Parent::_onSelected(); } void Sun::_onUnselected() { #ifdef TORQUE_DEBUG // Disable debug rendering on the light. if( isClientObject() ) mLight->enableDebugRendering( false ); #endif Parent::_onUnselected(); } DefineEngineMethod(Sun, apply, void, (), , "") { object->inspectPostApply(); } DefineEngineMethod(Sun, animate, void, ( F32 duration, F32 startAzimuth, F32 endAzimuth, F32 startElevation, F32 endElevation ), , "animate( F32 duration, F32 startAzimuth, F32 endAzimuth, F32 startElevation, F32 endElevation )") { object->animate(duration, startAzimuth, endAzimuth, startElevation, endElevation); }