/** 3D float Perlin noise. */ TqFloat CqNoise::FGNoise3( const CqVector3D& v ) { TqFloat x, y, z; x = v.x(); y = v.y(); z = v.z(); return ( 0.5f * ( 1.0f + CqNoise1234::noise( x, y, z ) ) ); }
/** Vector-valued 4D Perlin noise. */ CqVector3D CqNoise::PGNoise4( const CqVector3D& v, TqFloat t ) { TqFloat x, y, z; TqFloat a, b, c; x = v.x(); y = v.y(); z = v.z(); a = 0.5f * ( 1.0f + CqNoise1234::noise( x, y, z, t ) ); b = 0.5f * ( 1.0f + CqNoise1234::noise( x + O1x, y + O1y, z + O1z, t + O1t ) ); c = 0.5f * ( 1.0f + CqNoise1234::noise( x + O2x, y + O2y, z + O2z, t + O2t ) ); return ( CqVector3D( a, b, c ) ); }
// Dump a 3d vector void CqMPDump::dumpVec3(const CqVector3D& v) { TqFloat x = v.x(); TqFloat y = v.y(); TqFloat z = v.z(); size_t len_written = fwrite((void*)&x, sizeof(TqFloat), 1, m_outFile); len_written += fwrite((void*)&y, sizeof(TqFloat), 1, m_outFile); len_written += fwrite((void*)&z, sizeof(TqFloat), 1, m_outFile); if(len_written != 3) AQSIS_THROW_XQERROR(XqInvalidFile, EqE_System, "Error writing mpdump file"); }
void CqEnvironmentMapOld::SampleMap( CqVector3D& R1, CqVector3D& swidth, CqVector3D& twidth, std::valarray<TqFloat>& val, TqInt index, TqFloat* average_depth, TqFloat* shadow_depth ) { // Check the memory and make sure we don't abuse it CriticalMeasure(); if ( m_pImage != 0 ) { if ( Type() != MapType_LatLong ) { CqVector3D R2, R3, R4; R2 = R1 + swidth; R3 = R1 + twidth; R4 = R1 + swidth + twidth; SampleMap( R1, R2, R3, R4, val ); } else if ( Type() == MapType_LatLong ) { CqVector3D V = R1; V.Unit(); TqFloat sswidth = swidth.Magnitude(); TqFloat stwidth = twidth.Magnitude(); TqFloat ss1 = atan2( V.y(), V.x() ) / ( 2.0 * RI_PI ); // -.5 -> .5 ss1 = ss1 + 0.5; // remaps to 0 -> 1 TqFloat tt1 = acos( V.z() ) / RI_PI; CqTextureMapOld::SampleMap( ss1, tt1, sswidth/RI_PI, stwidth/RI_PI, val ); } } }
/** \brief Compute occlusion from the current view direction to the * given sample region. * * \param sampleRegion - parallelogram region over which to sample the map * \param sampleOpts - set of sampling options * \param numSamples - number of samples to take for the region. * \param outSamps[0] - Return parameter; amount of occlusion over the * sample region in the viewing direction for this map. */ void sample(const Sq3DSamplePllgram& sampleRegion, const CqShadowSampleOptions& sampleOpts, const TqInt numSamples, TqFloat* outSamps) { // filter weights CqConstFilter filterWeights; // Use constant depth approximation for the surface for maximum // sampling speed. We use the depth from the camera to the centre // of the sample region. CqConstDepthApprox depthFunc((m_currToLight*sampleRegion.c).z()); // Determine rough filter support. This results in a // texture-aligned box, so doesn't do proper anisotropic filtering. // For occlusion this isn't visible anyway because of the large // amount of averaging. We also want the filter setup to be as // fast as possible. // CqVector3D side1 = m_currToRasterVec*sampleRegion.s1; // CqVector3D side2 = m_currToRasterVec*sampleRegion.s2; // CqVector3D center = m_currToRaster*sampleRegion.c; // TqFloat sWidthOn2 = max(side1.x(), side2.x())*m_pixels.width()/2; // TqFloat tWidthOn2 = max(side1.y(), side2.y())*m_pixels.height()/2; // TODO: Fix the above calculation so that the width is actually // taken into account properly. CqVector3D center = m_currToRaster*sampleRegion.c; TqFloat sWidthOn2 = 0.5*(sampleOpts.sBlur()*m_pixels.width()); TqFloat tWidthOn2 = 0.5*(sampleOpts.tBlur()*m_pixels.height()); SqFilterSupport support( lround(center.x()-sWidthOn2), lround(center.x()+sWidthOn2) + 1, lround(center.y()-tWidthOn2), lround(center.y()+tWidthOn2) + 1); // percentage closer accumulator CqPcfAccum<CqConstFilter, CqConstDepthApprox> accumulator( filterWeights, depthFunc, sampleOpts.startChannel(), sampleOpts.biasLow(), sampleOpts.biasHigh(), outSamps); // accumulate occlusion over the filter support. filterTextureNowrapStochastic(accumulator, m_pixels, support, numSamples); }
/** 3D float Perlin periodic noise. */ TqFloat CqNoise::FGPNoise3( const CqVector3D& v, const CqVector3D& pv ) { TqFloat x, y, z; TqFloat pfx, pfy, pfz; TqInt px, py, pz; x = v.x(); y = v.y(); z = v.z(); pfx = pv.x() + 0.5f; // Temp variables to avoid having the FASTFLOOR() macro pfy = pv.y() + 0.5f; // expand to something that is difficult to optimise. pfz = pv.z() + 0.5f; // (An inline function fastfloor() would be nicer.) px = FASTFLOOR( pfx ); py = FASTFLOOR( pfy ); pz = FASTFLOOR( pfz ); return ( 0.5f * ( 1.0f + CqNoise1234::pnoise( x, y, z, px, py, pz ) ) ); }
/** 4D float Perlin periodic noise. */ TqFloat CqNoise::FGPNoise4( const CqVector3D& v, TqFloat t, const CqVector3D& pv, TqFloat pft ) { TqFloat x, y, z; TqFloat pfx, pfy, pfz; TqInt px, py, pz, pt; x = v.x(); y = v.y(); z = v.z(); pfx = pv.x() + 0.5f; // Temp variables to avoid having the FASTFLOOR() macro pfy = pv.y() + 0.5f; // expand to something that is difficult to optimise. pfz = pv.z() + 0.5f; pft = pft + 0.5f; px = FASTFLOOR( pfx ); py = FASTFLOOR( pfy ); pz = FASTFLOOR( pfz ); pt = FASTFLOOR( pft ); return ( 0.5f * ( 1.0f + CqNoise1234::pnoise( x, y, z, t, px, py, pz, pt ) ) ); }
/** Vector-valued 3D Perlin periodic noise. */ CqVector3D CqNoise::PGPNoise3( const CqVector3D& v, const CqVector3D& pv ) { TqFloat x, y, z; TqFloat a, b, c; TqFloat pfx, pfy, pfz; TqInt px, py, pz; x = v.x(); y = v.y(); z = v.z(); pfx = pv.x() + 0.5f; // Temp variables to avoid having the FASTFLOOR() macro pfy = pv.y() + 0.5f; // expand to something that is difficult to optimise. pfz = pv.z() + 0.5f; // This might seem stupid, but it *is* actually faster. px = FASTFLOOR(pfx); py = FASTFLOOR(pfy); pz = FASTFLOOR(pfz); a = 0.5f * ( 1.0f + CqNoise1234::pnoise( x, y, z, px, py, pz ) ); b = 0.5f * ( 1.0f + CqNoise1234::pnoise( x + O1x, y + O1y, z + O1z, px, py, pz ) ); c = 0.5f * ( 1.0f + CqNoise1234::pnoise( x + O2x, y + O2y, z + O2z, px, py, pz ) ); return ( CqVector3D ( a, b, c ) ); }
/** \brief Create a view from imageNum of the provided file. * * \param file - file from which to read the image data. * \param imageNum - subimage number for this view in the input file * \param currToWorld - current -> world transformation matrix. */ CqShadowView(const boost::shared_ptr<IqTiledTexInputFile>& file, TqInt imageNum, const CqMatrix& currToWorld) : m_currToLight(), m_currToRaster(), m_currToRasterVec(), m_viewDirec(), m_pixels(file, imageNum) { // TODO refactor with CqShadowSampler, also refactor this function, // since it's a bit unweildly... if(!file) AQSIS_THROW_XQERROR(XqInternal, EqE_NoFile, "Cannot construct shadow map from NULL file handle"); const CqTexFileHeader& header = file->header(imageNum); if(header.channelList().sharedChannelType() != Channel_Float32) AQSIS_THROW_XQERROR(XqBadTexture, EqE_BadFile, "Shadow maps must hold 32-bit floating point data"); // Get matrix which transforms the sample points to the light // camera coordinates. const CqMatrix* worldToLight = header.findPtr<Attr::WorldToCameraMatrix>(); if(!worldToLight) { AQSIS_THROW_XQERROR(XqBadTexture, EqE_BadFile, "No world -> camera matrix found in file \"" << file->fileName() << "\""); } m_currToLight = (*worldToLight) * currToWorld; // Get matrix which transforms the sample points to texture coordinates. const CqMatrix* worldToLightScreen = header.findPtr<Attr::WorldToScreenMatrix>(); if(!worldToLightScreen) { AQSIS_THROW_XQERROR(XqBadTexture, EqE_BadFile, "No world -> screen matrix found in file \"" << file->fileName() << "\""); } m_currToRaster = (*worldToLightScreen) * currToWorld; // worldToLightScreen transforms world coordinates to "screen" coordinates, // ie, onto the 2D box [-1,1]x[-1,1]. We instead want texture coordinates, // which correspond to the box [0,width]x[0,height]. In // addition, the direction of increase of the y-axis should be // swapped, since texture coordinates define the origin to be in // the top left of the texture rather than the bottom right. m_currToRaster.Translate(CqVector3D(1,-1,0)); m_currToRaster.Scale(0.5f, -0.5f, 1); // Transform the light origin to "current" space to use // when checking the visibility of a point. m_lightPos = m_currToLight.Inverse()*CqVector3D(0,0,0); // Transform the normal (0,0,1) in light space into a normal in // "current" space. The appropriate matrix is the inverse of the // cam -> light normal transformation, which itself is the inverse // transpose of currToLightVec. CqMatrix currToLightVec = m_currToLight; currToLightVec[3][0] = 0; currToLightVec[3][1] = 0; currToLightVec[3][2] = 0; m_viewDirec = currToLightVec.Transpose()*CqVector3D(0,0,1); m_viewDirec.Unit(); }
/** * Calculates bounds for a CqCurve. * * NOTE: This method makes the same assumptions as * CqSurfacePatchBicubic::Bound() does about the convex-hull property of the * curve. This is fine most of the time, but the user can specify basis * matrices like Catmull-Rom, which are non-convex. * * FIXME: Make sure that all hulls which reach this method are convex! * * @return CqBound object containing the bounds. */ void CqCurve::Bound(CqBound* bound) const { // Get the boundary in camera space. CqVector3D vecA( FLT_MAX, FLT_MAX, FLT_MAX ); CqVector3D vecB( -FLT_MAX, -FLT_MAX, -FLT_MAX ); TqFloat maxCameraSpaceWidth = 0; TqUint nWidthParams = cVarying(); for ( TqUint i = 0; i < ( *P() ).Size(); i++ ) { // expand the boundary if necessary to accomodate the // current vertex CqVector3D vecV = vectorCast<CqVector3D>(P()->pValue( i )[0]); if ( vecV.x() < vecA.x() ) vecA.x( vecV.x() ); if ( vecV.y() < vecA.y() ) vecA.y( vecV.y() ); if ( vecV.x() > vecB.x() ) vecB.x( vecV.x() ); if ( vecV.y() > vecB.y() ) vecB.y( vecV.y() ); if ( vecV.z() < vecA.z() ) vecA.z( vecV.z() ); if ( vecV.z() > vecB.z() ) vecB.z( vecV.z() ); // increase the maximum camera space width of the curve if // necessary if ( i < nWidthParams ) { TqFloat camSpaceWidth = width()->pValue( i )[0]; if ( camSpaceWidth > maxCameraSpaceWidth ) { maxCameraSpaceWidth = camSpaceWidth; } } } // increase the size of the boundary by half the width of the // curve in camera space vecA -= ( maxCameraSpaceWidth / 2.0 ); vecB += ( maxCameraSpaceWidth / 2.0 ); bound->vecMin() = vecA; bound->vecMax() = vecB; AdjustBoundForTransformationMotion( bound ); }
/** \brief Create a view from imageNum of the provided file. * * \param file - file from which to read the image data. * \param imageNum - subimage number for this view in the input file * \param currToWorld - current -> world transformation matrix. */ CqOccView(const boost::shared_ptr<IqTiledTexInputFile>& file, TqInt imageNum, const CqMatrix& currToWorld) : m_currToLight(), m_currToRaster(), m_currToRasterVec(), m_negViewDirec(), m_pixels(file, imageNum) { // TODO refactor with CqShadowSampler, also refactor this function, // since it's a bit unweildly... if(!file) AQSIS_THROW_XQERROR(XqInternal, EqE_NoFile, "Cannot construct shadow map from NULL file handle"); const CqTexFileHeader& header = file->header(imageNum); if(header.channelList().sharedChannelType() != Channel_Float32) AQSIS_THROW_XQERROR(XqBadTexture, EqE_BadFile, "Shadow maps must hold 32-bit floating point data"); // Get matrix which transforms the sample points to the light // camera coordinates. const CqMatrix* worldToLight = header.findPtr<Attr::WorldToCameraMatrix>(); if(!worldToLight) { AQSIS_THROW_XQERROR(XqBadTexture, EqE_BadFile, "No world -> camera matrix found in file \"" << file->fileName() << "\""); } m_currToLight = (*worldToLight) * currToWorld; // Get matrix which transforms the sample points to texture coordinates. const CqMatrix* worldToLightScreen = header.findPtr<Attr::WorldToScreenMatrix>(); if(!worldToLightScreen) { AQSIS_THROW_XQERROR(XqBadTexture, EqE_BadFile, "No world -> screen matrix found in file \"" << file->fileName() << "\""); } m_currToRaster = (*worldToLightScreen) * currToWorld; // worldToLightScreen transforms world coordinates to "screen" coordinates, // ie, onto the 2D box [-1,1]x[-1,1]. We instead want texture coordinates, // which correspond to the box [0,width]x[0,height]. In // addition, the direction of increase of the y-axis should be // swapped, since texture coordinates define the origin to be in // the top left of the texture rather than the bottom right. m_currToRaster.Translate(CqVector3D(1,-1,0)); m_currToRaster.Scale(0.5f*header.width(), -0.5f*header.height(), 1); // This extra translation is by half a pixel width - it moves the // raster coordinates m_currToRaster.Translate(CqVector3D(-0.5,-0.5,0)); // Convert current -> texture transformation into a vector // transform rather than a point transform. // TODO: Put this stuff into the CqMatrix class? m_currToRasterVec = m_currToRaster; m_currToRasterVec[3][0] = 0; m_currToRasterVec[3][1] = 0; m_currToRasterVec[3][2] = 0; // This only really makes sense when the matrix is affine rather // than projective, ie, the last column is (0,0,0,h) // // TODO: Investigate whether this is really correct. // assert(m_currToRasterVec[0][3] == 0); // assert(m_currToRasterVec[1][3] == 0); // assert(m_currToRasterVec[2][3] == 0); m_currToRasterVec[0][3] = 0; m_currToRasterVec[1][3] = 0; m_currToRasterVec[2][3] = 0; // Transform the normal (0,0,1) in light space into a normal in // "current" space. The appropriate matrix is the inverse of the // cam -> light normal transformation, which itself is the inverse // transpose of currToLightVec. CqMatrix currToLightVec = m_currToLight; currToLightVec[3][0] = 0; currToLightVec[3][1] = 0; currToLightVec[3][2] = 0; m_negViewDirec = currToLightVec.Transpose()*CqVector3D(0,0,-1); m_negViewDirec.Unit(); }
void CqOcclusionSampler::sample(const Sq3DSamplePllgram& samplePllgram, const CqVector3D& normal, const CqShadowSampleOptions& sampleOpts, TqFloat* outSamps) const { assert(sampleOpts.numChannels() == 1); // Unit normal indicating the hemisphere to sample for occlusion. CqVector3D N = normal; N.Unit(); const TqFloat sampNumMult = 4.0 * sampleOpts.numSamples() / m_maps.size(); // Accumulate the total occlusion over all directions. Here we use an // importance sampling approach: we decide how many samples each map should // have based on it's relative importance as measured by the map weight. TqFloat totOcc = 0; TqInt totNumSamples = 0; TqFloat maxWeight = 0; TqViewVec::const_iterator maxWeightMap = m_maps.begin(); for(TqViewVec::const_iterator map = m_maps.begin(), end = m_maps.end(); map != end; ++map) { TqFloat weight = (*map)->weight(N); if(weight > 0) { // Compute the number of samples to use. Assuming that the shadow // maps are spread evenly over the sphere, we have an area of // // 4*PI / m_maps.size() // // steradians per map. The density of sample points per steradian // should be // // sampleOpts.numSamples() * weight / PI // // Therefore the expected number of samples per map is TqFloat numSampFlt = sampNumMult*weight; // This isn't an integer though, so we take the floor, TqInt numSamples = lfloor(numSampFlt); // TODO: Investigate performance impact of using RandomFloat() here. if(m_random.RandomFloat() < numSampFlt - numSamples) { // And increment with a probability equal to the extra fraction // of samples that the current map should have. ++numSamples; } if(numSamples > 0) { // Compute amount of occlusion from the current view. TqFloat occ = 0; (*map)->sample(samplePllgram, sampleOpts, numSamples, &occ); // Accumulate into total occlusion and weight. totOcc += occ*numSamples; totNumSamples += numSamples; } if(weight > maxWeight) { maxWeight = weight; maxWeightMap = map; } } } // The algorithm above sometimes results in no samples being computed for // low total sample numbers. Here we attempt to allow very small numbers // of samples to be useful by sampling the most highly weighted map if no // samples have been taken if(totNumSamples == 0 && maxWeight > 0) { TqFloat occ = 0; (*maxWeightMap)->sample(samplePllgram, sampleOpts, 1, &occ); totOcc += occ; totNumSamples += 1; } // Normalize the sample *outSamps = totOcc / totNumSamples; }