//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, 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" #include "platform/profiler.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 ); mTile = true; mLiquidType = eOceanWater; mDensity = 1; mViscosity = 15; mWaveMagnitude = 1.0f; mSurfaceTexture = TextureHandle(); mSpecMaskTex = TextureHandle(); mSurfaceOpacity = 0.75f; mEnvMapOverTexture = TextureHandle(); mEnvMapUnderTexture = TextureHandle(); mEnvMapIntensity = 0.4f; mShoreTexture = TextureHandle(); mRemoveWetEdges = false; mAudioEnvironment = 0; // lets be good little programmers and initialize our data! mSurfaceName = NULL; mSpecMaskName = NULL; mEnvMapOverName = NULL; mEnvMapUnderName = NULL; mShoreName = NULL; dMemset( mSubmergeName, 0, sizeof( mSubmergeName ) ); mpTerrain = NULL; mSurfaceZ = 0.0f; mEditorApplied = false; mShoreDepth = 20.0f; mMinAlpha = 0.03f; mMaxAlpha = 1.0f; mDepthGradient = 1.0f; mUseDepthMap = true; mTessellationSurface = 50; mTessellationShore = 60; mSurfaceParallax = 0.5f; mFlowAngle = 0.0f; mFlowRate = 0.0f; mDistortGridScale = 0.1f; mDistortMagnitude = 0.05f; mDistortTime = 0.5f; mFluid.SetFogFn( FogFunction ); mSpecColor.set( 1.0, 1.0, 1.0, 1.0 ); mSpecPower = 6; mTerrainHalfSize = 1024; mTerrainSquareSize = 8; } //============================================================================== WaterBlock::~WaterBlock() { } //============================================================================== void WaterBlock::SnagTerrain( SceneObject* sceneObj, void * key ) { WaterBlock* pWater = (WaterBlock*)key; pWater->mpTerrain = dynamic_cast(sceneObj); if(pWater->mpTerrain) { pWater->mTerrainHalfSize = pWater->mpTerrain->getSquareSize() * 128.0; pWater->mTerrainSquareSize = pWater->mpTerrain->getSquareSize(); pWater->UpdateFluidRegion(); } else { pWater->mTerrainHalfSize = 1024; pWater->mTerrainSquareSize = 8; } } //============================================================================== void WaterBlock::UpdateFluidRegion( void ) { MatrixF M; Point3F P; P = mObjToWorld.getPosition(); P.x += F32(mTerrainHalfSize); P.y += F32(mTerrainHalfSize); mSurfaceZ = P.z + mObjScale.z; Point2F s(mObjScale.x, mObjScale.y); mFluid.SetInfo( P.x, P.y, s.x, s.y, mSurfaceZ, mWaveMagnitude, mSurfaceOpacity, mEnvMapIntensity, mRemoveWetEdges, mUseDepthMap, mTessellationSurface, mTessellationShore, mSurfaceParallax, mFlowAngle, mFlowRate, mDistortGridScale, mDistortMagnitude, mDistortTime, mSpecColor, mSpecPower, mTile, mTerrainHalfSize * 2, mTerrainSquareSize); P.x -= F32(mTerrainHalfSize); P.y -= F32(mTerrainHalfSize); M.identity(); M.setPosition( P ); Parent::setTransform( M ); resetWorldBox(); if( isServerObject() ) setMaskBits(1); } //============================================================================== void WaterBlock::CalculateDepthMaps(void) { // Generate Depth Textures. GenerateDepthTextures(mDepthBitmap, mDepthTexture, false); GenerateDepthTextures(mShoreDepthBitmap, mShoreDepthTexture, true); } //============================================================================== /// Generate depth textures for water interaction with terrain. /// /// What we are doing here is sample the terrain height at it's own resolution over the area of the /// defined waterblock. With these height points we can calculate an appropriate alpha value for the /// depth-map texture texel. This results in a *very* coarse alpha-map but this is resolved by passing /// the texture through an iterative blur-convolution. /// /// The resultant texture is used to alpha-blend in the surfaces and environment map using multi-texturing. /// @author Melv May void WaterBlock::GenerateDepthTextures(GBitmap* pBitmap, TextureHandle& mTexture, bool ShoreFlag) { // Is this a client object? if (isClientObject()) { // Yes, so we should have a depth-map bitmap. AssertWarn(pBitmap != NULL, "Waterblock: We should have a depth-map bitmap!"); // Do we have the terrain yet? if (mpTerrain) { F32 DepthPoint; U32 FluidPointColour; U32* pTexelBlockOutput; // Fetch the Bitmaps Data. U32* pDepthBits = (U32*)pBitmap->getAddress(0,0,0); // Calculate Fluid Min/Max Colour-Alpha. const U32 mFluidColour = 0xffffff; const U32 mMinColour = (mFluidColour + (((U32)(mMinAlpha * 255)) << 24)); const U32 mMaxColour = (mFluidColour + (((U32)(mMaxAlpha * 255)) << 24)); // Calculate the Depth Bitmap Resolutions. F32 DepthSqrResX = pBitmap->getWidth(0) / mFluid.m_SquaresInX; F32 DepthSqrResY = pBitmap->getHeight(0) / mFluid.m_SquaresInY; // Calculate the Inverse Depth Bitmap Resolutions. F32 InvDepthSqrResX = 1.0f / DepthSqrResX; F32 InvDepthSqrResY = 1.0f / DepthSqrResY; // Calculate the Depth Bitmap Texel Resolutions. F32 DepthTexResX = mObjScale.x / eDepthMapResolution; F32 DepthTexResY = mObjScale.y / eDepthMapResolution; // Calculate the Depth Bitmap Strides. U32 XStride = pBitmap->getWidth(0); U32 YStride = pBitmap->getHeight(0); // Calculate the FluidLevel. F32 FluidLevel = mSurfaceZ; F32 Height; // Fetch the Waterblock Position. Point3F TerrainPos = mObjToWorld.getPosition(); // Change Spaces. TerrainPos.x += F32(mTerrainHalfSize); TerrainPos.y += F32(mTerrainHalfSize); // Terrain Height Offsets. F32 TerrainOffsetX; F32 TerrainOffsetY = 0; // Calculate Alpha Range. F32 AlphaRange = mMaxAlpha - mMinAlpha; // Calculate depth Alpha-Texel Map. for (s32 t = 0; t < eDepthMapResolution; t++) { // Reset Terrain Height Offset X. TerrainOffsetX = 0; for (s32 s = 0; s < eDepthMapResolution; s++) { // Fetch Terrain Height at current position. if ( mpTerrain->getHeight( Point2F(TerrainPos.x + TerrainOffsetX , TerrainPos.y + TerrainOffsetY), &Height) ) { // Got a valid height so ... // Calculate Depth Point. DepthPoint = FluidLevel - Height; // Are we below the minimum depth? if (DepthPoint <= 0) { // Yes, so set to minimum colour/alpha. FluidPointColour = mMinColour; } // ... so are we below the maximum depth? else if (DepthPoint < mShoreDepth) { // Normalise Range. F32 NormRange = mClampF(DepthPoint / mShoreDepth, 0.0f, 1.0f); // Calculate Depth Alpha. F32 DepthAlpha = (AlphaRange * mPow(NormRange, 1.0f / mDepthGradient)) + mMinAlpha; // Calculate Resultant Fluid Colour/Alpha. FluidPointColour = ((U32)(DepthAlpha*255)) << 24 | mFluidColour; } // ... out of our depth range ... else { // Are we doing a shoreline depth-map? if (ShoreFlag) { // Yesm so normalise Range. F32 NormRange = mClampF((DepthPoint-mShoreDepth) / mShoreDepth, 0.0f, 1.0f); // Calculate Depth Alpha. F32 DepthAlpha = mMaxAlpha - ((AlphaRange * mPow(NormRange, 1.0f / mDepthGradient)) + mMinAlpha); // Calculate Resultant Fluid Colour/Alpha. FluidPointColour = ((U32)(255*DepthAlpha)) << 24 | mFluidColour; } else { // No so clip to maximum colour/alpha. FluidPointColour = mMaxColour; } } } else { // Eeek, empty grid square ... FluidPointColour = mFluidColour; } // Calculate Position of Texel Block Origin. pTexelBlockOutput = pDepthBits + (U32)(s + (t * YStride)); // Write Point Height to Texel. *pTexelBlockOutput = convertLEndianToHost(FluidPointColour); // Move to next Terrain Height Point. TerrainOffsetX += DepthTexResX; } // Move to next Terrain Height Point. TerrainOffsetY += DepthTexResY; } // Update the texture. mTexture.refresh(); #if 0 // -------------------------------------------------------------------------------------- // DEBUG CODE: Dump the Depth Alpha-Map as a PNG file. // -------------------------------------------------------------------------------------- // Output file. FileStream fStream; if(!fStream.open("depthmap.png", FileStream::Write)) { Con::printf("Waterblock: Failed to open debug depth-map file!"); return; } else { mDepthTexture.getBitmap()->writePNG(fStream); fStream.close(); } #endif // -------------------------------------------------------------------------------------- } } } //============================================================================== 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( isClientObject() ) { // load textures bool success = true; mSurfaceTexture = TextureHandle( mSurfaceName, MeshTexture ); mSpecMaskTex = TextureHandle( mSpecMaskName, MeshTexture ); mEnvMapOverTexture = TextureHandle( mEnvMapOverName, MeshTexture ); mEnvMapUnderTexture = TextureHandle( mEnvMapUnderName, MeshTexture ); mShoreTexture = TextureHandle( mShoreName, MeshTexture ); for( int i=0; ifindObjects( mWorldBox, (U32)TerrainObjectType, SnagTerrain, this ); if( mpTerrain ) { mFluid.SetTerrainData( mpTerrain->heightMap ); if (isClientObject()) CalculateDepthMaps(); } } 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, this ); if( mpTerrain ) { mFluid.SetTerrainData( mpTerrain->heightMap ); // MM: Calculate Depth Maps. if (isClientObject()) CalculateDepthMaps(); } } 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* ) { PROFILE_START(WaterBlock_render); 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. F32 halfTerrainSize = mTerrainHalfSize; Point3F W2Lv( halfTerrainSize, halfTerrainSize, 0.0f ); // World to Local vector Point3F L2Wv( -halfTerrainSize, -halfTerrainSize, 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]); 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" ); PROFILE_END(); } //============================================================================== void WaterBlock::inspectPostApply() { // MM: Flag Editor Applied. mEditorApplied = true; 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(); addGroup( "Debugging" ); addField( "UseDepthMask", TypeBool, Offset( mUseDepthMap, WaterBlock ) ); endGroup( "Debugging" ); addGroup("Media"); addField( "surfaceTexture", TypeFilename, Offset( mSurfaceName, WaterBlock ) ); addField( "ShoreTexture", TypeFilename, Offset( mShoreName, WaterBlock ) ); addField( "envMapOverTexture",TypeFilename, Offset( mEnvMapOverName, WaterBlock ) ); addField( "envMapUnderTexture",TypeFilename, Offset( mEnvMapUnderName, WaterBlock ) ); addField( "submergeTexture", TypeFilename, Offset( mSubmergeName, WaterBlock ), WC_NUM_SUBMERGE_TEX ); addField( "specularMaskTex", TypeFilename, Offset( mSpecMaskName, WaterBlock ) ); endGroup("Media"); addGroup("Fluid"); addField( "liquidType", TypeEnum, Offset( mLiquidType, WaterBlock ), 1, &gLiquidTypeTable ); addField( "density", TypeF32, Offset( mDensity, WaterBlock ) ); addField( "viscosity", TypeF32, Offset( mViscosity, WaterBlock ) ); endGroup("Fluid"); addGroup("Surface"); addField( "waveMagnitude", TypeF32, Offset( mWaveMagnitude, WaterBlock ) ); addField( "surfaceOpacity", TypeF32, Offset( mSurfaceOpacity, WaterBlock ) ); addField( "envMapIntensity", TypeF32, Offset( mEnvMapIntensity, WaterBlock ) ); addField( "TessSurface", TypeF32, Offset( mTessellationSurface, WaterBlock ) ); addField( "TessShore", TypeF32, Offset( mTessellationShore,WaterBlock ) ); addField( "SurfaceParallax", TypeF32, Offset( mSurfaceParallax, WaterBlock ) ); endGroup("Surface"); addGroup("Movement"); addField( "FlowAngle", TypeF32, Offset( mFlowAngle, WaterBlock ) ); addField( "FlowRate", TypeF32, Offset( mFlowRate, WaterBlock ) ); addField( "DistortGridScale", TypeF32, Offset( mDistortGridScale, WaterBlock ) ); addField( "DistortMag", TypeF32, Offset( mDistortMagnitude, WaterBlock ) ); addField( "DistortTime", TypeF32, Offset( mDistortTime, WaterBlock ) ); endGroup("Movement"); addGroup("Depth Fx"); addField( "ShoreDepth", TypeF32, Offset( mShoreDepth, WaterBlock ) ); addField( "DepthGradient", TypeF32, Offset( mDepthGradient, WaterBlock ) ); addField( "MinAlpha", TypeF32, Offset( mMinAlpha, WaterBlock ) ); addField( "MaxAlpha", TypeF32, Offset( mMaxAlpha, WaterBlock ) ); endGroup("Depth Fx"); addGroup("Misc"); addField( "tile", TypeBool, Offset(mTile, WaterBlock)); addField( "audioEnvironment", TypeAudioEnvironmentPtr, Offset( mAudioEnvironment, WaterBlock ) ); addField( "removeWetEdges", TypeBool, Offset( mRemoveWetEdges, WaterBlock ) ); addField( "specularColor", TypeColorF, Offset( mSpecColor, WaterBlock ) ); addField( "specularPower", TypeF32, Offset( mSpecPower, WaterBlock ) ); endGroup("Misc"); } //============================================================================== void WaterBlock::toggleWireFrame() { mFluid.m_ShowWire = !(mFluid.m_ShowWire); if( mFluid.m_ShowWire ) Con::printf( "WaterBlock wire frame ENABLED" ); else Con::printf( "WaterBlock wire frame DISABLED" ); } ConsoleMethod( WaterBlock, toggleWireFrame, void, 2, 2, "()") { object->toggleWireFrame(); } //============================================================================== 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->writeFlag(mTile); stream->writeAffineTransform( mObjToWorld ); mathWrite( *stream, mObjScale ); stream->writeString(mSurfaceName); stream->writeString(mSpecMaskName); stream->writeString(mEnvMapOverName); stream->writeString(mEnvMapUnderName); stream->writeString(mShoreName); 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); // MM: Write Depth-Map Controls. stream->write( mUseDepthMap ); stream->write( mShoreDepth ); stream->write( mMinAlpha ); stream->write( mMaxAlpha ); stream->write( mDepthGradient ); stream->write( mTessellationSurface ); stream->write( mTessellationShore ); stream->write( mSurfaceParallax ); stream->write( mFlowAngle ); stream->write( mFlowRate ); stream->write( mDistortGridScale ); stream->write( mDistortMagnitude ); stream->write( mDistortTime ); stream->write( mSpecColor ); stream->write( mSpecPower ); stream->writeFlag(mEditorApplied); mEditorApplied = false; 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. mTile = stream->readFlag(); stream->readAffineTransform( &mObjToWorld ); mathRead( *stream, &mObjScale ); mSurfaceName = stream->readSTString(); mSpecMaskName = stream->readSTString(); mEnvMapOverName = stream->readSTString(); mEnvMapUnderName = stream->readSTString(); mShoreName = stream->readSTString(); for( int i=0; ireadSTString(); } mSurfaceTexture = TextureHandle( mSurfaceName, MeshTexture ); mSpecMaskTex = TextureHandle( mSpecMaskName, MeshTexture ); mEnvMapOverTexture = TextureHandle( mEnvMapOverName, MeshTexture ); mEnvMapUnderTexture = TextureHandle( mEnvMapUnderName, MeshTexture ); mShoreTexture = TextureHandle( mShoreName, MeshTexture ); mFluid.SetTextures( mSurfaceTexture, mEnvMapOverTexture, mEnvMapUnderTexture, mShoreTexture, mDepthTexture, mShoreDepthTexture, mSpecMaskTex); for( int i=0; iread( &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; stream->read( &mUseDepthMap ); stream->read( &mShoreDepth ); stream->read( &mMinAlpha ); stream->read( &mMaxAlpha ); stream->read( &mDepthGradient ); stream->read( &mTessellationSurface ); stream->read( &mTessellationShore ); stream->read( &mSurfaceParallax ); stream->read( &mFlowAngle ); stream->read( &mFlowRate ); stream->read( &mDistortGridScale ); stream->read( &mDistortMagnitude ); stream->read( &mDistortTime ); stream->read( &mSpecColor ); stream->read( &mSpecPower ); if (stream->readFlag()) CalculateDepthMaps(); // Keep this *after* depth-map reads. 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 += F32(mTerrainHalfSize); Pos.y += F32(mTerrainHalfSize); } 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 + F32(mTerrainHalfSize); Y = (y * mObjScale.y) + Pos.y + F32(mTerrainHalfSize); 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 ); } //==============================================================================