/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ ////////////////////////////////////////////////////////////////////////////// // // // hsVertexShader Class Functions // // // //// Version History ///////////////////////////////////////////////////////// // // // 5.9.2001 mcn - Updated to reflect the new (temporary) vertex color/ // // lighting model. // // // ////////////////////////////////////////////////////////////////////////////// #include "HeadSpin.h" #include "Max.h" #include "stdmat.h" #include "istdplug.h" #include "dummy.h" #include "notetrck.h" #include "../MaxMain/plMaxNode.h" #include "hsBitVector.h" #include "hsMatrix44.h" #include "hsTemplates.h" #include "../plSurface/hsGMaterial.h" #include "UserPropMgr.h" #include "hsMaxLayerBase.h" #include "hsVertexShader.h" #include "hsConverterUtils.h" #include "hsControlConverter.h" #include "hsExceptionStack.h" #include "../plSurface/hsGMaterial.h" #include "../plSurface/plLayerInterface.h" #include "../plDrawable/plGeometrySpan.h" #include "plMaxLightContext.h" #include "plRenderGlobalContext.h" #include "plLightMapGen.h" #define MF_NATIVE_MAX_LIGHT #define MF_NO_RAY_SHADOW extern UserPropMgr gUserPropMgr; extern TimeValue GetTime(Interface *gi); //=========================================================================== // hsVertexShader //=========================================================================== hsVertexShader::hsVertexShader() : fConverterUtils(hsConverterUtils::Instance()), fInterface(nil), fLightMapGen(nil), fShaded(0) { hsGuardBegin("hsVertexShader::hsVertexShader"); fLocalToWorld.Reset(); hsGuardEnd; } hsVertexShader::~hsVertexShader() { hsGuardBegin("hsVertexShader::~hsVertexShader"); hsGuardEnd; } hsVertexShader& hsVertexShader::Instance() { hsGuardBegin("hsVertexShader::Instance"); static hsVertexShader instance; return instance; hsGuardEnd; } void hsVertexShader::Open() { hsGuardBegin("hsVertexShader::InitLights"); fLocalToWorld.Reset(); fInterface = ::GetCOREInterface(); fLightMapGen = &plLightMapGen::Instance(); hsGuardEnd; } void hsVertexShader::Close() { hsGuardBegin("hsVertexShader::DeInitLights"); fLightMapGen = nil; hsGuardEnd; } //// ShadeNode /////////////////////////////////////////////////////////////// // Same as the other ShadeNode, only this shades an array of plGeometrySpans. void hsVertexShader::ShadeNode(INode* node, hsMatrix44& l2w, hsMatrix44& w2l, hsTArray &spans) { // If we're flagged for WaterColor, our vertex colors are already done. if( ((plMaxNodeBase*)node)->GetCalcEdgeLens() || node->UserPropExists("XXXWaterColor") ) return; fLightMapGen->InitNode(node); fLocalToWorld = l2w; hsMatrix44 tempMatrix = w2l; // l2w's inverse tempMatrix.GetTranspose( &fNormalToWorld ); // Inverse-transpose of the fLocalToWorld matrix, int i; for( i = 0; i < spans.GetCount(); i++ ) IShadeSpan( spans[ i ], node); fLightMapGen->DeInitNode(); fShaded++; } //// IShadeSpan ////////////////////////////////////////////////////////////// // Shades a single plGeometrySpan. // 5.9.2001 mcn - Updated to support the new (temporary) vertex color/lighting // method. void hsVertexShader::IShadeSpan( plGeometrySpan *span, INode* node ) { hsColorRGBA preDiffuse, rtDiffuse, matAmbient; hsBitVector dirtyVector; int i; hsBool translucent, shadeIt, addingIt; plLayerInterface *layer = nil; hsGuardBegin("hsVertexShader::ShadeSpan"); const char* dbgNodeName = node->GetName(); if( span->fNumVerts == 0 ) return; fShadeColorTable = TRACKED_NEW hsColorRGBA[ span->fNumVerts ]; fIllumColorTable = TRACKED_NEW hsColorRGBA[ span->fNumVerts ]; translucent = IsTranslucent( span->fMaterial ); /// Get material layer #0 addingIt = false; shadeIt = !( span->fProps & plGeometrySpan::kPropNoPreShade ); if( span->fMaterial->GetNumLayers() != 0 ) { layer = span->fMaterial->GetLayer( 0 ); if( layer->GetShadeFlags() & hsGMatState::kShadeNoShade ) shadeIt = false; if( layer->GetBlendFlags() & hsGMatState::kBlendAdd ) addingIt = true; } float opacity = 1.f; for( i = 0; i < span->fMaterial->GetNumLayers(); i++ ) { plLayerInterface* lay = span->fMaterial->GetLayer(i); if( (lay->GetBlendFlags() & hsGMatState::kBlendAlpha) && ( !i || (lay->GetMiscFlags() & hsGMatState::kMiscRestartPassHere) ) ) { opacity = span->fMaterial->GetLayer(i)->GetOpacity(); } } /// Generate color table if( shadeIt ) IShadeVertices( span, &dirtyVector, node, translucent ); else { for( i = 0; i < span->fNumVerts; i++ ) { /// This is good for the old way, but not sure about the new way. Test once new way is in again -mcn // fShadeColorTable[ i ].Set( 1, 1, 1, 1 ); // fIllumColorTable[ i ].Set( 0, 0, 0, 1 ); hsPoint3 position; hsVector3 normal; hsColorRGBA color, illum; span->ExtractVertex( i, &position, &normal, &color, &illum ); span->ExtractInitColor( i, &color, &illum ); fShadeColorTable[ i ].Set( color.r, color.g, color.b, color.a ); fIllumColorTable[ i ].Set( illum.r, illum.g, illum.b, 1 ); } } /// Get mat colors to modulate by if( layer == nil ) { preDiffuse.Set( 1, 1, 1, 1 ); rtDiffuse.Set( 1, 1, 1, 1 ); matAmbient.Set( 0, 0, 0, 0 ); } else { if( layer->GetShadeFlags() & hsGMatState::kShadeWhite ) { preDiffuse.Set( 1, 1, 1, 1 ); rtDiffuse.Set( 1, 1, 1, 1 ); matAmbient.Set( 0, 0, 0, 0 ); } else { preDiffuse = layer->GetPreshadeColor(); // This is for vertex-based lighting, which basically ignores preshading rtDiffuse = layer->GetRuntimeColor(); // This is for vertex-based lighting, which basically ignores preshading matAmbient = layer->GetAmbientColor(); matAmbient.a = 0; } preDiffuse.a = opacity; rtDiffuse.a = opacity; } #if 0 /// Multiply by the material color, and scale by opacity if we're additive blending /// Apply colors now, multiplying by the material color as we go for( i = 0; i < span->fNumVerts; i++ ) { fShadeColorTable[ i ] *= matDiffuse; fShadeColorTable[ i ] += matAmbient; fIllumColorTable[ i ] *= matDiffuse; fIllumColorTable[ i ] += matAmbient; } if( addingIt ) { for( i = 0; i < span->fNumVerts; i++ ) { float opacity = fShadeColorTable[ i ].a; fShadeColorTable[ i ] *= opacity; fIllumColorTable[ i ] *= opacity; } } #else /// Combine shade and illum together into the diffuse color if( ( span->fProps & plGeometrySpan::kLiteMask ) != plGeometrySpan::kLiteMaterial ) { /// The two vertex lighting formulas take in a vetex color pre-processed, i.e. in /// the form of: vtxColor = ( maxVtxColor * materialDiffuse + maxIllumColor ) span->fProps |= plGeometrySpan::kDiffuseFoldedIn; if( !shadeIt ) { for( i = 0; i < span->fNumVerts; i++ ) { fIllumColorTable[ i ].a = 0; fShadeColorTable[ i ] = (fShadeColorTable[ i ] * rtDiffuse) + fIllumColorTable[ i ]; fIllumColorTable[ i ].Set( 0, 0, 0, 0 ); } } else { for( i = 0; i < span->fNumVerts; i++ ) { fIllumColorTable[ i ].a = 1.f; // Following needs to be changed to allow user input vertex colors to modulate // the runtime light values. // fShadeColorTable[ i ] = fIllumColorTable[ i ] * rtDiffuse; fShadeColorTable[ i ] = fShadeColorTable[ i ] * fIllumColorTable[ i ] * rtDiffuse; fIllumColorTable[ i ].Set( 0, 0, 0, 0 ); } } } else { if( !shadeIt ) { // Not shaded, so runtime lit, so we want BLACK vertex colors for( i = 0; i < span->fNumVerts; i++ ) { fShadeColorTable[ i ].Set( 0, 0, 0, 0 ); fIllumColorTable[ i ].Set( 0, 0, 0, 0 ); } } else { for( i = 0; i < span->fNumVerts; i++ ) { fShadeColorTable[ i ] *= fIllumColorTable[ i ]; fIllumColorTable[ i ].Set( 0, 0, 0, 0 ); } } } #endif /// Loop and stuff for( i = 0; i < span->fNumVerts; i++ ) span->StuffVertex( i, fShadeColorTable + i, fIllumColorTable + i ); delete [] fShadeColorTable; delete [] fIllumColorTable; hsGuardEnd; } //// IShadeVertices ////////////////////////////////////////////////////////// // Shades an array of vertices from a plGeometrySpan. // 5.9.2001 mcn - Updated for the new lighting model. Now on runtime, we // want the following properties on each vertex: // diffuseColor = vertexColor * matDiffuse + matAmbient (including alpha) // specularColor = ( illumniation + pre-shading ) * matDiffuse + matAmbient // We do the mat modulation outside of this function, so we // just gotta make sure the two arrays get the right values. void hsVertexShader::IShadeVertices( plGeometrySpan *span, hsBitVector *dirtyVector, INode* node, hsBool translucent ) { hsGuardBegin( "hsVertexShader::IShadeVertices" ); plMaxNode* maxNode = (plMaxNode*)node; if( maxNode->CanConvert() && (nil != maxNode->GetLightMapComponent()) ) return; int index; hsPoint3 position; hsVector3 normal; hsColorRGBA color, illum; plTmpVertex3 *vertices; /// Allocate temp vertex array vertices = TRACKED_NEW plTmpVertex3[ span->fNumVerts ]; for( index = 0; index < span->fNumVerts; index++ ) { span->ExtractVertex( index, &position, &normal, &color, &illum ); span->ExtractInitColor( index, &color, &illum ); /// fShadeColorTable is the shaded portion. fIllumColorTable is the illuminated portion; /// for more and less confusing details, see above. fShadeColorTable[ index ].Set( color.r, color.g, color.b, color.a ); fIllumColorTable[ index ].Set( illum.r, illum.g, illum.b, 1 ); position = fLocalToWorld * position; normal = fNormalToWorld * normal; vertices[ index ].fLocalPos = position; vertices[ index ].fNormal = normal; vertices[ index ].fNormal.Normalize(); } const char* dbgNodeName = node->GetName(); TimeValue t = fInterface->GetTime(); Box3 bbox; node->EvalWorldState(t).obj->GetDeformBBox(t, bbox, &node->GetObjectTM(t)); plMaxLightContext ctx(bbox, t); for( index = 0; index < span->fNumVerts; index++ ) INativeShadeVtx(fIllumColorTable[index], ctx, vertices[ index ], translucent); // Delete temp arrays delete [] vertices; hsGuardEnd; } void hsVertexShader::INativeShadeVtx(hsColorRGBA& shade, plMaxLightContext& ctx, const plTmpVertex3& vtx, hsBool translucent) { ctx.SetPoint(vtx.fLocalPos, vtx.fNormal); Color color = fLightMapGen->ShadePoint(ctx); shade.r += color.r; shade.g += color.g; shade.b += color.b; // To handle two-sided translucency here, we should compute the shade on each side and sum them. if( translucent ) { ctx.SetPoint(vtx.fLocalPos, -vtx.fNormal); color = fLightMapGen->ShadePoint(ctx); shade.r += color.r; shade.g += color.g; shade.b += color.b; } } void hsVertexShader::INativeShadowVtx(hsColorRGBA& shade, plMaxLightContext& ctx, const plTmpVertex3& vtx, hsBool translucent) { ctx.SetPoint(vtx.fLocalPos, vtx.fNormal); Color color = fLightMapGen->ShadowPoint(ctx); shade.r += color.r; shade.g += color.g; shade.b += color.b; } hsBool hsVertexShader::IsTranslucent( hsGMaterial *material ) { hsGuardBegin("hsVertexShader::IsTranslucent"); if( material ) { plLayerInterface* layer = material->GetLayer(0); if( layer && ( layer->GetShadeFlags() & hsGMatState::kShadeSoftShadow ) ) { return true; } } return false; hsGuardEnd; }