//----------------------------------------------------------------------------- // V12 Engine // // Copyright (c) 2001 GarageGames.Com // Portions Copyright (c) 2001 by Sierra Online, Inc. //----------------------------------------------------------------------------- #include "terrain/waterBlock.h" #include "console/consoleTypes.h" #include "sceneGraph/sceneGraph.h" #include "sceneGraph/sceneState.h" #include "Core/bitStream.h" #include "Math/mBox.h" #include "dgl/dgl.h" #include "Core/color.h" #include "terrain/terrData.h" #include "terrain/terrRender.h" #include "Math/mathIO.h" #include "sceneGraph/sgUtil.h" #include "audio/audioDataBlock.h" //============================================================================== IMPLEMENT_CO_NETOBJECT_V1(WaterBlock); //============================================================================== bool WaterBlock::mCameraSubmerged = false; U32 WaterBlock::mSubmergedType = 0; TextureHandle WaterBlock::mSubmergeTexture[WC_NUM_SUBMERGE_TEX]; //============================================================================== // I know this is a bit of a hack. I've done this in order to avoid a coupling // between the fluid and the rest of the system. // static SceneState* pSceneState = NULL; static F32 FogFunction( F32 Distance, F32 DeltaZ ) { return( pSceneState->getHazeAndFog( Distance, DeltaZ ) ); } //============================================================================== WaterBlock::WaterBlock() { mNetFlags.set(Ghostable | ScopeAlways); mTypeMask = WaterObjectType; mObjBox.min.set( 0, 0, 0 ); mObjBox.max.set( 1, 1, 1 ); mLiquidType = eOceanWater; mDensity = 1; mViscosity = 15; mWaveMagnitude = 1.0f; mSurfaceTexture = TextureHandle(); mSurfaceOpacity = 0.75f; mEnvMapTexture = TextureHandle(); mEnvMapIntensity = 1.0f; mRemoveWetEdges = true; mAudioEnvironment = 0; // lets be good little programmers and initialize our data! mSurfaceName = NULL; mEnvMapName = NULL; dMemset( mSubmergeName, 0, sizeof( mSubmergeName ) ); mpTerrain = NULL; mSurfaceZ = 0.0f; mFluid.SetFogFn( FogFunction ); } //============================================================================== WaterBlock::~WaterBlock() { } //============================================================================== void WaterBlock::SnagTerrain( SceneObject* sceneObj, S32 key ) { WaterBlock* pWater = (WaterBlock*)key; pWater->mpTerrain = dynamic_cast(sceneObj); } //============================================================================== void WaterBlock::UpdateFluidRegion( void ) { MatrixF M; Point3F P; P = mObjToWorld.getPosition(); P.x += 1024.0f; P.y += 1024.0f; mSurfaceZ = P.z + mObjScale.z; mFluid.SetInfo( P.x, P.y, mObjScale.x, mObjScale.y, mSurfaceZ, mWaveMagnitude, mSurfaceOpacity, mEnvMapIntensity, mRemoveWetEdges ); P.x -= 1024.0f; P.y -= 1024.0f; M.identity(); M.setPosition( P ); Parent::setTransform( M ); resetWorldBox(); if( isServerObject() ) setMaskBits(1); } //============================================================================== bool WaterBlock::onAdd() { if( !isClientObject() ) { // Make sure that the Fluid has had a chance to tweak the values. UpdateFluidRegion(); } if( !Parent::onAdd() ) return false; if(!mRemoveWetEdges) { mObjBox.min.set(-1e4, -1e4, 0); mObjBox.max.set( 1e4, 1e4, 1); } resetWorldBox(); addToScene(); if( isClientObject() ) { // load textures mSurfaceTexture = TextureHandle( mSurfaceName, MeshTexture ); mEnvMapTexture = TextureHandle( mEnvMapName, MeshTexture ); for( int i=0; ifindObjects( mWorldBox, (U32)TerrainObjectType, SnagTerrain, (S32)this ); if( mpTerrain ) mFluid.SetTerrainData( mpTerrain->heightMap ); } return( true ); } return( false ); } //============================================================================== // The incoming matrix transforms the water into the WORLD. This includes any // offset in the terrain, so the translation can be negative. We need to get // the translation to be expressed in "terrain square" terms. The terrain // offset is always -1024,-1024. void WaterBlock::setTransform( const MatrixF &mat ) { mObjToWorld = mat; UpdateFluidRegion(); } //============================================================================== void WaterBlock::setScale( const VectorF & scale ) { mObjScale = scale; UpdateFluidRegion(); } //============================================================================== bool WaterBlock::prepRenderImage( SceneState* state, const U32 stateKey, const U32, const bool ) { // Attempt to get the terrain. if( (mpTerrain == NULL) && (mContainer != NULL) ) { mContainer->findObjects( mWorldBox, (U32)TerrainObjectType, SnagTerrain, (S32)this ); if( mpTerrain ) mFluid.SetTerrainData( mpTerrain->heightMap ); } if (isLastState(state, stateKey)) return false; setLastState(state, stateKey); // This should be sufficient for most objects that don't manage zones, and // don't need to return a specialized RenderImage... if (state->isObjectRendered(this)) { SceneRenderImage* image = new SceneRenderImage; image->obj = this; image->isTranslucent = true; image->sortType = SceneRenderImage::Plane; image->plane = PlaneF(0, 0, 1, -mSurfaceZ); image->poly[0] = Point3F(mObjBox.min.x, mObjBox.min.y, 1); image->poly[1] = Point3F(mObjBox.min.x, mObjBox.max.y, 1); image->poly[2] = Point3F(mObjBox.max.x, mObjBox.max.y, 1); image->poly[3] = Point3F(mObjBox.max.x, mObjBox.min.y, 1); for (U32 i = 0; i < 4; i++) { image->poly[i].convolve(mObjScale); getTransform().mulP(image->poly[i]); } // Calc the area of this poly Point3F intermed; mCross(image->poly[2] - image->poly[0], image->poly[3] - image->poly[1], &intermed); image->polyArea = intermed.len() * 0.5; state->insertRenderImage(image); } return false; } //============================================================================== void WaterBlock::renderObject( SceneState* state, SceneRenderImage* ) { AssertFatal( dglIsInCanonicalState(), "Error, GL not in canonical state on entry" ); RectI viewport; Point3F Eye; // Camera in water space. bool CameraSubmergedFlag = false; dglGetViewport ( &viewport ); glMatrixMode ( GL_PROJECTION ); glPushMatrix (); state->setupObjectProjection( this ); /**** // Debug assist. // Render a wire outline around the base of the water block. if( 0 ) { glMatrixMode ( GL_MODELVIEW ); glPushMatrix (); dglMultMatrix ( &mObjToWorld ); F32 X0 = 0; F32 Y0 = 0; F32 X1 = mObjScale.x; F32 Y1 = mObjScale.y; F32 Z = 0; glDisable ( GL_TEXTURE_2D ); glDisable ( GL_DEPTH_TEST ); glEnable ( GL_BLEND ); glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glPolygonMode ( GL_FRONT_AND_BACK, GL_LINE ); glColor4f ( 0.6f, 0.6f, 0.0f, 0.5f ); glBegin ( GL_QUADS ); glVertex3f ( X0, Y0, Z ); glVertex3f ( X1, Y0, Z ); glVertex3f ( X1, Y1, Z ); glVertex3f ( X0, Y1, Z ); glEnd (); glEnable ( GL_DEPTH_TEST ); glColor4f ( 0.8f, 0.8f, 0.8f, 0.8f ); glBegin ( GL_QUADS ); glVertex3f ( X0, Y0, Z ); glVertex3f ( X1, Y0, Z ); glVertex3f ( X1, Y1, Z ); glVertex3f ( X0, Y1, Z ); glEnd (); glPolygonMode ( GL_FRONT_AND_BACK, GL_FILL ); glPopMatrix (); glMatrixMode ( GL_MODELVIEW ); } ****/ // Handle the fluid. { // The water block lives in terrain space which is -1024,-1024. // To get into world space, we just add 1024,1024. // The fluid lives in world space. Point3F W2Lv( 1024.0f, 1024.0f, 0.0f ); // World to Local vector Point3F L2Wv( -1024.0f, -1024.0f, 0.0f ); // Local to World vector MatrixF L2Wm; // Local to World matrix L2Wm.identity(); L2Wm.setPosition( L2Wv ); glMatrixMode ( GL_MODELVIEW ); glPushMatrix (); dglMultMatrix ( &L2Wm ); // We need the eye in water space. { Eye = state->getCameraPosition() + W2Lv; mFluid.SetEyePosition( Eye.x, Eye.y, Eye.z ); } // We need the frustrum in water space. { MatrixF L2Cm; dglGetModelview( &L2Cm ); L2Cm.inverse(); Point3F Dummy; Dummy = L2Cm.getPosition(); F64 frustumParam[6]; dglGetFrustum(&frustumParam[0], &frustumParam[1], &frustumParam[2], &frustumParam[3], &frustumParam[4], &frustumParam[5]); sgComputeOSFrustumPlanes(frustumParam, L2Cm, Dummy, mClipPlane[1], mClipPlane[2], mClipPlane[3], mClipPlane[4], mClipPlane[5]); // near plane is needed as well... PlaneF p(0, 1, 0, -frustumParam[4]); mTransformPlane(L2Cm, Point3F(1,1,1), p, &mClipPlane[0]); // F64 left, right, bottom, top, near, far; // MatrixF L2Cm; // dglGetFrustum( &left, &right, &bottom, &top, &near, &far ); // far = state->getVisibleDistance(); // Point3F Origin ( 0, 0, 0 ); // Point3F FarOrigin ( 0, far, 0 ); // Point3F UpperLeft ( left, near, top ); // Point3F UpperRight ( right, near, top ); // Point3F LowerLeft ( left, near, bottom ); // Point3F LowerRight ( right, near, bottom ); // dglGetModelview( &L2Cm ); // L2Cm.inverse(); // Point3F Dummy; // Dummy = L2Cm.getPosition(); // L2Cm.mulP( Origin ); // L2Cm.mulP( FarOrigin ); // L2Cm.mulP( UpperLeft ); // L2Cm.mulP( UpperRight ); // L2Cm.mulP( LowerLeft ); // L2Cm.mulP( LowerRight ); // // Frustrum clip planes: 0=T 1=B 2=L 3=R 4=N 5=F // mClipPlane[0].set( UpperLeft, Origin, UpperRight ); // mClipPlane[1].set( LowerRight, Origin, LowerLeft ); // mClipPlane[2].set( LowerLeft, Origin, UpperLeft ); // mClipPlane[3].set( UpperRight, Origin, LowerRight ); // mClipPlane[4].set( UpperRight, UpperLeft, LowerRight ); // // far plane is reverse of near plane vector // mClipPlane[5].x = -mClipPlane[4].x; // mClipPlane[5].y = -mClipPlane[4].y; // mClipPlane[5].z = -mClipPlane[4].z; // mClipPlane[5].d = -mClipPlane[4].d + far; mFluid.SetFrustrumPlanes( (F32*)mClipPlane ); } // Fog stuff. { pSceneState = state; ColorF FogColor = state->getFogColor(); mFluid.SetFogParameters( FogColor.red, FogColor.green, FogColor.blue, state->getVisibleDistance() ); } // And RENDER! { mFluid.Render( CameraSubmergedFlag ); } // Clean up. glPopMatrix (); glMatrixMode ( GL_MODELVIEW ); } // // And now the closing ceremonies... // glMatrixMode ( GL_PROJECTION ); glPopMatrix (); glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); dglSetViewport ( viewport ); // // Oh yes. We have to set some state information for the scene... // if( CameraSubmergedFlag ) { mCameraSubmerged = true; mSubmergedType = mLiquidType; } AssertFatal( dglIsInCanonicalState(), "Error, GL not in canonical state on exit" ); } //============================================================================== void WaterBlock::inspectPostApply() { resetWorldBox(); setMaskBits(1); } //============================================================================== static EnumTable::Enums gLiquidTypeEnums[] = { { WaterBlock::eWater, "Water" }, { WaterBlock::eOceanWater, "OceanWater" }, { WaterBlock::eRiverWater, "RiverWater" }, { WaterBlock::eStagnantWater, "StagnantWater" }, { WaterBlock::eLava, "Lava" }, { WaterBlock::eHotLava, "HotLava" }, { WaterBlock::eCrustyLava, "CrustyLava" }, { WaterBlock::eQuicksand, "Quicksand" } }; static EnumTable gLiquidTypeTable( 8, gLiquidTypeEnums ); //------------------------------------------------------------------------------ void WaterBlock::initPersistFields() { Parent::initPersistFields(); addField( "liquidType", TypeEnum, Offset( mLiquidType, WaterBlock ), 1, &gLiquidTypeTable ); addField( "density", TypeF32, Offset( mDensity, WaterBlock ) ); addField( "viscosity", TypeF32, Offset( mViscosity, WaterBlock ) ); addField( "waveMagnitude", TypeF32, Offset( mWaveMagnitude, WaterBlock ) ); addField( "surfaceTexture", TypeString, Offset( mSurfaceName, WaterBlock ) ); addField( "surfaceOpacity", TypeF32, Offset( mSurfaceOpacity, WaterBlock ) ); addField( "envMapTexture", TypeString, Offset( mEnvMapName, WaterBlock ) ); addField( "envMapIntensity", TypeF32, Offset( mEnvMapIntensity, WaterBlock ) ); addField( "submergeTexture", TypeString, Offset( mSubmergeName, WaterBlock ), WC_NUM_SUBMERGE_TEX ); addField( "removeWetEdges", TypeBool, Offset( mRemoveWetEdges, WaterBlock ) ); addField( "audioEnvironment", TypeAudioEnvironmentPtr, Offset( mAudioEnvironment, WaterBlock ) ); } //============================================================================== void WaterBlock::cToggleWireFrame( SimObject* obj, S32, const char** ) { WaterBlock* pWaterBlock = static_cast(obj); if( pWaterBlock ) { pWaterBlock->mFluid.m_ShowWire = !(pWaterBlock->mFluid.m_ShowWire); if( pWaterBlock->mFluid.m_ShowWire ) Con::printf( "WaterBlock wire frame ENABLED" ); else Con::printf( "WaterBlock wire frame DISABLED" ); } } //============================================================================== void WaterBlock::consoleInit() { Con::addCommand( "WaterBlock", "toggleWireFrame", cToggleWireFrame, "waterBlock.toggleWireFrame()", 2, 2 ); } //============================================================================== U32 WaterBlock::packUpdate( NetConnection* c, U32 mask, BitStream* stream ) { U32 retMask = Parent::packUpdate( c, mask, stream ); // No masking in here now. // There's not too much data, and it doesn't change during normal game play. stream->writeAffineTransform( mObjToWorld ); mathWrite( *stream, mObjScale ); stream->writeString( mSurfaceName ); stream->writeString( mEnvMapName ); for( int i=0; iwriteString( mSubmergeName[i] ); } stream->write( (S32)mLiquidType ); stream->write( mDensity ); stream->write( mViscosity ); stream->write( mWaveMagnitude ); stream->write( mSurfaceOpacity ); stream->write( mEnvMapIntensity ); stream->write( mRemoveWetEdges ); // audio environment: if(stream->writeFlag(mAudioEnvironment)) stream->writeRangedU32(mAudioEnvironment->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); return( retMask ); } //============================================================================== void WaterBlock::unpackUpdate( NetConnection* c, BitStream* stream ) { Parent::unpackUpdate( c, stream ); U32 LiquidType; // No masking in here now. // There's not too much data, and it doesn't change during normal game play. stream->readAffineTransform( &mObjToWorld ); mathRead( *stream, &mObjScale ); mSurfaceName = stream->readSTString(); mEnvMapName = stream->readSTString(); for( int i=0; ireadSTString(); } stream->read( &LiquidType ); stream->read( &mDensity ); stream->read( &mViscosity ); stream->read( &mWaveMagnitude ); stream->read( &mSurfaceOpacity ); stream->read( &mEnvMapIntensity ); stream->read( &mRemoveWetEdges ); // audio environment: if(stream->readFlag()) { U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); mAudioEnvironment = dynamic_cast(Sim::findObject(profileId)); } else mAudioEnvironment = 0; mLiquidType = (EWaterType)LiquidType; UpdateFluidRegion(); if( !isProperlyAdded() ) return; resetWorldBox(); } //============================================================================== // This method can take a point in world space, or water block space. The default // assumes pos is in world space, and therefore must transform it to waterblock // space. bool WaterBlock::isPointSubmerged(const Point3F &pos, bool worldSpace) const { return( isPointSubmergedSimple( pos, worldSpace ) ); } //============================================================================== bool WaterBlock::isPointSubmergedSimple(const Point3F &pos, bool worldSpace) const { Point3F Pos = pos; if( Pos.z > mSurfaceZ ) return( false ); if( worldSpace ) { Pos.x += 1024.0f; Pos.y += 1024.0f; } return( mFluid.IsFluidAtXY( Pos.x, Pos.y ) ); } //============================================================================== bool WaterBlock::castRay( const Point3F& start, const Point3F& end, RayInfo* info ) { F32 t, x, y, X, Y; Point3F Pos; // // Looks like the incoming points are in parametric object space. Great. // // The water surface is 1.0. Bail if the ray does not cross the surface. if( (start.z > 1.0f) && (end.z > 1.0f) ) return( false ); if( (start.z < 1.0f) && (end.z < 1.0f) ) return( false ); // The ray crosses the surface plane. Find out where. t = (start.z - 1.0f) / (start.z - end.z); x = start.x + (end.x - start.x) * t; y = start.y + (end.y - start.y) * t; Pos = mObjToWorld.getPosition(); X = (x * mObjScale.x) + Pos.x + 1024.0f; Y = (y * mObjScale.y) + Pos.y + 1024.0f; if( mFluid.IsFluidAtXY( X, Y ) ) { info->t = t; info->point.x = x; info->point.y = y; info->point.z = 1.0f; info->normal.x = 0.0f; info->normal.y = 0.0f; info->normal.z = 1.0f; info->object = this; info->material = 0; return( true ); } // Hmm. Guess we missed! return( false ); } //============================================================================== bool WaterBlock::isWater( U32 liquidType ) { EWaterType wType = EWaterType( liquidType ); return( wType == eWater || wType == eOceanWater || wType == eRiverWater || wType == eStagnantWater ); } //============================================================================== bool WaterBlock::isLava( U32 liquidType ) { EWaterType wType = EWaterType( liquidType ); return( wType == eLava || wType == eHotLava || wType == eCrustyLava ); } //============================================================================== bool WaterBlock::isQuicksand( U32 liquidType ) { EWaterType wType = EWaterType( liquidType ); return( wType == eQuicksand ); } //==============================================================================