Water Simulation

This individual project was a water simulation on a 2D plane using normal mapping. It was done over the course of two weeks in March of 2014. It was completed predominantly in C++, but there is also a basic OpenGL fragment shader for specular lighting written in GLSL.

The underlying system is a 2D grid of particles attached to one another using springs. Their positions are then converted in to a normal map and sent to the GPU as a texture. The normal texture is then sampled by the fragment shader and used with the point light to generate lighting and specular effects on the single polygon shown.

The full source code and visual studio project can be found at this link.

The following code snippet is the update function for the water surface class, which consists of an array of particles.

void WaterSurface::update(float deltaSeconds)
{
    m_internalTime += deltaSeconds;
    const int neighborsToCheck = 2;
    for(int ii = 0; ii < SIZE_OF_WATERSURFACE_ARRAY; ii++)
    {
        float aggregateForces = 0.f;
        for(int xNeighbor = -1*neighborsToCheck; xNeighbor <= neighborsToCheck; xNeighbor++)
        {
            for(int yNeighbor = -1*neighborsToCheck; yNeighbor <= neighborsToCheck; yNeighbor++)
            {
                int neighborIndex = getIndexOfNeighbor(ii, xNeighbor, yNeighbor);
                if(neighborIndex > -1 && neighborIndex != ii)
                {
                    const float m_springStrength = 500.f/(4*neighborsToCheck*neighborsToCheck);
                    const float m_dampingStrength = 5.f/(4*neighborsToCheck*neighborsToCheck);

                    float differential = m_surface[ii].m_previousPosition.z - m_surface[neighborIndex].m_previousPosition.z;
                    float velocityDifferential = m_surface[ii].m_previousVelocity.z - m_surface[neighborIndex].m_previousVelocity.z;
                    //TODO get rid of this divide in favor of something else
                    float distanceFalloff = 1.0f/((xNeighbor*xNeighbor + yNeighbor*yNeighbor));
                    float springForce = (-1 * m_springStrength * differential);
                    float dampingForce = -1 * m_dampingStrength * velocityDifferential;
                    float lastCalculatedForce = springForce + dampingForce;
                    aggregateForces += (lastCalculatedForce*distanceFalloff);
                }
                float returningStrength = 1.0f;
                float returningDampening = 0.3f;
                aggregateForces += -1 * returningStrength * m_surface[ii].m_previousPosition.z;
                aggregateForces += -1 * returningDampening * m_surface[ii].m_previousVelocity.z;
            }
        }
        m_surface[ii].m_velocity.z = m_surface[ii].m_previousVelocity.z + aggregateForces*deltaSeconds;
        m_surface[ii].update(deltaSeconds);
    }

    for(int ii = 0; ii < SIZE_OF_WATERSURFACE_ARRAY; ii++)
    {
        m_surface[ii].reposition();
        if(    getIndexOfNeighbor(ii, -1, 0) >=0 && getIndexOfNeighbor(ii, 1, 0) >=0 && getIndexOfNeighbor(ii, 0, -1) >=0 && getIndexOfNeighbor(ii, 0, 1) >=0)
        {
            updateNormalAtIndex(ii);
        }
    }

    m_material.m_primaryLightSource = Vector3f(50.f*sin(m_internalTime)+50.f, 50.f*sin(2*m_internalTime)+50.f, 50.f);
}