/* FLUIDS v.1 - SPH Fluid Simulator for CPU and GPU Copyright (C) 2008. Rama Hoetzlein, http://www.rchoetzlein.com ZLib license This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include "gl_helper.h" #include "point_set.h" int PointSet::m_pcurr = -1; PointSet::PointSet () { m_GridRes.Set ( 0, 0, 0 ); m_pcurr = -1; Reset (); } int PointSet::GetGridCell ( int x, int y, int z ) { return (int) ( (z*m_GridRes.y + y)*m_GridRes.x + x); } Point* PointSet::firstGridParticle ( int gc, int& p ) { m_pcurr = m_Grid [ gc ]; if ( m_pcurr == -1 ) return 0x0; p = m_pcurr; return (Point*) (mBuf[0].data + m_pcurr * mBuf[0].stride); } Point* PointSet::nextGridParticle ( int& p ) { Point* pnt = 0x0; if ( m_pcurr != -1 ) { pnt = (Point*) (mBuf[0].data + m_pcurr * mBuf[0].stride); p = m_pcurr; m_pcurr = pnt->next; } return pnt; } unsigned short* PointSet::getNeighborTable ( int n, int& cnt ) { cnt = m_NC[n]; if ( cnt == 0 ) return 0x0; return &m_Neighbor[n][0]; } float PointSet::GetValue ( float x, float y, float z ) { float dx, dy, dz, dsq; float sum; int pndx; Point* pcurr; float R2 = 1.8*1.8; Grid_FindCells ( Vector3DF(x,y,z), m_GridCellsize/2.0 ); int cnt = 0; sum = 0.0; for (int cell=0; cell < 8; cell++ ) { if ( m_GridCell[cell] != -1 ) { pndx = m_Grid [ m_GridCell[cell] ]; while ( pndx != -1 ) { pcurr = (Point*) (mBuf[0].data + pndx*mBuf[0].stride); dx = x - pcurr->pos.x; dy = y - pcurr->pos.y; dz = z - pcurr->pos.z; dsq = dx*dx+dy*dy+dz*dz; if ( dsq < R2 ) sum += R2 / dsq; pndx = pcurr->next; } } } return sum; } Vector3DF PointSet::GetGradient ( float x, float y, float z ) { Vector3DF norm; float dx, dy, dz, dsq; float sum; int pndx; Point* pcurr; float R2 = (m_GridCellsize/2.0)*(m_GridCellsize/2.0); Grid_FindCells ( Vector3DF(x,y,z), m_GridCellsize/2.0 ); int cnt = 0; sum = 0.0; norm.Set (0,0,0); for (int cell=0; cell < 8; cell++ ) { if ( m_GridCell[cell] != -1 ) { pndx = m_Grid [ m_GridCell[cell] ]; while ( pndx != -1 ) { pcurr = (Point*) (mBuf[0].data + pndx*mBuf[0].stride); dx = x - pcurr->pos.x; dy = y - pcurr->pos.y; dz = z - pcurr->pos.z; dsq = dx*dx+dy*dy+dz*dz; if ( dsq > 0 && dsq < R2 ) { dsq = 2.0*R2 / (dsq*dsq); norm.x += dx * dsq; norm.y += dy * dsq; norm.z += dz * dsq; } pndx = pcurr->next; } } } norm.Normalize (); return norm; } DWORD PointSet::GetColor ( float x, float y, float z ) { Vector3DF clr; float dx, dy, dz, dsq; float sum; int pndx; Point* pcurr; float R2 = (m_GridCellsize/2.0)*(m_GridCellsize/2.0); Grid_FindCells ( Vector3DF(x,y,z), m_GridCellsize/2.0 ); int cnt = 0; sum = 0.0; clr.Set (0,0,0); for (int cell=0; cell < 8; cell++ ) { if ( m_GridCell[cell] != -1 ) { pndx = m_Grid [ m_GridCell[cell] ]; while ( pndx != -1 ) { pcurr = (Point*) (mBuf[0].data + pndx*mBuf[0].stride); dx = x - pcurr->pos.x; dy = y - pcurr->pos.y; dz = z - pcurr->pos.z; dsq = dx*dx+dy*dy+dz*dz; if ( dsq < R2 ) { dsq = 2.0*R2 / (dsq*dsq); clr.x += RED(pcurr->clr) * dsq; clr.y += GRN(pcurr->clr) * dsq; clr.z += BLUE(pcurr->clr) * dsq; } pndx = pcurr->next; } } } clr.Normalize (); return COLORA(clr.x, clr.y, clr.z, 1.0); } void PointSet::Reset () { // Reset number of particles // ResetBuffer ( 0 ); m_Time = 0; m_DT = 0.1; m_Param[POINT_GRAV] = 100.0; m_Param[PLANE_GRAV] = 0.0; m_Vec[ POINT_GRAV_POS].Set(0,0,50.0); m_Vec[ PLANE_GRAV_DIR].Set(0,0,-9.8); m_Vec[ EMIT_RATE ].Set ( 1, 10, 0 ); m_Vec[ EMIT_POS ].Set ( 50, 0, 35 ); m_Vec[ EMIT_ANG ].Set ( 90, 45, 50.0 ); m_Vec[ EMIT_DANG ].Set ( 0, 0, 0 ); m_Vec[ EMIT_SPREAD ].Set ( 4, 4, 1 ); } void PointSet::Initialize ( int mode, int total ) { switch (mode) { case BPOINT: { FreeBuffers (); AddBuffer ( BPOINT, sizeof ( Point ), total ); AddAttribute ( 0, "pos", sizeof ( Vector3DF ), false ); AddAttribute ( 0, "color", sizeof ( DWORD ), false ); Reset (); } break; case BPARTICLE: { FreeBuffers (); AddBuffer ( BPARTICLE, sizeof ( Particle ), total ); AddAttribute ( 0, "pos", sizeof ( Vector3DF ), false ); AddAttribute ( 0, "color", sizeof ( DWORD ), false ); AddAttribute ( 0, "vel", sizeof ( Vector3DF ), false ); AddAttribute ( 0, "ndx", sizeof ( unsigned short ), false ); AddAttribute ( 0, "age", sizeof ( unsigned short ), false ); Reset (); } break; } } int PointSet::AddPoint () { xref ndx; AddElem ( 0, ndx ); return ndx; } int PointSet::AddPointReuse () { xref ndx; if ( NumPoints() < mBuf[0].max-1 ) AddElem ( 0, ndx ); else RandomElem ( 0, ndx ); return ndx; } void PointSet::AddVolume ( Vector3DF min, Vector3DF max, float spacing ) { Vector3DF pos; Point* p; float dx, dy, dz; dx = max.x-min.x; dy = max.y-min.y; dz = max.z-min.z; for (float z = max.z; z >= min.z; z -= spacing ) { for (float y = min.y; y <= max.y; y += spacing ) { for (float x = min.x; x <= max.x; x += spacing ) { p = GetPoint ( AddPointReuse () ); pos.Set ( x, y, z); //pos.x += -0.05 + float( rand() * 0.1 ) / RAND_MAX; //pos.y += -0.05 + float( rand() * 0.1 ) / RAND_MAX; //pos.z += -0.05 + float( rand() * 0.1 ) / RAND_MAX; p->pos = pos; p->clr = COLORA( (x-min.x)/dx, (y-min.y)/dy, (z-min.z)/dz, 1); } } } } void PointSet::Draw ( float* view_mat, float rad ) { char* dat; Point* p; glEnable ( GL_NORMALIZE ); if ( m_Param[PNT_DRAWMODE] == 0 ) { glLoadMatrixf ( view_mat ); dat = mBuf[0].data; for (int n = 0; n < NumPoints(); n++) { p = (Point*) dat; glPushMatrix (); glTranslatef ( p->pos.x, p->pos.y, p->pos.z ); glScalef ( 0.2, 0.2, 0.2 ); glColor4f ( RED(p->clr), GRN(p->clr), BLUE(p->clr), ALPH(p->clr) ); drawSphere (); glPopMatrix (); dat += mBuf[0].stride; } } else if ( m_Param[PNT_DRAWMODE] == 1 ) { glLoadMatrixf ( view_mat ); dat = mBuf[0].data; glBegin ( GL_POINTS ); for (int n=0; n < NumPoints(); n++) { p = (Point*) dat; glColor3f ( RED(p->clr), GRN(p->clr), BLUE(p->clr) ); glVertex3f ( p->pos.x, p->pos.y, p->pos.z ); dat += mBuf[0].stride; } glEnd (); } } void PointSet::Emit ( float spacing ) { Particle* p; Vector3DF dir; Vector3DF pos; float ang_rand, tilt_rand; float rnd = m_Vec[EMIT_RATE].y * 0.15; int x = (int) sqrt(m_Vec[EMIT_RATE].y); for ( int n = 0; n < m_Vec[EMIT_RATE].y; n++ ) { ang_rand = (float(rand()*2.0/RAND_MAX) - 1.0) * m_Vec[EMIT_SPREAD].x; tilt_rand = (float(rand()*2.0/RAND_MAX) - 1.0) * m_Vec[EMIT_SPREAD].y; dir.x = cos ( ( m_Vec[EMIT_ANG].x + ang_rand) * DEGtoRAD ) * sin( ( m_Vec[EMIT_ANG].y + tilt_rand) * DEGtoRAD ) * m_Vec[EMIT_ANG].z; dir.y = sin ( ( m_Vec[EMIT_ANG].x + ang_rand) * DEGtoRAD ) * sin( ( m_Vec[EMIT_ANG].y + tilt_rand) * DEGtoRAD ) * m_Vec[EMIT_ANG].z; dir.z = cos ( ( m_Vec[EMIT_ANG].y + tilt_rand) * DEGtoRAD ) * m_Vec[EMIT_ANG].z; pos = m_Vec[EMIT_POS]; pos.x += spacing * (n/x); pos.y += spacing * (n%x); p = (Particle*) GetElem( 0, AddPointReuse () ); p->pos = pos; p->vel = dir; p->vel_eval = dir; p->age = 0; p->clr = COLORA ( m_Time/10.0, m_Time/5.0, m_Time /4.0, 1 ); } } void PointSet::Run () { if ( m_Vec[EMIT_RATE].x > 0 && ++m_Frame >= (int) m_Vec[EMIT_RATE].x ) { m_Frame = 0; Emit ( 1.0 ); } Advance(); } void PointSet::Advance () { char* dat; Particle* p; Vector3DF vnext, accel, norm; vnext = m_Vec[EMIT_DANG]; vnext *= m_DT; m_Vec[EMIT_ANG] += vnext; dat = mBuf[0].data; for ( int c = 0; c < NumPoints(); c++ ) { p = (Particle*) dat; accel.Set (0, 0, 0); // Plane gravity if ( m_Param[PLANE_GRAV] > 0) accel += m_Vec[PLANE_GRAV_DIR]; // Point gravity if ( m_Param[POINT_GRAV] > 0 ) { norm.x = ( p->pos.x - m_Vec[POINT_GRAV_POS].x ); norm.y = ( p->pos.y - m_Vec[POINT_GRAV_POS].y ); norm.z = ( p->pos.z - m_Vec[POINT_GRAV_POS].z ); norm.Normalize (); norm *= m_Param[POINT_GRAV]; accel -= norm; } // Leapfrog Integration ---------------------------- vnext = accel; vnext *= m_DT; vnext += p->vel; // v(t+1/2) = v(t-1/2) + a(t) dt p->vel_eval = p->vel; p->vel_eval += vnext; p->vel_eval *= 0.5; // v(t+1) = [v(t-1/2) + v(t+1/2)] * 0.5 used to compute forces later p->vel = vnext; vnext *= m_DT; p->pos += vnext; // p(t+1) = p(t) + v(t+1/2) dt // Euler integration ------------------------------- // accel += m_Gravity; // accel *= m_DT; // mParticles[c].vel += accel; // v(t+1) = v(t) + a(t) dt // mParticles[c].vel_eval += accel; // mParticles[c].vel_eval *= m_DT/d; // mParticles[c].pos += mParticles[c].vel_eval; // mParticles[c].vel_eval = mParticles[c].vel; dat += mBuf[0].stride; } m_Time += m_DT; } // Ideal grid cell size (gs) = 2 * smoothing radius = 0.02*2 = 0.04 // Ideal domain size = k*gs/d = k*0.02*2/0.005 = k*8 = {8, 16, 24, 32, 40, 48, ..} // (k = number of cells, gs = cell size, d = simulation scale) void PointSet::Grid_Setup ( Vector3DF min, Vector3DF max, float sim_scale, float cell_size, float border ) { float world_cellsize = cell_size / sim_scale; m_Grid.clear (); m_GridMin = min; m_GridMin -= border; m_GridMax = max; m_GridMax += border; m_GridSize = m_GridMax; m_GridSize -= m_GridMin; m_GridCellsize = world_cellsize; m_GridRes.x = ceil ( m_GridSize.x / world_cellsize ); // Determine grid resolution m_GridRes.y = ceil ( m_GridSize.y / world_cellsize ); m_GridRes.z = ceil ( m_GridSize.z / world_cellsize ); m_GridSize.x = m_GridRes.x * cell_size / sim_scale; // Adjust grid size to multiple of cell size m_GridSize.y = m_GridRes.y * cell_size / sim_scale; m_GridSize.z = m_GridRes.z * cell_size / sim_scale; m_GridDelta = m_GridRes; // delta = translate from world space to cell # m_GridDelta /= m_GridSize; m_GridTotal = (int)(m_GridSize.x * m_GridSize.y * m_GridSize.z); m_Grid.clear (); m_GridCnt.clear (); m_Grid.reserve ( m_GridTotal ); m_GridCnt.reserve ( m_GridTotal ); for (int n=0; n < m_GridTotal; n++) { m_Grid.push_back ( -1 ); m_GridCnt.push_back ( 0 ); } } void PointSet::Grid_Draw ( float* view_mat ) { float clr; int cx, cy, cz; float x1, y1, z1; float x2, y2, z2; int g = 0; glLoadMatrixf ( view_mat ); glColor3f ( 0.7, 0.7, 0.7 ); glBegin ( GL_LINES ); cz = 0; //for ( cz = 0; cz < m_GridRes.z; cz++ ) { for ( cy = 0; cy < m_GridRes.y; cy++ ) { for ( cx = 0; cx < m_GridRes.x; cx++ ) { // Cell is not empty. Process it. //if ( m_Grid[g] != 0x0 ) { // clr = m_GridCnt[g]/30.0; clr = 0.25; if ( clr <0.25) clr =0.25; if ( clr >1) clr =1 ; glColor3f ( clr, clr, clr ); x1 = (cx * m_GridDelta.x) + m_GridMin.x; x2 = ((cx+1) * m_GridDelta.x) + m_GridMin.x; y1 = (cy * m_GridDelta.y) + m_GridMin.y; y2 = ((cy+1) * m_GridDelta.y) + m_GridMin.y; z1 = (cz * m_GridDelta.z) + m_GridMin.z; z2 = ((cz+1) * m_GridDelta.z) + m_GridMin.z; glVertex3f ( x1, y1, z1 ); glVertex3f ( x2, y1, z1 ); glVertex3f ( x2, y1, z1 ); glVertex3f ( x2, y2, z1 ); glVertex3f ( x2, y2, z1 ); glVertex3f ( x1, y2, z1 ); glVertex3f ( x1, y2, z1 ); glVertex3f ( x1, y1, z1 ); glVertex3f ( x1, y1, z2 ); glVertex3f ( x2, y1, z2 ); glVertex3f ( x2, y1, z2 ); glVertex3f ( x2, y2, z2 ); glVertex3f ( x2, y2, z2 ); glVertex3f ( x1, y2, z2 ); glVertex3f ( x1, y2, z2 ); glVertex3f ( x1, y1, z2 ); glVertex3f ( x1, y1, z1 ); glVertex3f ( x1, y1, z2 ); glVertex3f ( x1, y2, z1 ); glVertex3f ( x1, y2, z2 ); glVertex3f ( x2, y2, z1 ); glVertex3f ( x2, y2, z2 ); glVertex3f ( x2, y1, z1 ); glVertex3f ( x2, y1, z2 ); //} g++; } } //} glEnd (); } void PointSet::Grid_InsertParticles () { char *dat1, *dat1_end; Point *p; int gs; int gx, gy, gz; dat1_end = mBuf[0].data + NumPoints()*mBuf[0].stride; for ( dat1 = mBuf[0].data; dat1 < dat1_end; dat1 += mBuf[0].stride ) ((Point*) dat1)->next = -1; for (int n=0; n < m_GridTotal; n++) { m_Grid[n] = -1; m_GridCnt[n] = 0; } dat1_end = mBuf[0].data + NumPoints()*mBuf[0].stride; int n = 0; for ( dat1 = mBuf[0].data; dat1 < dat1_end; dat1 += mBuf[0].stride ) { p = (Point*) dat1; gx = (int)( (p->pos.x - m_GridMin.x) * m_GridDelta.x); // Determine grid cell gy = (int)( (p->pos.y - m_GridMin.y) * m_GridDelta.y); gz = (int)( (p->pos.z - m_GridMin.z) * m_GridDelta.z); gs = (int)( (gz*m_GridRes.y + gy)*m_GridRes.x + gx); if ( gs >= 0 && gs < m_GridTotal ) { p->next = m_Grid[gs]; m_Grid[gs] = n; m_GridCnt[gs]++; } n++; } } int PointSet::Grid_FindCell ( Vector3DF p ) { int gc; Vector3DI cell; cell.x = (int) (p.x - m_GridMin.x) * m_GridDelta.x; cell.y = (int) (p.y - m_GridMin.y) * m_GridDelta.y; cell.z = (int) (p.z - m_GridMin.z) * m_GridDelta.z; gc = (int)( (cell.z*m_GridRes.y + cell.y)*m_GridRes.x + cell.x); if ( gc < 0 || gc > m_GridTotal ) return -1; return gc; } void PointSet::Grid_FindCells ( Vector3DF p, float radius ) { Vector3DI sph_min; // Compute sphere range sph_min.x = (int)((-radius + p.x - m_GridMin.x) * m_GridDelta.x); sph_min.y = (int)((-radius + p.y - m_GridMin.y) * m_GridDelta.y); sph_min.z = (int)((-radius + p.z - m_GridMin.z) * m_GridDelta.z); if ( sph_min.x < 0 ) sph_min.x = 0; if ( sph_min.y < 0 ) sph_min.y = 0; if ( sph_min.z < 0 ) sph_min.z = 0; m_GridCell[0] = (int)((sph_min.z * m_GridRes.y + sph_min.y) * m_GridRes.x + sph_min.x); m_GridCell[1] = m_GridCell[0] + 1; m_GridCell[2] = (int)(m_GridCell[0] + m_GridRes.x); m_GridCell[3] = m_GridCell[2] + 1; if ( sph_min.z+1 < m_GridRes.z ) { m_GridCell[4] = (int)(m_GridCell[0] + m_GridRes.y*m_GridRes.x); m_GridCell[5] = m_GridCell[4] + 1; m_GridCell[6] = (int)(m_GridCell[4] + m_GridRes.x); m_GridCell[7] = m_GridCell[6] + 1; } if ( sph_min.x+1 >= m_GridRes.x ) { m_GridCell[1] = -1; m_GridCell[3] = -1; m_GridCell[5] = -1; m_GridCell[7] = -1; } if ( sph_min.y+1 >= m_GridRes.y ) { m_GridCell[2] = -1; m_GridCell[3] = -1; m_GridCell[6] = -1; m_GridCell[7] = -1; } }