void Plot(int _roughnessIndex, int _row, int _column, ImageFile _image, PanelOutput _panel, Label _label) { _image.Clear(float4.One); // Determine range first int W = m_results.GetLength(0); float[] values = new float[W]; float min = float.MaxValue; float max = -float.MaxValue; for (int X = 0; X < W; X++) { LTC ltc = m_results[_roughnessIndex, X]; double term = ltc.invM[_row, _column]; term /= ltc.invM[1, 1]; // Normalize by central term values[X] = (float)term; min = Math.Min(min, values[X]); max = Math.Max(max, values[X]); } float2 rangeY = float2.UnitY; rangeY.x = Math.Min(rangeY.x, min); rangeY.y = Math.Max(rangeY.y, max); // Plot graph and update UI float2 rangeX = new float2(0, 1); _image.PlotGraph(new float4(0, 0, 0, 1), rangeX, rangeY, ( float _X ) => { int X = Math.Min(W - 1, (int)(W * _X)); return(values[X]); }); _image.PlotAxes(float4.UnitW, rangeX, rangeY, 0.1f, 0.1f * (rangeY.y - rangeY.x)); _label.Text = "Coefficient m" + _row + "" + _column + " - Range [" + rangeY.x + ", " + rangeY.y + "]"; _panel.m_bitmap = _image.AsBitmap; _panel.Refresh(); }
void TestBuildImage( BUILD_TESTS _type ) { try { switch ( _type ) { case BUILD_TESTS.FILL_PIXEL: // Write pixel per pixel m_imageFile = new ImageFile( 378, 237, ImageFile.PIXEL_FORMAT.RGB16, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) ); for ( uint Y=0; Y < m_imageFile.Height; Y++ ) { for ( uint X=0; X < m_imageFile.Width; X++ ) { float R = (float) (1+X) / m_imageFile.Width; float G = (float) (1+Y) / m_imageFile.Height; m_imageFile[X,Y] = new float4( R, G, 1.0f-0.5f*(R+G), 1 ); } } break; case BUILD_TESTS.FILL_SCANLINE: // Write scanline per scanline m_imageFile = new ImageFile( 378, 237, ImageFile.PIXEL_FORMAT.RGB16, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) ); float4[] scanline = new float4[m_imageFile.Width]; for ( uint Y=0; Y < m_imageFile.Height; Y++ ) { for ( uint X=0; X < m_imageFile.Width; X++ ) { float R = 1.0f - (float) (1+X) / m_imageFile.Width; float G = (float) (1+Y) / m_imageFile.Height; scanline[X].Set( R, G, 1.0f-0.5f*(R+G), 1 ); } m_imageFile.WriteScanline( Y, scanline ); } break; // Buddhabrot case BUILD_TESTS.ADDITIVE_BUDDHABROT: { m_imageFile = new ImageFile( 378, 237, ImageFile.PIXEL_FORMAT.RGB16, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) ); uint W = m_imageFile.Width; uint H = m_imageFile.Height; float2 Z, Z0; int iterations = 50; float4 inc = (1.0f / iterations) * float4.One; float zoom = 2.0f; float invZoom = 1.0f / zoom; #if DIRECT_WRITE // Either directly accumulate to image for ( uint Y=0; Y < H; Y++ ) { Z0.y = zoom * (Y - 0.5f * H) / H; for ( uint X=0; X < W; X++ ) { Z0.x = zoom * (X - 0.5f * W) / H; Z = Z0; for ( int i=0; i < iterations; i++ ) { Z.Set( Z.x*Z.x - Z.y*Z.y + Z0.x, 2.0f * Z.x * Z.y + Z0.y ); int Nx = (int) (invZoom * Z.x * H + 0.5f * W); int Ny = (int) (invZoom * Z.y * H + 0.5f * H); if ( Nx >= 0 && Nx < W && Ny >= 0 && Ny < H ) { // float4 tagada = (float4) m_imageFile[(uint)Nx,(uint)Ny]; // tagada += inc; // m_imageFile[(uint)Nx,(uint)Ny] = tagada; m_imageFile.Add( (uint)Nx, (uint)Ny, inc ); } } } } #else // Or accumulate to a temp array and write result (this is obviously faster!) float[,] accumulators = new float[W,H]; for ( uint Y=0; Y < H; Y++ ) { Z0.y = zoom * (Y - 0.5f * H) / H; for ( uint X=0; X < W; X++ ) { Z0.x = zoom * (X - 0.5f * W) / H; Z = Z0; for ( int i=0; i < iterations; i++ ) { Z.Set( Z.x*Z.x - Z.y*Z.y + Z0.x, 2.0f * Z.x * Z.y + Z0.y ); int Nx = (int) (invZoom * Z.x * H + 0.5f * W); int Ny = (int) (invZoom * Z.y * H + 0.5f * H); if ( Nx >= 0 && Nx < W && Ny >= 0 && Ny < H ) accumulators[Nx, Ny] += inc.x; } } } float4 temp = new float4(); for ( uint Y=0; Y < H; Y++ ) { for ( uint X=0; X < W; X++ ) { float a = accumulators[X,Y]; temp.Set( a, a, a, 1 ); m_imageFile[X,Y] = temp; } } #endif } break; case BUILD_TESTS.STRESS_BUILD: ImageFile.PIXEL_FORMAT[] formats = new ImageFile.PIXEL_FORMAT[] { ImageFile.PIXEL_FORMAT.R8, ImageFile.PIXEL_FORMAT.RG8, ImageFile.PIXEL_FORMAT.RGB8, ImageFile.PIXEL_FORMAT.RGBA8, ImageFile.PIXEL_FORMAT.R16, // ImageFile.PIXEL_FORMAT.RG16, ImageFile.PIXEL_FORMAT.RGB16, ImageFile.PIXEL_FORMAT.RGBA16, ImageFile.PIXEL_FORMAT.R16F, ImageFile.PIXEL_FORMAT.RG16F, ImageFile.PIXEL_FORMAT.RGB16F, ImageFile.PIXEL_FORMAT.RGBA16F, ImageFile.PIXEL_FORMAT.R32F, // ImageFile.PIXEL_FORMAT.RG32F, ImageFile.PIXEL_FORMAT.RGB32F, ImageFile.PIXEL_FORMAT.RGBA32F, }; Random RNG = new Random( 1 ); for ( int i=0; i < 1000; i++ ) { uint W = 100 + (uint) (1000 * RNG.NextDouble()); uint H = 100 + (uint) (1000 * RNG.NextDouble()); ImageFile.PIXEL_FORMAT format = formats[RNG.Next( formats.Length-1 )]; m_imageFile = new ImageFile( W, H,format, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) ); m_imageFile.Dispose(); } m_imageFile = new ImageFile( 1000, 1000, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) ); m_imageFile.Clear( new float4( 0, 1, 0.2f, 1 ) ); break; } panelBuild.Bitmap = m_imageFile.AsBitmap; } catch ( Exception _e ) { MessageBox.Show( "Error: " + _e.Message ); } }
protected void TestConvertLDR2HDR( System.IO.FileInfo[] _LDRImageFileNames, bool _responseCurveOnly, bool _RAW ) { try { // Load the LDR images List< ImageFile > LDRImages = new List< ImageFile >(); foreach ( System.IO.FileInfo LDRImageFileName in _LDRImageFileNames ) LDRImages.Add( new ImageFile( LDRImageFileName ) ); // Retrieve the shutter speeds List< float > shutterSpeeds = new List< float >(); foreach ( ImageFile LDRImage in LDRImages ) { shutterSpeeds.Add( LDRImage.Metadata.ExposureTime ); } // Retrieve filter type Bitmap.FILTER_TYPE filterType = Bitmap.FILTER_TYPE.NONE; if ( radioButtonFilterNone.Checked ) { filterType = Bitmap.FILTER_TYPE.NONE; } else if ( radioButtonFilterGaussian.Checked ) { filterType = Bitmap.FILTER_TYPE.SMOOTHING_GAUSSIAN; } else if ( radioButtonFilterGaussian2Pass.Checked ) { filterType = Bitmap.FILTER_TYPE.SMOOTHING_GAUSSIAN_2_PASSES; } else if ( radioButtonFilterTent.Checked ) { filterType = Bitmap.FILTER_TYPE.SMOOTHING_TENT; } else if ( radioButtonFilterCurveFitting.Checked ) { filterType = Bitmap.FILTER_TYPE.GAUSSIAN_PLUS_CURVE_FITTING; } // Check EXR save is working! // ImageFile pipo = new ImageFile(); // pipo.ConvertFrom( LDRImages[0], ImageFile.PIXEL_FORMAT.RGB32F ); // pipo.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromJPG\Result.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_DEFAULT ); // Check bitmap->tone mapped image file is working // { // Bitmap tempBitmap = new Bitmap(); // List< float > responseCurve = new List< float >( 256 ); // for ( int i=0; i < 256; i++ ) // responseCurve.Add( (float) (Math.Log( (1+i) / 256.0 ) / Math.Log(2)) ); // tempBitmap.LDR2HDR( new ImageFile[] { LDRImages[4] }, new float[] { 1.0f }, responseCurve, 1.0f ); // // ImageFile tempHDR = new ImageFile(); // tempBitmap.ToImageFile( tempHDR, new ColorProfile( ColorProfile.STANDARD_PROFILE.LINEAR ) ); // // ImageFile tempToneMappedHDR = new ImageFile(); // tempToneMappedHDR.ToneMapFrom( tempHDR,( float3 _HDRColor, ref float3 _LDRColor ) => { // // Just do gamma un-correction, don't care about actual HDR range... // _LDRColor.x = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.x ), 1.0f / 2.2f ); // Here we need to clamp negative values that we sometimes get in EXR format // _LDRColor.y = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.y ), 1.0f / 2.2f ); // (must be coming from the log encoding I suppose) // _LDRColor.z = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.z ), 1.0f / 2.2f ); // } ); // // panel1.Bitmap = tempToneMappedHDR.AsBitmap; // return; // } ////////////////////////////////////////////////////////////////////////////////////////////// // Build the HDR device-independent bitmap // uint bitsPerPixel = _RAW ? 12U : 8U; uint bitsPerPixel = _RAW ? 8U : 8U; float quality = _RAW ? 3.0f : 3.0f; Bitmap.HDRParms parms = new Bitmap.HDRParms() { _inputBitsPerComponent = bitsPerPixel, _luminanceFactor = 1.0f, _curveSmoothnessConstraint = 1.0f, _quality = quality, _responseCurveFilterType = filterType }; ImageUtility.Bitmap HDRImage = new ImageUtility.Bitmap(); // HDRImage.LDR2HDR( LDRImages.ToArray(), shutterSpeeds.ToArray(), parms ); // Compute response curve List< float > responseCurve = new List< float >(); Bitmap.ComputeCameraResponseCurve( LDRImages.ToArray(), shutterSpeeds.ToArray(), parms._inputBitsPerComponent, parms._curveSmoothnessConstraint, parms._quality, responseCurve ); // Filter List< float > responseCurve_filtered = new List< float >(); // Bitmap.FilterCameraResponseCurve( responseCurve, responseCurve_filtered, Bitmap.FILTER_TYPE.CURVE_FITTING ); Bitmap.FilterCameraResponseCurve( responseCurve, responseCurve_filtered, filterType ); // using ( System.IO.FileStream S = new System.IO.FileInfo( "../../responseCurve3.float" ).Create() ) // using ( System.IO.BinaryWriter W = new System.IO.BinaryWriter( S ) ) { // for ( int i=0; i < 256; i++ ) // W.Write( responseCurve[i] ); // } // Write info string info = "Exposures:\r\n"; foreach ( float shutterSpeed in shutterSpeeds ) info += " " + shutterSpeed + "s + "; info += "\r\nLog2 exposures (EV):\r\n"; foreach ( float shutterSpeed in shutterSpeeds ) info += " " + (float) (Math.Log( shutterSpeed ) / Math.Log(2)) + "EV + "; info += "\r\n\r\n"; if ( _responseCurveOnly ) { ////////////////////////////////////////////////////////////////////////////////////////////// // Render the response curve as a graph ImageFile tempCurveBitmap = new ImageFile( 1024, 768, ImageFile.PIXEL_FORMAT.RGB8, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) ); int responseCurveSizeMax = responseCurve.Count-1; float2 rangeX = new float2( 0, responseCurveSizeMax+1 ); float2 rangeY = new float2( 0, 500 ); tempCurveBitmap.Clear( new float4( 1, 1, 1, 1 ) ); // tempCurveBitmap.PlotGraphAutoRangeY( red, rangeX, ref rangeY, ( float x ) => { tempCurveBitmap.PlotGraph( red, rangeX, rangeY, ( float x ) => { int i0 = (int) Math.Min( responseCurveSizeMax, Math.Floor( x ) ); int i1 = (int) Math.Min( responseCurveSizeMax, i0+1 ); float g0 = responseCurve[i0]; float g1 = responseCurve[i1]; float t = x - i0; // return g0 + (g1-g0) * t; return (float) Math.Pow( 2.0f, g0 + (g1-g0) * t ); } ); tempCurveBitmap.PlotGraph( blue, rangeX, rangeY, ( float x ) => { int i0 = (int) Math.Min( responseCurveSizeMax, Math.Floor( x ) ); int i1 = (int) Math.Min( responseCurveSizeMax, i0+1 ); float g0 = responseCurve_filtered[i0]; float g1 = responseCurve_filtered[i1]; float t = x - i0; // return g0 + (g1-g0) * t; return (float) Math.Pow( 2.0f, g0 + (g1-g0) * t ); } ); // tempCurveBitmap.PlotAxes( black, rangeX, rangeY, 8, 2 ); info += "• Linear range Y = [" + rangeY.x + ", " + rangeY.y + "]\r\n"; rangeY = new float2( -4, 4 ); tempCurveBitmap.PlotLogGraphAutoRangeY( black, rangeX, ref rangeY, ( float x ) => { // tempCurveBitmap.PlotLogGraph( black, rangeX, rangeY, ( float x ) => { int i0 = (int) Math.Min( responseCurveSizeMax, Math.Floor( x ) ); int i1 = (int) Math.Min( responseCurveSizeMax, i0+1 ); float g0 = responseCurve[i0]; float g1 = responseCurve[i1]; float t = x - i0; // return g0 + (g1-g0) * t; return (float) Math.Pow( 2.0f, g0 + (g1-g0) * t ); }, -1.0f, 2.0f ); tempCurveBitmap.PlotLogGraph( blue, rangeX, rangeY, ( float x ) => { // tempCurveBitmap.PlotLogGraph( black, rangeX, rangeY, ( float x ) => { int i0 = (int) Math.Min( responseCurveSizeMax, Math.Floor( x ) ); int i1 = (int) Math.Min( responseCurveSizeMax, i0+1 ); float g0 = responseCurve_filtered[i0]; float g1 = responseCurve_filtered[i1]; float t = x - i0; // return g0 + (g1-g0) * t; return (float) Math.Pow( 2.0f, g0 + (g1-g0) * t ); }, -1.0f, 2.0f ); tempCurveBitmap.PlotLogAxes( black, rangeX, rangeY, -16, 2 ); info += "• Log2 range Y = [" + rangeY.x + ", " + rangeY.y + "]\r\n"; panelOutputHDR.Bitmap = tempCurveBitmap.AsBitmap; } else { ////////////////////////////////////////////////////////////////////////////////////////////// // Recompose the HDR image HDRImage.LDR2HDR( LDRImages.ToArray(), shutterSpeeds.ToArray(), responseCurve_filtered, 1.0f ); // Display as a tone-mapped bitmap ImageFile tempHDR = new ImageFile(); HDRImage.ToImageFile( tempHDR, new ColorProfile( ColorProfile.STANDARD_PROFILE.LINEAR ) ); if ( _RAW ) { tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromRAW\Result.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_DEFAULT ); tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromRAW\Result_B44LC.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_B44 | ImageFile.SAVE_FLAGS.SF_EXR_LC ); tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromRAW\Result_noLZW.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_NONE ); } else { tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromJPG\Result.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_DEFAULT ); tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromJPG\Result_B44LC.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_B44 | ImageFile.SAVE_FLAGS.SF_EXR_LC ); tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromJPG\Result_noLZW.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_NONE ); } ImageFile tempToneMappedHDR = new ImageFile(); tempToneMappedHDR.ToneMapFrom( tempHDR,( float3 _HDRColor, ref float3 _LDRColor ) => { // Just do gamma un-correction, don't care about actual HDR range... _LDRColor.x = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.x ), 1.0f / 2.2f ); // Here we need to clamp negative values that we sometimes get in EXR format _LDRColor.y = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.y ), 1.0f / 2.2f ); // (must be coming from the log encoding I suppose) _LDRColor.z = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.z ), 1.0f / 2.2f ); } ); panelOutputHDR.Bitmap = tempToneMappedHDR.AsBitmap; } textBoxHDR.Text = info; } catch ( Exception _e ) { MessageBox.Show( "Error: " + _e.Message ); // Show debug image // panelLoad.Bitmap = Bitmap.DEBUG.AsBitmap; } }
void UpdateGraph1D() { double time = (DateTime.Now - m_startTime).TotalSeconds; if (checkBoxGPU.Checked) { UpdateGraph1D_GPU(time); return; } TestTransform1D(time); m_image.Clear(float4.One); float2 rangeX = new float2(0.0f, SIGNAL_SIZE); float2 rangeY = new float2(-1, 1); // Plot input signal if (checkBoxShowInput.Checked) { // m_image.PlotGraphAutoRangeY( m_black, rangeX, ref rangeY, ( float x ) => { m_image.PlotGraph(m_black, rangeX, rangeY, ( float x ) => { int X = Math.Max(0, Math.Min(SIGNAL_SIZE - 1, (int)x)); return((float)m_signalSource[X].r); }); } // Plot reconstructed signals (Real and Imaginary parts) if (checkBoxShowReconstructedSignal.Checked) { m_image.PlotGraph(m_red, rangeX, rangeY, ( float x ) => { int X = Math.Max(0, Math.Min(SIGNAL_SIZE - 1, (int)x)); return((float)m_signalReconstructed[X].r); }); m_image.PlotGraph(m_blue, rangeX, rangeY, ( float x ) => { int X = Math.Max(0, Math.Min(SIGNAL_SIZE - 1, (int)x)); return((float)m_signalReconstructed[X].i); }); } m_image.PlotAxes(m_black, rangeX, rangeY, 16.0f, 0.1f); ////////////////////////////////////////////////////////////////////////// // Render spectrum as (Real=Red, Imaginary=Blue) vertical lines for each frequency float2 cornerMin = m_image.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(rangeX.x, -1.0f)); float2 cornerMax = m_image.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(rangeX.y, +1.0f)); float2 delta = cornerMax - cornerMin; float zeroY = cornerMin.y + 0.5f * delta.y; float2 Xr0 = new float2(0, zeroY); float2 Xr1 = new float2(0, 0); float2 Xi0 = new float2(0, zeroY); float2 Xi1 = new float2(0, 0); float scale = 10.0f; float4 spectrumColorRe = new float4(1, 0.25f, 0, 1); float4 spectrumColorIm = new float4(0, 0.5f, 1, 1); int size = m_spectrum.Length; int halfSize = size >> 1; for (int i = 0; i < m_spectrum.Length; i++) { float X = cornerMin.x + i * delta.x / m_spectrum.Length; // int frequencyIndex = i; // Show spectrum as output by FFT int frequencyIndex = (i + halfSize) % size; // Show offset spectrum with DC term in the middle Xr0.x = X; Xr1.x = X; Xr1.y = cornerMin.y + 0.5f * (scale * (float)m_spectrum[frequencyIndex].r + 1.0f) * delta.y; Xi0.x = X + 1; Xi1.x = X + 1; Xi1.y = cornerMin.y + 0.5f * (scale * (float)m_spectrum[frequencyIndex].i + 1.0f) * delta.y; m_image.DrawLine(spectrumColorRe, Xr0, Xr1); m_image.DrawLine(spectrumColorIm, Xi0, Xi1); } imagePanel.Bitmap = m_image.AsBitmap; }
void CreateNoiseSpectrum(Complex[,] _spectrum) { uint size = m_handMadeBlueNoise.Width; uint halfSize = size >> 1; double noiseScale = floatTrackbarControlScale.Value; double noiseBias = floatTrackbarControlOffset.Value; double radialOffset = floatTrackbarControlRadialOffset.Value; double radialScale = floatTrackbarControlRadialScale.Value; double distributionPower = floatTrackbarControlDistributionPower.Value; double sigma = floatTrackbarControlSigma.Value; Complex Cnoise = new Complex(); for (uint Y = 0; Y < m_handMadeBlueNoise.Height; Y++) { uint Yoff = (Y + halfSize) & (size - 1); int Yrel = (int)Y - (int)halfSize; for (uint X = 0; X < m_handMadeBlueNoise.Width; X++) { uint Xoff = (X + halfSize) & (size - 1); int Xrel = (int)X - (int)halfSize; // Fetch "center noise" from blue noise // Cnoise = output[(halfSize>>1) + (X & (halfSize-1)), (halfSize>>1) + (Y & (halfSize-1))]; // Cnoise /= maxAverage; // Center noise is already factored by the maximum average so we "renormalize it" // Apply simple uniform noise Cnoise.r = noiseScale * (Math.Pow(SimpleRNG.GetUniform(), distributionPower) - noiseBias); Cnoise.i = noiseScale * (Math.Pow(SimpleRNG.GetUniform(), distributionPower) - noiseBias); // Cnoise.r = noiseScale * (SimpleRNG.GetNormal() - noiseBias); // Cnoise.i = noiseScale * (SimpleRNG.GetNormal() - noiseBias); // Cnoise.r = noiseScale * (SimpleRNG.GetLaplace( 0, sigma ) - noiseBias); // Cnoise.i = noiseScale * (SimpleRNG.GetLaplace( 0, sigma ) - noiseBias); // Cnoise.r = 2.0 * SimpleRNG.GetNormal( 0, 1 ) - 1.0; // Cnoise.i = 2.0 * SimpleRNG.GetNormal( 0, 1 ) - 1.0; // Cnoise.r = 1.0; // Cnoise.i = 0.0; // Apply weighting by radial profile int sqRadius = Xrel * Xrel + Yrel * Yrel; // Use averaged radial profile extracted from the noise texture // int radius = (int) Math.Max( 1, Math.Min( halfSize-1, Math.Sqrt( sqRadius ) ) ); // double profileFactor = m_radialSliceAverage_Smoothed[radius].r; //profileFactor *= 2.0; // Use the Mathematica hand-fitted curve double profileFactor = RadialProfile(radialOffset + radialScale * Math.Sqrt(sqRadius) / halfSize); //profileFactor *= 0.75 / 1; //profileFactor *= 3.0; //profileFactor *= Math.Sqrt( 2.0 ); //profileFactor *= 1.1; Cnoise *= profileFactor; //Cnoise = output[Xoff,Yoff]; //Cnoise *= Math.Exp( 0.01 * Math.Max( 0.0, radius - 128 ) ); _spectrum[Xoff, Yoff] = Cnoise; } } _spectrum[0, 0].Set(floatTrackbarControlDC.Value, 0.0); // Central value for constant term #if COMPUTE_RADIAL_SLICE const int BUCKETS_COUNT = 1000; uint[] noiseDistributionHistogram = new uint[BUCKETS_COUNT]; for (uint i = 0; i < m_handMadeBlueNoise.Width * m_handMadeBlueNoise.Height; i++) { double noiseValue = noiseScale * (Math.Pow(SimpleRNG.GetUniform(), distributionPower) - noiseBias); double noiseValue2 = noiseScale * (Math.Pow(SimpleRNG.GetUniform(), distributionPower) - noiseBias); noiseValue = Math.Abs(noiseValue + noiseValue2); noiseValue = Math.Pow(SimpleRNG.GetUniform(), 0.5); noiseValue = SimpleRNG.GetUniform() < 0.5 ? 0.5 * Math.Sqrt(1.0 - noiseValue) : 1.0 - 0.5 * Math.Sqrt(1.0 - noiseValue); int bucketIndex = Math.Min(BUCKETS_COUNT - 1, (int)Math.Floor(noiseValue * BUCKETS_COUNT)); noiseDistributionHistogram[bucketIndex]++; } // for ( uint Y=0; Y < m_handMadeBlueNoise.Height; Y++ ) { // uint Yoff = (Y + halfSize) & (size-1); // int Yrel = (int) Y - (int) halfSize; // for ( uint X=0; X < m_handMadeBlueNoise.Width; X++ ) { // uint Xoff = (X + halfSize) & (size-1); // int Xrel = (int) X - (int) halfSize; // int sqRadius = Xrel*Xrel + Yrel*Yrel; // double radius = Math.Sqrt( sqRadius ) / halfSize; // if ( radius < 0.01f ) // continue; // Avoid central values because of too much imprecision // // double random = Math.Abs( _spectrum[Xoff,Yoff].r ); // double profileAmplitude = RadialProfile( radius ); // double normalizedRandom = random / profileAmplitude; // // int bucketIndex = Math.Min( BUCKETS_COUNT-1, (int) Math.Floor( normalizedRandom * BUCKETS_COUNT ) ); // noiseDistributionHistogram[bucketIndex]++; // } // } m_spectrumNoiseDistribution_Custom.Clear(float4.One); float2 rangeX = new float2(0, 1); float2 rangeY = new float2(0, 4.0f / BUCKETS_COUNT); m_spectrumNoiseDistribution_Custom.PlotGraph(float4.UnitW, rangeX, rangeY, ( float _x ) => { int bucketIndex = Math.Min(BUCKETS_COUNT - 1, (int)Math.Floor(_x * BUCKETS_COUNT)); return((float)noiseDistributionHistogram[bucketIndex] / (m_blueNoise.Width * m_blueNoise.Height)); }); #endif }
protected override void OnLoad(EventArgs e) { base.OnLoad(e); try { #if COMPUTE_RADIAL_SLICE m_blueNoise = new ImageFile(new System.IO.FileInfo(@"Images\Data\512_512\LDR_LLL1_0.png")); // m_blueNoise = new ImageFile( new System.IO.FileInfo( @"Images\BlueNoiseSpectrumCorners256.png" ) ); // m_blueNoise = new ImageFile( new System.IO.FileInfo( @"BlueNoise512x512_VoidAndCluster.png" ) ); // m_blueNoise = new ImageFile( new System.IO.FileInfo( @"BlueNoise256x256_VoidAndCluster.png" ) ); uint size = m_blueNoise.Width; uint halfSize = size >> 1; try { m_device.Init(panelImageSpectrum.Handle, false, false); m_FFT = new SharpMath.FFT.FFT2D_GPU(m_device, size); } catch (Exception) { } if (m_FFT == null) { return; } m_blueNoiseSpectrum = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB)); m_handMadeBlueNoise = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB)); m_handMadeSpectrum = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB)); m_scanline = new float4[m_blueNoise.Width]; ////////////////////////////////////////////////////////////////////////// // Apply FFT to blue noise Complex[,] input = new Complex[m_blueNoise.Width, m_blueNoise.Height]; Complex[,] output = new Complex[m_blueNoise.Width, m_blueNoise.Height]; m_blueNoise.ReadPixels((uint X, uint Y, ref float4 _color) => { input[X, Y].Set(_color.x, 0); }); // for ( uint Y=0; Y < m_blueNoise.Height; Y++ ) { // m_blueNoise.ReadScanline( Y, m_scanline ); // for ( uint X=0; X < m_blueNoise.Width; X++ ) // input[X,Y].Set( m_scanline[X].x, 0 ); // } m_FFT.FFT_Forward(input, output); ////////////////////////////////////////////////////////////////////////// // Build the radial slice average Complex[] radialSliceAverage = new Complex[halfSize]; uint[] radialSliceAverageCounters = new uint[halfSize]; for (uint Y = 0; Y < m_blueNoise.Height; Y++) { uint Yoff = (Y + halfSize) & (size - 1); int Yrel = (int)Y - (int)halfSize; for (uint X = 0; X < m_blueNoise.Width; X++) { uint Xoff = (X + halfSize) & (size - 1); int Xrel = (int)X - (int)halfSize; int sqRadius = Xrel * Xrel + Yrel * Yrel; if (sqRadius > (halfSize - 1) * (halfSize - 1)) { continue; } int radius = (int)Math.Floor(Math.Sqrt(sqRadius)); // radialSliceAverage[radius] += output[Xoff,Yoff]; radialSliceAverage[radius].r += Math.Abs(output[Xoff, Yoff].r); radialSliceAverage[radius].i += Math.Abs(output[Xoff, Yoff].i); radialSliceAverageCounters[radius]++; } } for (int i = 0; i < halfSize; i++) { // radialSliceAverage[i].r = Math.Abs( radialSliceAverage[i].r ); radialSliceAverage[i] *= radialSliceAverageCounters[i] > 0 ? 1.0f / radialSliceAverageCounters[i] : 1.0f; } double minAverage = double.MaxValue; double maxAverage = double.MinValue; for (int i = 0; i < halfSize; i++) { if (radialSliceAverage[i].r < 1e-12) { radialSliceAverage[i].r = 1e-12; } if (radialSliceAverage[i].i < 1e-12) { radialSliceAverage[i].i = 1e-12; } if (i > 1) { minAverage = Math.Min(minAverage, radialSliceAverage[i].r); maxAverage = Math.Max(maxAverage, radialSliceAverage[i].r); } } // Write it to disk using (System.IO.FileStream S = new System.IO.FileInfo("BlueNoiseRadialProfile255.complex").Create()) using (System.IO.BinaryWriter W = new System.IO.BinaryWriter(S)) { for (int i = 1; i < radialSliceAverage.Length; i++) { W.Write(radialSliceAverage[i].r); W.Write(radialSliceAverage[i].i); } } // Smooth it out m_radialSliceAverage_Smoothed = new Complex[halfSize]; Complex average = new Complex(); for (int i = 1; i < halfSize; i++) { average.Zero(); for (int j = -4; j <= 4; j++) { average += radialSliceAverage[Math.Max(1, Math.Min(halfSize - 1, i + j))]; } m_radialSliceAverage_Smoothed[i] = average / 9.0; } ////////////////////////////////////////////////////////////////////////// // Build spectrum noise distribution histogram const int BUCKETS_COUNT = 1000; uint[] noiseDistributionHistogram = new uint[BUCKETS_COUNT]; for (uint Y = 0; Y < m_blueNoise.Height; Y++) { uint Yoff = (Y + halfSize) & (size - 1); int Yrel = (int)Y - (int)halfSize; for (uint X = 0; X < m_blueNoise.Width; X++) { uint Xoff = (X + halfSize) & (size - 1); int Xrel = (int)X - (int)halfSize; int sqRadius = Xrel * Xrel + Yrel * Yrel; double radius = Math.Sqrt(sqRadius) / halfSize; if (radius < 0.01f) { continue; // Avoid central values because of too much imprecision } double random = Math.Abs(output[Xoff, Yoff].r); double profileAmplitude = RadialProfile(radius); double normalizedRandom = random / profileAmplitude; int bucketIndex = Math.Min(BUCKETS_COUNT - 1, (int)Math.Floor(normalizedRandom * BUCKETS_COUNT)); noiseDistributionHistogram[bucketIndex]++; } } // Write histogram to disk using (System.IO.FileStream S = new System.IO.FileInfo("noiseDistribution.float").Create()) using (System.IO.BinaryWriter W = new System.IO.BinaryWriter(S)) { for (int i = 0; i < BUCKETS_COUNT; i++) { W.Write((float)noiseDistributionHistogram[i] / BUCKETS_COUNT); } } ////////////////////////////////////////////////////////////////////////// // Build the images // Initial Blue Noise Spectrum for (uint Y = 0; Y < m_blueNoiseSpectrum.Height; Y++) { uint Yoff = (Y + halfSize) & (size - 1); // Initial blue noise spectrum for (uint X = 0; X < m_blueNoiseSpectrum.Width; X++) { uint Xoff = (X + halfSize) & (size - 1); float R = (float)output[Xoff, Yoff].r; float I = (float)output[Xoff, Yoff].i; R *= 500.0f; I *= 500.0f; R = Math.Abs(R); m_scanline[X].Set(R, R, R, 1.0f); } m_blueNoiseSpectrum.WriteScanline(Y, m_scanline); } // ===================================================== // Average radial slice m_spectrumRadialSlice = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB)); m_spectrumRadialSlice.Clear(float4.One); float2 rangeX = new float2(0, halfSize); // float2 rangeY = new float2( -8, 0 ); // m_spectrumRadialSlice.PlotLogGraphAutoRangeY( float4.UnitW, rangeX, ref rangeY, ( float _x ) => { // m_spectrumRadialSlice.PlotLogGraph( new float4( 0, 0, 0, 1 ), rangeX, rangeY, ( float _x ) => { // return (float) radialSliceAverage[(int) Math.Floor( _x )].r; // }, -1.0f, 10.0f ); // // m_spectrumRadialSlice.PlotLogGraph( new float4( 0, 0.5f, 0, 1 ), rangeX, rangeY, ( float _x ) => { // return (float) RadialProfile( _x / halfSize ); // }, -1.0f, 10.0f ); // m_spectrumRadialSlice.PlotLogGraph( new float4( 0.25f, 0.25f, 0.25f, 1 ), rangeX, rangeY, ( float _x ) => { return (float) radialSliceAverage[(int) Math.Floor( _x )].i; }, -1.0f, 10.0f ); // m_spectrumRadialSlice.PlotLogGraph( new float4( 1, 0, 0, 1 ), rangeX, rangeY, ( float _x ) => { return (float) m_radialSliceAverage_Smoothed[(int) Math.Floor( _x )].r; }, -1.0f, 10.0f ); // m_spectrumRadialSlice.PlotLogGraph( new float4( 0, 0, 1, 1 ), rangeX, rangeY, ( float _x ) => { return (float) m_radialSliceAverage_Smoothed[(int) Math.Floor( _x )].i; }, -1.0f, 10.0f ); // m_spectrumRadialSlice.PlotLogAxes( float4.UnitW, rangeX, rangeY, -16.0f, 10.0f ); float2 rangeY = new float2(0, 0.0005f); m_spectrumRadialSlice.PlotGraph(new float4(0, 0, 0, 1), rangeX, rangeY, ( float _x ) => { return((float)radialSliceAverage[(int)Math.Floor(_x)].r); }); m_spectrumRadialSlice.PlotGraph(new float4(0, 0.5f, 0, 1), rangeX, rangeY, ( float _x ) => { return((float)RadialProfile(_x / halfSize)); }); m_spectrumRadialSlice.PlotGraph(new float4(0.25f, 0.25f, 0.25f, 1), rangeX, rangeY, ( float _x ) => { return((float)radialSliceAverage[(int)Math.Floor(_x)].i); }); m_spectrumRadialSlice.PlotGraph(new float4(1, 0, 0, 1), rangeX, rangeY, ( float _x ) => { return((float)m_radialSliceAverage_Smoothed[(int)Math.Floor(_x)].r); }); m_spectrumRadialSlice.PlotGraph(new float4(0, 0, 1, 1), rangeX, rangeY, ( float _x ) => { return((float)m_radialSliceAverage_Smoothed[(int)Math.Floor(_x)].i); }); m_spectrumRadialSlice.PlotAxes(float4.UnitW, rangeX, rangeY, 16.0f, 1e-4f); // ===================================================== // Noise distribution m_spectrumNoiseDistribution = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB)); m_spectrumNoiseDistribution_Custom = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB)); m_spectrumNoiseDistribution.Clear(float4.One); rangeX = new float2(0, 1); rangeY = new float2(0, 1.0f / BUCKETS_COUNT); m_spectrumNoiseDistribution.PlotGraph(float4.UnitW, rangeX, rangeY, ( float _x ) => { int bucketIndex = Math.Min(BUCKETS_COUNT - 1, (int)Math.Floor(_x * BUCKETS_COUNT)); return((float)noiseDistributionHistogram[bucketIndex] / (m_blueNoise.Width * m_blueNoise.Height)); }); #else // Create our hand made textures const uint NOISE_SIZE = 512; try { m_device.Init(panelImageSpectrum.Handle, false, false); m_FFT = new SharpMath.FFT.FFT2D_GPU(m_device, NOISE_SIZE); } catch (Exception) { } m_handMadeBlueNoise = new ImageFile(NOISE_SIZE, NOISE_SIZE, PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB)); m_handMadeSpectrum = new ImageFile(m_handMadeBlueNoise.Width, m_handMadeBlueNoise.Height, PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB)); // Read radial profile from disk Complex[] radialSliceAverage = new Complex[256]; using (System.IO.FileStream S = new System.IO.FileInfo("BlueNoiseRadialProfile255.complex").OpenRead()) using (System.IO.BinaryReader R = new System.IO.BinaryReader(S)) { for (int i = 1; i < radialSliceAverage.Length; i++) { radialSliceAverage[i].r = R.ReadSingle(); radialSliceAverage[i].i = R.ReadSingle(); } } #endif ////////////////////////////////////////////////////////////////////////// // Build initial blue-noise RebuildNoise(); } catch (Exception _e) { MessageBox.Show(this, "Error during Load() => " + _e.Message, "BlueNoiseGenerator"); } }
void UpdateLevels() { // const uint BUCKETS_PER_LAYER = LEVEL_BUCKETS_COUNT / LAYERS_COUNT; // // float normalizer = 1.0f / (LAYERS_COUNT-1); // uint bucketIndex = 0; // for ( uint layerIndex=0; layerIndex < LAYERS_COUNT; layerIndex++ ) { // for ( uint i=0; i < BUCKETS_PER_LAYER; i++ ) { // float t = (float) i / BUCKETS_PER_LAYER; // m_levels[bucketIndex++] = normalizer * (layerIndex + t); // } // } #if CUBIC_SPLINES float F = 4.0f; float T0 = F * (0 * 1.0f + floatTrackbarControlTangent0.Value); float T1_in = F * (0 * 1.0f + floatTrackbarControlTangent1.Value); float T1_out = checkBoxSplit1.Checked ? F * (0 * 1.0f + floatTrackbarControlTangent1_Out.Value) : T1_in; float T2_in = F * (0 * 1.0f + floatTrackbarControlTangent2.Value); float T2_out = checkBoxSplit2.Checked ? F * (0 * 1.0f + floatTrackbarControlTangent2_Out.Value) : T2_in; float T3 = F * (0 * 1.0f + floatTrackbarControlTangent3.Value); float4[] hermites = new float4[6]; // hermites[0].Set( 0.0f, T0, 1.0f, T1 ); // hermites[1].Set( 0.0f, T1, 1.0f, T2 ); // hermites[2].Set( 0.0f, T2, 1.0f, T3 ); hermites[0].Set(0.0f, T0, 0.5f, -0.5f * (T0 - T1_in)); hermites[1].Set(0.5f, -0.5f * (T0 - T1_in), 1.0f, T1_in); hermites[2].Set(0.0f, T1_out, 0.5f, 0.5f * (T1_out + T2_in)); hermites[3].Set(0.5f, 0.5f * (T1_out + T2_in), 1.0f, T2_in); hermites[4].Set(0.0f, T2_out, 0.5f, 0.5f * (T2_out + T3)); hermites[5].Set(0.5f, 0.5f * (T2_out + T3), 1.0f, T3); #elif POWERS // float[] powers = new float[LAYERS_COUNT-1]; // powers[0] = floatTrackbarControlTangent0.Value; // powers[1] = floatTrackbarControlTangent1.Value; // powers[2] = floatTrackbarControlTangent2.Value; // powers[0] = powers[0] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[0]) : 1.0f + 9.0f * powers[0]; // powers[1] = powers[1] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[1]) : 1.0f + 9.0f * powers[1]; // powers[2] = powers[2] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[2]) : 1.0f + 9.0f * powers[2]; float[] powers = new float[LAYERS_COUNT - 1]; float[] scalesX = new float[LAYERS_COUNT - 1]; float[] scalesY = new float[LAYERS_COUNT - 1]; powers[0] = floatTrackbarControlTangent0.Value; powers[1] = floatTrackbarControlTangent1.Value; powers[2] = floatTrackbarControlTangent2.Value; // powers[0] = powers[0] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[0]) : 1.0f + 9.0f * powers[0]; // powers[1] = powers[1] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[1]) : 1.0f + 9.0f * powers[1]; // powers[2] = powers[2] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[2]) : 1.0f + 9.0f * powers[2]; powers[0] = (float)Math.Pow(10.0, 3.0 * powers[0]); powers[1] = (float)Math.Pow(10.0, 3.0 * powers[1]); powers[2] = (float)Math.Pow(10.0, 3.0 * powers[2]); scalesX[0] = 1.0f; // + 0.5f * Math.Max( 0.0f, floatTrackbarControlTangent0.Value ); scalesY[0] = 1.0f; // + 0.5f * Math.Max( 0.0f, -floatTrackbarControlTangent0.Value ); scalesX[1] = 1.0f; // + 0.5f * Math.Max( 0.0f, floatTrackbarControlTangent1.Value ); scalesY[1] = 1.0f; // + 0.5f * Math.Max( 0.0f, -floatTrackbarControlTangent1.Value ); scalesX[2] = 1.0f; // + 0.5f * Math.Max( 0.0f, floatTrackbarControlTangent2.Value ); scalesY[2] = 1.0f; // + 0.5f * Math.Max( 0.0f, -floatTrackbarControlTangent2.Value ); #endif for (uint bucketIndex = 0; bucketIndex < LEVEL_BUCKETS_COUNT; bucketIndex++) { float maskIn = (float)bucketIndex / (LEVEL_BUCKETS_COUNT - 1); float scaledMask = (LAYERS_COUNT - 1) * maskIn; uint layerStart = Math.Min(LAYERS_COUNT - 2, (uint)Math.Floor(scaledMask)); float t = scaledMask - layerStart; #if CUBIC_SPLINES // Compute Hermite curves // float4 hermite = hermites[layerStart]; // float maskOut = hermite.x * (1+2*t)*(1-t)*(1-t) // + hermite.y * t*(1-t)*(1-t) // + hermite.z * t*t*(3-2*t) // + hermite.w * t*t*(t-1); t = 2.0f * t; uint hermiteIndex = 2 * layerStart; if (t > 1.0f) { hermiteIndex++; t -= 1.0f; } float4 hermite = hermites[hermiteIndex]; float maskOut = hermite.x * (1 + 2 * t) * (1 - t) * (1 - t) + hermite.y * t * (1 - t) * (1 - t) + hermite.z * t * t * (3 - 2 * t) + hermite.w * t * t * (t - 1); #elif POWERS // // Apply curves // float tIn = 2.0f * t - 1.0f; // float tOut = Math.Sign( tIn ) * (float) Math.Pow( Math.Abs( tIn ), powers[layerStart] ); // float maskOut = (layerStart + 0.5f * (1.0f + tOut)) / (LAYERS_COUNT-1); float maskOut = scalesY[layerStart] * (float)Math.Pow(t / scalesX[layerStart], powers[layerStart]); #endif maskOut = Math.Max(0, Math.Min(1, maskOut)); maskOut = (layerStart + maskOut) / (LAYERS_COUNT - 1); m_levels[bucketIndex] = maskOut; } // Redraw levels float2 rangeX = new float2(0, 1); float2 rangeY = new float2(0, 1); m_imageLevels.Clear(float4.One); m_imageLevels.PlotAxes(float4.UnitW, rangeX, rangeY, 1.0f / (LAYERS_COUNT - 1), 1.0f / (LAYERS_COUNT - 1)); m_imageLevels.PlotGraph(float4.UnitW, rangeX, rangeY, ( float x ) => { return(m_levels[Math.Min(LEVEL_BUCKETS_COUNT - 1, (uint)((LEVEL_BUCKETS_COUNT - 1) * x))]); }); for (int layerIndex = 1; layerIndex < LAYERS_COUNT; layerIndex++) { m_imageLevels.DrawLine(new float4(0.2f, 0.5f, 0.75f, 1), m_imageLevels.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)layerIndex / (LAYERS_COUNT - 1), 0)), m_imageLevels.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)layerIndex / (LAYERS_COUNT - 1), 1))); m_imageLevels.DrawLine(new float4(0.2f, 0.5f, 0.75f, 1), m_imageLevels.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(0, (float)layerIndex / (LAYERS_COUNT - 1))), m_imageLevels.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(1, (float)layerIndex / (LAYERS_COUNT - 1)))); } panelOutputLevels.m_bitmap = m_imageLevels.AsBitmap; panelOutputLevels.Refresh(); }
/// <summary> /// Computes the radius of the sphere tangent to a vertex given a set of neighbor vertices /// </summary> /// <param name="_P"></param> /// <param name="_N"></param> /// <param name="_neighbors"></param> /// <returns></returns> float ComputeTangentSphereRadius_SUPER_SLOW(float3 _P, float3 _N, float3[] _neighbors) { ImageFile graph = new ImageFile((uint)panelOutputGraph.Width, (uint)panelOutputGraph.Height, PIXEL_FORMAT.BGRA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB)); graph.Clear(float4.One); Func <float3, float, float3[], float> SquareDistance = (float3 _C, float _R, float3[] _Pns) => { float result = 0.0f; foreach (float3 Pn in _Pns) { result += (Pn - _C).LengthSquared - _R * _R; } return(Math.Abs(result)); }; // Func<float3,float3[],float> SquareDistance = ( float3 _C, float3[] _Pns ) => { float result = 0.0f; foreach ( float3 Pn in _Pns ) result += (Pn - _C).Length; return result / _Pns.Length; }; float2 rangeX = new float2(0, 4), rangeY = new float2(-50, 50); graph.PlotAxes(float4.UnitW, rangeX, rangeY, 0.5f, 5.0f); graph.PlotGraph(float4.UnitW, rangeX, rangeY, ( float x ) => { return(SquareDistance(_P - x * _N, x, _neighbors)); }); const float eps = 0.01f; const float tol = 1e-3f; float previousR = -float.MaxValue; float R = 0.0f; float step = 0.1f; float3 C = _P; int iterationsCount = 0; float previousSqDistance = float.MaxValue; float bestSqDistance = float.MaxValue; float bestR = 0.0f; int bestIterationsCount = 0; while (step > tol && R < 10000.0f && iterationsCount < 1000) { // Compute gradient float sqDistance = SquareDistance(C, R, _neighbors); float sqDistance2 = SquareDistance(C - eps * _N, R + eps, _neighbors); float grad = (sqDistance2 - sqDistance) / eps; if (previousSqDistance < sqDistance) { step *= 0.95f; // Climbing up again, roll down slower... } // Follow opposite gradient direction toward minimum previousR = R; R -= step * grad; C = _P - R * _N; iterationsCount++; previousSqDistance = sqDistance; if (sqDistance < bestSqDistance) { bestSqDistance = sqDistance; bestR = previousR; bestIterationsCount = iterationsCount; } graph.DrawLine(new float4(1, 0, 0, 1), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(previousR, sqDistance)), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(R, SquareDistance(_P - R * _N, R, _neighbors)))); float k = 0.1f; graph.DrawLine(new float4(0, 0.5f, 0, 1), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(previousR, k * (iterationsCount - 1))), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(R, k * iterationsCount))); } // Since we crossed the minimum, take the average for a better result R = 0.5f * (R + previousR); panelOutputGraph.m_bitmap = graph.AsBitmap; panelOutputGraph.Refresh(); labelResult.Text = "R = " + R + " (" + previousSqDistance + ") in " + iterationsCount + " iterations...\r\nBest = " + bestR + " (" + bestSqDistance + ") in " + bestIterationsCount + " iterations..."; return(R); }
/// <summary> /// Computes the radius of the sphere tangent to a vertex given a set of neighbor vertices /// </summary> /// <param name="_P"></param> /// <param name="_N"></param> /// <param name="_neighbors"></param> /// <returns></returns> float ComputeTangentSphereRadius(float3 _P, float3 _N, float3[] _neighbors, bool _debugGraph) { Func <float3, double, float3[], double> SquareDistance = (float3 _C, double _R, float3[] _Pns) => { double result = 0.0f; foreach (float3 Pn in _Pns) { result += (Pn - _C).LengthSquared - _R * _R; } return(result); }; float2 rangeX = new float2(-4, 4), rangeY = new float2(-50, 50); if (_debugGraph) { graph.Clear(float4.One); graph.PlotAxes(float4.UnitW, rangeX, rangeY, 0.5f, 5.0f); graph.PlotGraph(float4.UnitW, rangeX, rangeY, ( float x ) => { return((float)SquareDistance(_P - x * _N, x, _neighbors)); }); } const float eps = 0.01f; const double tol = 1e-3; double previousR = -double.MaxValue; double R = 0.0f; float3 C = _P; int iterationsCount = 0; double previousSqDistance = double.MaxValue; double bestSqDistance = double.MaxValue; double bestR = 0.0f; int bestIterationsCount = 0; while (Math.Abs(R) < 10000.0f && iterationsCount < 1000) { // Compute gradient double sqDistance = SquareDistance(C, R, _neighbors); double sqDistance2 = SquareDistance(C - eps * _N, R + eps, _neighbors); double grad = (sqDistance2 - sqDistance) / eps; // Compute intersection with secant Y=0 double t = -sqDistance * (Math.Abs(grad) > 1e-6f ? 1.0f / grad : (Math.Sign(grad) * 1e6)); if (Math.Abs(t) < tol) { break; } previousR = R; R += t; C = _P - (float)R * _N; iterationsCount++; previousSqDistance = sqDistance; if (sqDistance < bestSqDistance) { bestSqDistance = sqDistance; bestR = previousR; bestIterationsCount = iterationsCount; } if (_debugGraph) { graph.DrawLine(new float4(1, 0, 0, 1), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)previousR, (float)sqDistance)), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)R, (float)SquareDistance(_P - (float)R * _N, R, _neighbors)))); float k = 0.1f; graph.DrawLine(new float4(0, 0.5f, 0, 1), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)previousR, k * (iterationsCount - 1))), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)R, k * iterationsCount))); } } if (_debugGraph) { panelOutputGraph.m_bitmap = graph.AsBitmap; panelOutputGraph.Refresh(); labelResult.Text = "R = " + R + " (" + previousSqDistance + ") in " + iterationsCount + " iterations...\r\nBest = " + bestR + " (" + bestSqDistance + ") in " + bestIterationsCount + " iterations..."; } // if ( R < 1000.0 ) // throw new Exception( "Maybe R should be Math.Abs()'ed? (neighbors are all lying on a flat plane or in a ?" ); R = Math.Max(-10000.0, Math.Min(10000.0, R)); return((float)R); }