void simpleFluidEmitter::surfaceFluidEmitter( MFnFluid& fluid, const MMatrix& fluidWorldMatrix, int plugIndex, MDataBlock& block, double dt, double conversion, double dropoff ) //============================================================================== // // Method: // // simpleFluidEmitter::surfaceFluidEmitter // // Description: // // Emits fluid from one of a predefined set of volumes (cube, sphere, // cylinder, cone, torus). // // Parameters: // // fluid: fluid into which we are emitting // fluidWorldMatrix: object->world matrix for the fluid // plugIndex: identifies which fluid connected to the emitter // we are emitting into // block: datablock for the emitter, to retrieve attribute // values // dt: time delta for this frame // conversion: mapping from UI emission rates to internal units // dropoff: specifies how much emission rate drops off as // the surface points move away from the centers // of the voxels in which they lie. // // Notes: // // To associate an owner object with an emitter, use the // addDynamic MEL command, e.g. "addDynamic simpleFluidEmitter1 pPlane1". // //============================================================================== { // get relevant world matrices // MMatrix fluidInverseWorldMatrix = fluidWorldMatrix.inverse(); // get emission rates for density, fuel, heat, and emission color // double densityEmit = fluidDensityEmission( block ); double fuelEmit = fluidFuelEmission( block ); double heatEmit = fluidHeatEmission( block ); bool doEmitColor = fluidEmitColor( block ); MColor emitColor = fluidColor( block ); // rate modulation based on frame time, user value conversion factor, and // standard emitter "rate" value (not actually exposed in most fluid // emitters, but there anyway). // double theRate = getRate(block) * dt * conversion; // get voxel dimensions and sizes (object space) // double size[3]; unsigned int res[3]; fluid.getDimensions( size[0], size[1], size[2] ); fluid.getResolution( res[0], res[1], res[2] ); // voxel sizes double dx = size[0] / res[0]; double dy = size[1] / res[1]; double dz = size[2] / res[2]; // voxel centers double Ox = -size[0]/2; double Oy = -size[1]/2; double Oz = -size[2]/2; // get the "swept geometry" data for the emitter surface. This structure // tracks the motion of each emitter triangle over the time interval // for this simulation step. We just use positions on the emitter // surface at the end of the time step to do the emission. // MDataHandle sweptHandle = block.inputValue( mSweptGeometry ); MObject sweptData = sweptHandle.data(); MFnDynSweptGeometryData fnSweptData( sweptData ); // for "non-jittered" sampling, just reset the random state for each // triangle, which gives us a fixed set of samples all the time. // Sure, they're still jittered, but they're all jittered the same, // which makes them kinda uniform. // bool jitter = fluidJitter(block); if( !jitter ) { resetRandomState( plugIndex, block ); } if( fnSweptData.triangleCount() > 0 ) { // average voxel face area - use this as the canonical unit that // receives the emission rate specified by the users. Scale the // rate for other triangles accordingly. // double vfArea = pow(dx*dy*dz, 2.0/3.0); // very rudimentary support for textured emission rate and // textured emission color. We simply sample each texture once // at the center of each emitter surface triangle. This will // cause aliasing artifacts when these triangles are large. // MFnDependencyNode fnNode( thisMObject() ); MObject rateTextureAttr = fnNode.attribute( "textureRate" ); MObject colorTextureAttr = fnNode.attribute( "particleColor" ); bool texturedRate = hasValidEmission2dTexture( rateTextureAttr ); bool texturedColor = hasValidEmission2dTexture( colorTextureAttr ); // construct texture coordinates for each triangle center // MDoubleArray uCoords, vCoords; if( texturedRate || texturedColor ) { uCoords.setLength( fnSweptData.triangleCount() ); vCoords.setLength( fnSweptData.triangleCount() ); int t; for( t = 0; t < fnSweptData.triangleCount(); t++ ) { MDynSweptTriangle tri = fnSweptData.sweptTriangle( t ); MVector uv0 = tri.uvPoint(0); MVector uv1 = tri.uvPoint(1); MVector uv2 = tri.uvPoint(2); MVector uvMid = (uv0+uv1+uv2)/3.0; uCoords[t] = uvMid[0]; vCoords[t] = uvMid[1]; } } // evaluate textured rate and color values at the triangle centers // MDoubleArray texturedRateValues; if( texturedRate ) { texturedRateValues.setLength( uCoords.length() ); evalEmission2dTexture( rateTextureAttr, uCoords, vCoords, NULL, &texturedRateValues ); } MVectorArray texturedColorValues; if( texturedColor ) { texturedColorValues.setLength( uCoords.length() ); evalEmission2dTexture( colorTextureAttr, uCoords, vCoords, &texturedColorValues, NULL ); } for( int t = 0; t < fnSweptData.triangleCount(); t++ ) { // calculate emission rate and color values for this triangle // double curTexturedRate = texturedRate ? texturedRateValues[t] : 1.0; MColor curTexturedColor; if( texturedColor ) { MVector& curVec = texturedColorValues[t]; curTexturedColor.r = (float)curVec[0]; curTexturedColor.g = (float)curVec[1]; curTexturedColor.b = (float)curVec[2]; curTexturedColor.a = 1.0; } else { curTexturedColor = emitColor; } MDynSweptTriangle tri = fnSweptData.sweptTriangle( t ); MVector v0 = tri.vertex(0); MVector v1 = tri.vertex(1); MVector v2 = tri.vertex(2); // compute number of samples for this triangle based on area, // with large triangles receiving approximately 1 sample for // each voxel that they intersect // double triArea = tri.area(); int numSamples = (int)(triArea / vfArea); if( numSamples < 1 ) numSamples = 1; // compute emission rate for the points on the triangle. // Scale the canonical rate by the area ratio of this triangle // to the average voxel size, then split it amongst all the samples. // double triRate = (theRate*(triArea/vfArea))/numSamples; triRate *= curTexturedRate; for( int j = 0; j < numSamples; j++ ) { // generate a random point on the triangle, // map it into fluid local space // double r1 = randgen(); double r2 = randgen(); if( r1 + r2 > 1 ) { r1 = 1-r1; r2 = 1-r2; } double r3 = 1 - (r1+r2); MPoint randPoint = r1*v0 + r2*v1 + r3*v2; randPoint *= fluidInverseWorldMatrix; // figure out where the current point lies // ::int3 coord; fluid.toGridIndex( randPoint, coord ); if( (coord[0]<0) || (coord[1]<0) || (coord[2]<0) || (coord[0]>=(int)res[0]) || (coord[1]>=(int)res[1]) || (coord[2]>=(int)res[2]) ) { continue; } // do some falloff based on how far from the voxel center // the current point lies // MPoint gridPoint; gridPoint.x = Ox + (coord[0]+0.5)*dx; gridPoint.y = Oy + (coord[1]+0.5)*dy; gridPoint.z = Oz + (coord[2]+0.5)*dz; MVector diff = gridPoint - randPoint; double distSquared = diff * diff; double distDrop = dropoff * distSquared; double newVal = triRate * exp( -distDrop ); // emit into the voxel // if( newVal != 0 ) { fluid.emitIntoArrays( (float) newVal, coord[0], coord[1], coord[2], (float)densityEmit, (float)heatEmit, (float)fuelEmit, doEmitColor, curTexturedColor ); } } } } }
void simpleFluidEmitter::volumeFluidEmitter( MFnFluid& fluid, const MMatrix& fluidWorldMatrix, int plugIndex, MDataBlock& block, double dt, double conversion, double dropoff ) //============================================================================== // // Method: // // simpleFluidEmitter::volumeFluidEmitter // // Description: // // Emits fluid from points distributed over the surface of the // emitter's owner object. // // Parameters: // // fluid: fluid into which we are emitting // fluidWorldMatrix: object->world matrix for the fluid // plugIndex: identifies which fluid connected to the emitter // we are emitting into // block: datablock for the emitter, to retrieve attribute // values // dt: time delta for this frame // conversion: mapping from UI emission rates to internal units // dropoff: specifies how much emission rate drops off as // we move away from the local y-axis of the // volume emitter shape. // //============================================================================== { // get emitter position and relevant matrices // MPoint emitterPos = getWorldPosition(); MMatrix emitterWorldMatrix = getWorldMatrix(); MMatrix fluidInverseWorldMatrix = fluidWorldMatrix.inverse(); // get emission rates for density, fuel, heat, and emission color // double densityEmit = fluidDensityEmission( block ); double fuelEmit = fluidFuelEmission( block ); double heatEmit = fluidHeatEmission( block ); bool doEmitColor = fluidEmitColor( block ); MColor emitColor = fluidColor( block ); // rate modulation based on frame time, user value conversion factor, and // standard emitter "rate" value (not actually exposed in most fluid // emitters, but there anyway). // double theRate = getRate(block) * dt * conversion; // get voxel dimensions and sizes (object space) // double size[3]; unsigned int res[3]; fluid.getDimensions( size[0], size[1], size[2] ); fluid.getResolution( res[0], res[1], res[2] ); // voxel sizes double dx = size[0] / res[0]; double dy = size[1] / res[1]; double dz = size[2] / res[2]; // voxel centers double Ox = -size[0]/2; double Oy = -size[1]/2; double Oz = -size[2]/2; // find the voxels that intersect the bounding box of the volume // primitive associated with the emitter // MBoundingBox bbox; if( !volumePrimitiveBoundingBox( bbox ) ) { // shouldn't happen // return; } // transform volume primitive into fluid space // bbox.transformUsing( emitterWorldMatrix ); bbox.transformUsing( fluidInverseWorldMatrix ); MPoint lowCorner = bbox.min(); MPoint highCorner = bbox.max(); // get fluid voxel coord range of bounding box // ::int3 lowCoords; ::int3 highCoords; fluid.toGridIndex( lowCorner, lowCoords ); fluid.toGridIndex( highCorner, highCoords ); int i; for ( i = 0; i < 3; i++ ) { if ( lowCoords[i] < 0 ) { lowCoords[i] = 0; } else if ( lowCoords[i] > ((int)res[i])-1 ) { lowCoords[i] = ((int)res[i])-1; } if ( highCoords[i] < 0 ) { highCoords[i] = 0; } else if ( highCoords[i] > ((int)res[i])-1 ) { highCoords[i] = ((int)res[i])-1; } } // figure out the emitter size relative to the voxel size, and compute // a per-voxel sampling rate that uses 1 sample/voxel for emitters that // are >= 2 voxels big in all dimensions. For smaller emitters, use up // to 8 samples per voxel. // double emitterVoxelSize[3]; emitterVoxelSize[0] = (highCorner[0]-lowCorner[0])/dx; emitterVoxelSize[1] = (highCorner[1]-lowCorner[1])/dy; emitterVoxelSize[2] = (highCorner[2]-lowCorner[2])/dz; double minVoxelSize = MIN(emitterVoxelSize[0],MIN(emitterVoxelSize[1],emitterVoxelSize[2])); if( minVoxelSize < 1.0 ) { minVoxelSize = 1.0; } int maxSamples = 8; int numSamples = (int)(8.0/(minVoxelSize*minVoxelSize*minVoxelSize) + 0.5); if( numSamples < 1 ) numSamples = 1; if( numSamples > maxSamples ) numSamples = maxSamples; // non-jittered, just use one sample in the voxel center. Should replace // with uniform sampling pattern. // bool jitter = fluidJitter(block); if( !jitter ) { numSamples = 1; } // for each voxel that could potentially intersect the volume emitter // primitive, take some samples in the voxel. For those inside the // volume, compute their dropoff relative to the primitive's local y-axis, // and emit an appropriate amount into the voxel. // for( i = lowCoords[0]; i <= highCoords[0]; i++ ) { double x = Ox + (i+0.5)*dx; for( int j = lowCoords[1]; j < highCoords[1]; j++ ) { double y = Oy + (j+0.5)*dy; for( int k = lowCoords[2]; k < highCoords[2]; k++ ) { double z = Oz + (k+0.5)*dz; for ( int si = 0; si < numSamples; si++) { // compute voxel sample point (object space) // double rx, ry, rz; if(jitter) { rx = x + dx*(randgen() - 0.5); ry = y + dy*(randgen() - 0.5); rz = z + dz*(randgen() - 0.5); } else { rx = x; ry = y; rz = z; } // to world space MPoint pt( rx, ry, rz ); pt *= fluidWorldMatrix; // test to see if point is inside volume primitive // if( volumePrimitivePointInside( pt, emitterWorldMatrix ) ) { // compute dropoff // double dist = pt.distanceTo( emitterPos ); double distDrop = dropoff * (dist*dist); double newVal = (theRate * exp( -distDrop )) / (double)numSamples; // emit into arrays // if( newVal != 0.0 ) { fluid.emitIntoArrays( (float) newVal, i, j, k, (float)densityEmit, (float)heatEmit, (float)fuelEmit, doEmitColor, emitColor ); } } } } } } }