// * This file is part of the COLOBOT source code // * Copyright (C) 2001-2008, Daniel ROUX & EPSITEC SA, www.epsitec.ch // * // * 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 http://www.gnu.org/licenses/. // water.cpp #include #include #include #include "common/struct.h" #include "math/geometry.h" #include "math/conv.h" #include "old/d3dengine.h" #include "old/d3dmath.h" #include "old/d3dutil.h" #include "common/event.h" #include "common/misc.h" #include "common/iman.h" #include "old/math3d.h" #include "old/particule.h" #include "old/terrain.h" #include "object/object.h" #include "old/sound.h" #include "old/water.h" // Constructor of the terrain. CWater::CWater(CInstanceManager* iMan, CD3DEngine* engine) { m_iMan = iMan; m_iMan->AddInstance(CLASS_WATER, this); m_engine = engine; m_terrain = 0; m_particule = 0; m_sound = 0; m_type[0] = WATER_NULL; m_type[1] = WATER_NULL; m_level = 0.0f; m_bDraw = true; m_bLava = false; m_color = 0xffffffff; m_subdiv = 4; m_filename[0] = 0; } // Destructor of the terrain. CWater::~CWater() { } bool CWater::EventProcess(const Event &event) { if ( event.event == EVENT_FRAME ) { return EventFrame(event); } if ( event.event == EVENT_KEYDOWN ) { #if 0 if ( event.param == 'S' ) { if ( m_subdiv == 1 ) m_subdiv = 2; else if ( m_subdiv == 2 ) m_subdiv = 4; else if ( m_subdiv == 4 ) m_subdiv = 8; else if ( m_subdiv == 8 ) m_subdiv = 1; SetLevel(m_level); } if ( event.param == 'M' ) { SetLevel(m_level+1.0f); } if ( event.param == 'D' ) { SetLevel(m_level-1.0f); } if ( event.param == 'H' ) { m_bDraw = !m_bDraw; } if ( event.param == 'C' ) { if ( m_color == 0xffffffff ) m_color = 0xcccccccc; else if ( m_color == 0xcccccccc ) m_color = 0x88888888; else if ( m_color == 0x88888888 ) m_color = 0x44444444; else if ( m_color == 0x44444444 ) m_color = 0x00000000; else if ( m_color == 0x00000000 ) m_color = 0xffffffff; } if ( event.param == 'Q' ) { int i; i = (m_color>>24); i += 0x44; i &= 0xff; i = (i<<24); m_color &= 0x00ffffff; m_color |= i; } if ( event.param == 'W' ) { int i; i = (m_color>>16); i += 0x44; i &= 0xff; i = (i<<16); m_color &= 0xff00ffff; m_color |= i; } if ( event.param == 'E' ) { int i; i = (m_color>>8); i += 0x44; i &= 0xff; i = (i<<8); m_color &= 0xffff00ff; m_color |= i; } if ( event.param == 'R' ) { int i; i = m_color; i += 0x44; i &= 0xff; m_color &= 0xffffff00; m_color |= i; } #endif } return true; } // Makes water evolve. bool CWater::EventFrame(const Event &event) { if ( m_engine->RetPause() ) return true; m_time += event.rTime; if ( m_type[0] == WATER_NULL ) return true; if ( m_bLava ) { LavaFrame(event.rTime); } return true; } // Makes evolve the steam jets on the lava. void CWater::LavaFrame(float rTime) { Math::Vector eye, lookat, dir, perp, pos; float distance, shift, level; int i; if ( m_particule == 0 ) { m_particule = (CParticule*)m_iMan->SearchInstance(CLASS_PARTICULE); } for ( i=0 ; i= 0.1f ) { eye = m_engine->RetEyePt(); lookat = m_engine->RetLookatPt(); distance = Math::Rand()*200.0f; shift = (Math::Rand()-0.5f)*200.0f; dir = Normalize(lookat-eye); pos = eye + dir*distance; perp.x = -dir.z; perp.y = dir.y; perp.z = dir.x; pos = pos + perp*shift; level = m_terrain->RetFloorLevel(pos, true); if ( level < m_level ) { pos.y = m_level; level = Math::Rand(); if ( level < 0.8f ) { if ( VaporCreate(PARTIFIRE, pos, 0.02f+Math::Rand()*0.06f) ) { m_lastLava = m_time; } } else if ( level < 0.9f ) { if ( VaporCreate(PARTIFLAME, pos, 0.5f+Math::Rand()*3.0f) ) { m_lastLava = m_time; } } else { if ( VaporCreate(PARTIVAPOR, pos, 0.2f+Math::Rand()*2.0f) ) { m_lastLava = m_time; } } } } } // Removes all the steam jets. void CWater::VaporFlush() { int i; for ( i=0 ; iPlay(SOUND_BLUP, pos, 1.0f, 1.0f-Math::Rand()*0.5f); } if ( m_vapor[i].type == PARTIFLAME ) { //? m_sound->Play(SOUND_SWIM, pos, 1.0f, 1.0f-Math::Rand()*0.5f); } if ( m_vapor[i].type == PARTIVAPOR ) { m_sound->Play(SOUND_PSHHH, pos, 0.3f, 2.0f); } return true; } } return false; } // Makes evolve a steam jet, void CWater::VaporFrame(int i, float rTime) { Math::Vector pos, speed; Math::Point dim; int j; m_vapor[i].time += rTime; if ( m_sound == 0 ) { m_sound = (CSound*)m_iMan->SearchInstance(CLASS_SOUND); } if ( m_vapor[i].time <= m_vapor[i].delay ) { if ( m_time-m_vapor[i].last >= m_engine->ParticuleAdapt(0.02f) ) { m_vapor[i].last = m_time; if ( m_vapor[i].type == PARTIFIRE ) { for ( j=0 ; j<10 ; j++ ) { pos = m_vapor[i].pos; pos.x += (Math::Rand()-0.5f)*2.0f; pos.z += (Math::Rand()-0.5f)*2.0f; pos.y -= 1.0f; speed.x = (Math::Rand()-0.5f)*6.0f; speed.z = (Math::Rand()-0.5f)*6.0f; speed.y = 8.0f+Math::Rand()*5.0f; dim.x = Math::Rand()*1.5f+1.5f; dim.y = dim.x; m_particule->CreateParticule(pos, speed, dim, PARTIERROR, 2.0f, 10.0f); } } else if ( m_vapor[i].type == PARTIFLAME ) { pos = m_vapor[i].pos; pos.x += (Math::Rand()-0.5f)*8.0f; pos.z += (Math::Rand()-0.5f)*8.0f; pos.y -= 2.0f; speed.x = (Math::Rand()-0.5f)*2.0f; speed.z = (Math::Rand()-0.5f)*2.0f; speed.y = 4.0f+Math::Rand()*4.0f; dim.x = Math::Rand()*2.0f+2.0f; dim.y = dim.x; m_particule->CreateParticule(pos, speed, dim, PARTIFLAME); } else { pos = m_vapor[i].pos; pos.x += (Math::Rand()-0.5f)*4.0f; pos.z += (Math::Rand()-0.5f)*4.0f; pos.y -= 2.0f; speed.x = (Math::Rand()-0.5f)*2.0f; speed.z = (Math::Rand()-0.5f)*2.0f; speed.y = 8.0f+Math::Rand()*8.0f; dim.x = Math::Rand()*1.0f+1.0f; dim.y = dim.x; m_particule->CreateParticule(pos, speed, dim, PARTIVAPOR); } } } else { m_vapor[i].bUsed = false; } } // Adjusts the position to normal, to imitate reflections on an expanse of water at rest. void CWater::AdjustLevel(Math::Vector &pos, Math::Vector &norm, Math::Point &uv1, Math::Point &uv2) { #if 0 float t1, t2; uv1.x = (pos.x+10000.0f)/40.0f; uv1.y = (pos.z+10000.0f)/40.0f; t1 = m_time*1.5f + pos.x*0.1f * pos.z*0.2f; pos.y += sinf(t1)*m_eddy.y; t1 = m_time*0.6f + pos.x*0.1f * pos.z*0.2f; t2 = m_time*0.7f + pos.x*0.3f * pos.z*0.4f; pos.x += sinf(t1)*m_eddy.x; pos.z += sinf(t2)*m_eddy.z; //? uv2.x = (pos.x+10000.0f)/40.0f+0.3f; //? uv2.y = (pos.z+10000.0f)/40.0f+0.4f; uv2.x = (pos.x+10000.0f)/20.0f; uv2.y = (pos.z+10000.0f)/20.0f; t1 = m_time*0.7f + pos.x*5.5f + pos.z*5.6f; t2 = m_time*0.8f + pos.x*5.7f + pos.z*5.8f; norm = Math::Vector(sinf(t1)*m_glint, 1.0f, sinf(t2)*m_glint); #else float t1, t2; t1 = m_time*1.5f + pos.x*0.1f * pos.z*0.2f; pos.y += sinf(t1)*m_eddy.y; t1 = m_time*1.5f; uv1.x = (pos.x+10000.0f)/40.0f+sinf(t1)*m_eddy.x*0.02f; uv1.y = (pos.z+10000.0f)/40.0f-cosf(t1)*m_eddy.z*0.02f; uv2.x = (pos.x+10010.0f)/20.0f+cosf(-t1)*m_eddy.x*0.02f; uv2.y = (pos.z+10010.0f)/20.0f-sinf(-t1)*m_eddy.z*0.02f; t1 = m_time*0.50f + pos.x*2.1f + pos.z*1.1f; t2 = m_time*0.75f + pos.x*2.0f + pos.z*1.0f; norm = Math::Vector(sinf(t1)*m_glint, 1.0f, sinf(t2)*m_glint); #endif } // Draw the back surface of the water. // This surface prevents to see the sky (background) underwater! void CWater::DrawBack() { LPDIRECT3DDEVICE7 device; D3DVERTEX2 vertex[4]; // 2 triangles D3DMATERIAL7 material; Math::Matrix matrix; Math::Vector eye, lookat, n, p, p1, p2; Math::Point uv1, uv2; float deep, dist; if ( !m_bDraw ) return; if ( m_type[0] == WATER_NULL ) return; if ( m_lineUsed == 0 ) return; eye = m_engine->RetEyePt(); lookat = m_engine->RetLookatPt(); ZeroMemory( &material, sizeof(D3DMATERIAL7) ); material.diffuse = m_diffuse; material.ambient = m_ambient; m_engine->SetMaterial(material); m_engine->SetTexture("", 0); device = m_engine->RetD3DDevice(); device->SetRenderState(D3DRENDERSTATE_LIGHTING, false); device->SetRenderState(D3DRENDERSTATE_ZENABLE, false); device->SetRenderState(D3DRENDERSTATE_ZWRITEENABLE, false); m_engine->SetState(D3DSTATENORMAL); deep = m_engine->RetDeepView(0); m_engine->SetDeepView(deep*2.0f, 0); m_engine->SetFocus(m_engine->RetFocus()); m_engine->UpdateMatProj(); // twice the depth of view matrix.LoadIdentity(); { D3DMATRIX mat = MAT_TO_D3DMAT(matrix); device->SetTransform(D3DTRANSFORMSTATE_WORLD, &mat); } p.x = eye.x; p.z = eye.z; dist = Math::DistanceProjected(eye, lookat); p.x = (lookat.x-eye.x)*deep*1.0f/dist + eye.x; p.z = (lookat.z-eye.z)*deep*1.0f/dist + eye.z; p1.x = (lookat.z-eye.z)*deep*2.0f/dist + p.x; p1.z = -(lookat.x-eye.x)*deep*2.0f/dist + p.z; p2.x = -(lookat.z-eye.z)*deep*2.0f/dist + p.x; p2.z = (lookat.x-eye.x)*deep*2.0f/dist + p.z; p1.y = -50.0f; p2.y = m_level; n.x = (lookat.x-eye.x)/dist; n.z = (lookat.z-eye.z)/dist; n.y = 0.0f; uv1.x = uv1.y = 0.0f; uv2.x = uv2.y = 0.0f; vertex[0] = D3DVERTEX2(Math::Vector(p1.x, p2.y, p1.z), n, uv1.x,uv2.y); vertex[1] = D3DVERTEX2(Math::Vector(p1.x, p1.y, p1.z), n, uv1.x,uv1.y); vertex[2] = D3DVERTEX2(Math::Vector(p2.x, p2.y, p2.z), n, uv2.x,uv2.y); vertex[3] = D3DVERTEX2(Math::Vector(p2.x, p1.y, p2.z), n, uv2.x,uv1.y); device->DrawPrimitive(D3DPT_TRIANGLESTRIP, D3DFVF_VERTEX2, vertex, 4, NULL); m_engine->AddStatisticTriangle(2); m_engine->SetDeepView(deep, 0); m_engine->SetFocus(m_engine->RetFocus()); m_engine->UpdateMatProj(); // gives the initial depth of view device->SetRenderState(D3DRENDERSTATE_LIGHTING, true); device->SetRenderState(D3DRENDERSTATE_ZENABLE, true); device->SetRenderState(D3DRENDERSTATE_ZWRITEENABLE, false); } // Draws the flat surface of the water. void CWater::DrawSurf() { LPDIRECT3DDEVICE7 device; D3DVERTEX2* vertex; // triangles D3DMATERIAL7 material; Math::Matrix matrix; Math::Vector eye, lookat, n, pos, p; Math::Point uv1, uv2; bool bUnder; DWORD flags; float deep, size, sizez, radius; int rankview, i, j, u; if ( !m_bDraw ) return; if ( m_type[0] == WATER_NULL ) return; if ( m_lineUsed == 0 ) return; vertex = (D3DVERTEX2*)malloc(sizeof(D3DVERTEX2)*(m_brick+2)*2); eye = m_engine->RetEyePt(); lookat = m_engine->RetLookatPt(); rankview = m_engine->RetRankView(); bUnder = ( rankview == 1); device = m_engine->RetD3DDevice(); //? device->SetRenderState(D3DRENDERSTATE_AMBIENT, 0xffffffff); //? device->SetRenderState(D3DRENDERSTATE_LIGHTING, true); //? device->SetRenderState(D3DRENDERSTATE_ZENABLE, false); device->SetRenderState(D3DRENDERSTATE_ZWRITEENABLE, false); matrix.LoadIdentity(); { D3DMATRIX mat = MAT_TO_D3DMAT(matrix); device->SetTransform(D3DTRANSFORMSTATE_WORLD, &mat); } ZeroMemory( &material, sizeof(D3DMATERIAL7) ); material.diffuse = m_diffuse; material.ambient = m_ambient; m_engine->SetMaterial(material); m_engine->SetTexture(m_filename, 0); m_engine->SetTexture(m_filename, 1); if ( m_type[rankview] == WATER_TT ) { m_engine->SetState(D3DSTATETTb|D3DSTATEDUALw|D3DSTATEWRAP, m_color); } if ( m_type[rankview] == WATER_TO ) { m_engine->SetState(D3DSTATENORMAL|D3DSTATEDUALw|D3DSTATEWRAP); } if ( m_type[rankview] == WATER_CT ) { m_engine->SetState(D3DSTATETTb); } if ( m_type[rankview] == WATER_CO ) { m_engine->SetState(D3DSTATENORMAL); } device->SetRenderState(D3DRENDERSTATE_FOGENABLE, true); size = m_size/2.0f; if ( bUnder ) sizez = -size; else sizez = size; // Draws all the lines. deep = m_engine->RetDeepView(0)*1.5f; for ( i=0 ; i deep+radius ) continue; D3DVECTOR pD3D = VEC_TO_D3DVEC(p); device->ComputeSphereVisibility(&pD3D, &radius, 1, 0, &flags); if ( flags & D3DSTATUS_CLIPINTERSECTIONALL ) continue; u = 0; p.x = pos.x-size; p.z = pos.z-sizez; p.y = pos.y; AdjustLevel(p, n, uv1, uv2); if ( bUnder ) n.y = -n.y; vertex[u++] = D3DVERTEX2(p, n, uv1.x,uv1.y, uv2.x,uv2.y); p.x = pos.x-size; p.z = pos.z+sizez; p.y = pos.y; AdjustLevel(p, n, uv1, uv2); if ( bUnder ) n.y = -n.y; vertex[u++] = D3DVERTEX2(p, n, uv1.x,uv1.y, uv2.x,uv2.y); for ( j=0 ; jDrawPrimitive(D3DPT_TRIANGLESTRIP, D3DFVF_VERTEX2, vertex, u, NULL); m_engine->AddStatisticTriangle(u-2); } free(vertex); } // Indicates if there is water in a given position. bool CWater::RetWater(int x, int y) { Math::Vector pos; float size, offset, level; int dx, dy; x *= m_subdiv; y *= m_subdiv; size = m_size/m_subdiv; offset = m_brick*m_size/2.0f; for ( dy=0 ; dy<=m_subdiv ; dy++ ) { for ( dx=0 ; dx<=m_subdiv ; dx++ ) { pos.x = (x+dx)*size - offset; pos.z = (y+dy)*size - offset; pos.y = 0.0f; level = m_terrain->RetFloorLevel(pos, true); if ( level < m_level+m_eddy.y ) return true; } } return false; } // Updates the positions, relative to the ground. bool CWater::CreateLine(int x, int y, int len) { float offset; m_line[m_lineUsed].x = x; m_line[m_lineUsed].y = y; m_line[m_lineUsed].len = len; offset = m_brick*m_size/2.0f - m_size/2.0f; m_line[m_lineUsed].px1 = m_size* m_line[m_lineUsed].x - offset; m_line[m_lineUsed].px2 = m_size*(m_line[m_lineUsed].x+m_line[m_lineUsed].len) - offset; m_line[m_lineUsed].pz = m_size* m_line[m_lineUsed].y - offset; m_lineUsed ++; return ( m_lineUsed < MAXWATERLINE ); } // Creates all expanses of water. bool CWater::Create(WaterType type1, WaterType type2, const char *filename, D3DCOLORVALUE diffuse, D3DCOLORVALUE ambient, float level, float glint, Math::Vector eddy) { int x, y, len; m_type[0] = type1; m_type[1] = type2; m_diffuse = diffuse; m_ambient = ambient; m_level = level; m_glint = glint; m_eddy = eddy; m_time = 0.0f; m_lastLava = 0.0f; strcpy(m_filename, filename); VaporFlush(); if ( m_filename[0] != 0 ) { m_engine->LoadTexture(m_filename, 0); m_engine->LoadTexture(m_filename, 1); } if ( m_terrain == 0 ) { m_terrain = (CTerrain*)m_iMan->SearchInstance(CLASS_TERRAIN); } m_brick = m_terrain->RetBrick()*m_terrain->RetMosaic(); m_size = m_terrain->RetSize(); m_brick /= m_subdiv; m_size *= m_subdiv; if ( m_type[0] == WATER_NULL ) return true; m_lineUsed = 0; for ( y=0 ; y= 5 ) { if ( !CreateLine(x-len+1, y, len) ) return false; len = 0; } } else // dry? { if ( len != 0 ) { if ( !CreateLine(x-len, y, len) ) return false; len = 0; } } } if ( len != 0 ) { if ( !CreateLine(x-len, y, len) ) return false; } } return true; } // Removes all the water. void CWater::Flush() { m_type[0] = WATER_NULL; m_type[1] = WATER_NULL; m_level = 0.0f; m_bLava = false; } // Changes the level of the water. bool CWater::SetLevel(float level) { m_level = level; return Create(m_type[0], m_type[1], m_filename, m_diffuse, m_ambient, m_level, m_glint, m_eddy); } // Returns the current level of water. float CWater::RetLevel() { return m_level; } // Returns the current level of water for a given object. float CWater::RetLevel(CObject* object) { ObjectType type; type = object->RetType(); if ( type == OBJECT_HUMAN || type == OBJECT_TECH ) { return m_level-3.0f; } if ( type == OBJECT_MOBILEfa || type == OBJECT_MOBILEta || type == OBJECT_MOBILEwa || type == OBJECT_MOBILEia || type == OBJECT_MOBILEfc || type == OBJECT_MOBILEtc || type == OBJECT_MOBILEwc || type == OBJECT_MOBILEic || type == OBJECT_MOBILEfi || type == OBJECT_MOBILEti || type == OBJECT_MOBILEwi || type == OBJECT_MOBILEii || type == OBJECT_MOBILEfs || type == OBJECT_MOBILEts || type == OBJECT_MOBILEws || type == OBJECT_MOBILEis || type == OBJECT_MOBILErt || type == OBJECT_MOBILErc || type == OBJECT_MOBILErr || type == OBJECT_MOBILErs || type == OBJECT_MOBILEsa || type == OBJECT_MOBILEtg || type == OBJECT_MOBILEft || type == OBJECT_MOBILEtt || type == OBJECT_MOBILEwt || type == OBJECT_MOBILEit || type == OBJECT_MOBILEdr ) { return m_level-2.0f; } return m_level; } // Management of the mode of lava/water. void CWater::SetLava(bool bLava) { m_bLava = bLava; } bool CWater::RetLava() { return m_bLava; } // Adjusts the eye of the camera, not to be in the water. void CWater::AdjustEye(Math::Vector &eye) { if ( m_bLava ) { if ( eye.y < m_level+2.0f ) { eye.y = m_level+2.0f; // never under the lava } } else { if ( eye.y >= m_level-2.0f && eye.y <= m_level+2.0f ) // close to the surface? { eye.y = m_level+2.0f; // bam, well above } } }