Closed veillette closed 1 year ago
I replaced the moving box approach for the smoothing function to a gaussian kernel.
In many ways it is a very similar implementation to the previous smooth method, but it not takes into account variable weights.
As a result of the modification, some of the nomenclature has changed. Since we are using a gaussian function, it is commonly accepted to refer to its "width" using the term standard deviation. As a result, the query parameter associated with the smoothing was renamed to smoothingStandardDeviation.
/**
* Smooths the curve. Called when the user presses the 'smooth' button.
*
* This method uses a weighted-average algorithm for 'smoothing' a curve, using a gaussian kernel
* see https://en.wikipedia.org/wiki/Kernel_smoother
*/
public smooth(): void {
// Save the current values of our Points for the next undoToLastSave call. Note that the current y-values are the
// same as the previous y-values for all Points in the OriginalCurve.
this.saveCurrentPoints();
// gaussian kernel that will be used in the convolution of our curve
const gaussianFunction = ( x: number ) => Math.exp( -1 / 2 * ( x / STANDARD_DEVIATION ) ** 2 ) /
( STANDARD_DEVIATION * Math.sqrt( 2 * Math.PI ) );
// Loop through each Point and set the Point's new y-value.
this.points.forEach( point => {
// Flag that tracks the sum of the weighted y-values of all Points
let weightedY = 0;
let totalWeight = 0;
// we want to use the kernel over a number of standard deviations
// beyond 3 standard deviations, the kernel has very small weight, less than 1%.
const numberOfStandardDeviations = 3;
// Loop through each point on BOTH sides of the window, adding the y-value to our total.
for ( let dx = -numberOfStandardDeviations * STANDARD_DEVIATION;
dx < numberOfStandardDeviations * STANDARD_DEVIATION;
dx += 1 / this.pointsPerCoordinate ) {
// weight of the point
const weight = gaussianFunction( dx );
totalWeight += weight;
// Add the Point's lastSavedY, which was the Point's y-value before the smooth() method was called.
weightedY += this.getClosestPointAt( point.x + dx ).lastSavedY * weight;
}
// Set the Point's new y-value to the weighted average.
point.y = weightedY / totalWeight;
} );
// Signal that this Curve has changed.
this.curveChangedEmitter.emit();
}
Here a screenshot after one smooth event:
ALthough f(x) looks similar as above, f' and f'' are clearly not made of piecewise function anymore.
That looks really nice!
This behaves well. Closing
We need to generalized the smooth functionality
The triangle function
The triangle function after pressing the smooth button once.
The smooth function does a "Box smoothing", such that piece wise linear function become piecewise quadratic. Although the function f(x) looks smooth, the derivative and second derivative are still box-like.
We could generalize the smooth function to use a Gaussian kernel to smooth the function. This would ensure that all the derivatives would be smooth, even after one press of the smooth button.