-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Address behavior of FreeForm drag. #218
Comments
Signed-off-by: Martin Veillette <[email protected]>
The commit above fixes the issue above by setting the penultimatePosition to null. Therefore, for the first drag event, there is only one point, which does not lie on the curve, such that you can create segments of curve as Note the single dot that results from an exceedingly short drag event. I'll address this next week to come up with a more acceptable behavior. |
…itions (see #218) Signed-off-by: Martin Veillette <[email protected]>
Signed-off-by: Martin Veillette <[email protected]>
Signed-off-by: Martin Veillette <[email protected]>
Note for self: New approach to average curve based on mollifying functions. New approach to average curve for free form /**
* Allows the user to drag Points in the Curve to any desired position to create customs but smooth shapes.
* This method will update the curve with the new position value. It attempts to create a smooth curve
* between position and antepenultimatePosition.
* The main goal of the drawToForm method is to create a curve segment that is smooth enough that it can be
* twice differentiable without generating discontinuities.
*
* @param position - in model coordinates
* @param penultimatePosition - in model coordinates
* @param antepenultimatePosition - in model coordinates
*/
private drawFreeformToPosition( position: Vector2,
penultimatePosition: Vector2 | null,
antepenultimatePosition: Vector2 | null ): void {
// Closest point associated with the position
const closestPoint = this.getClosestPointAt( position.x );
// Amount to shift the CurvePoint closest to the passed-in position.
closestPoint.y = position.y;
// Point associated with the last drag event
if ( penultimatePosition ) {
const lastPoint = this.getClosestPointAt( penultimatePosition.x );
// We want to create a straight line between this point and the last drag event point
const closestVector = closestPoint.getVector();
this.interpolate( closestVector.x, closestVector.y, lastPoint.x, penultimatePosition.y );
}
else {
// There is no position associated with the last drag event.
// Let's create a hill with a narrow width at the closestPoint.
// See https://github.com/phetsims/calculus-grapher/issues/218
this.createHillAt( WEE_WIDTH, closestPoint.x, closestPoint.y );
}
if ( penultimatePosition && antepenultimatePosition ) {
const lastPoint = this.getClosestPointAt( penultimatePosition.x );
// Point associated with the last drag event
const nextToLastPoint = this.getClosestPointAt( antepenultimatePosition.x );
// Checks that lastPoint is in between closestPoint and lastPoint
if ( ( closestPoint.x - lastPoint.x ) * ( nextToLastPoint.x - lastPoint.x ) < 0 ) {
// Finds two control points that are approximately midway between our three points
const cp1Point = this.getClosestPointAt( ( position.x + penultimatePosition.x ) / 2 );
const cp2Point = this.getClosestPointAt( ( penultimatePosition.x + antepenultimatePosition.x ) / 2 );
// Check that the lastPoint is between cp1 and cp2
if ( ( cp1Point.x - lastPoint.x ) * ( cp2Point.x - lastPoint.x ) < 0 ) {
// x separation between two adjacent points in a curve array
const deltaX = this.deltaX;
const isDescending = cp1Point.x < cp2Point.x;
const p1x = isDescending ? cp1Point.x : cp2Point.x;
const p2x = isDescending ? cp2Point.x : cp1Point.x;
const linearOne = this.linear( closestPoint.x, position.y, lastPoint.x, penultimatePosition.y );
const linearTwo = this.linear( lastPoint.x, penultimatePosition.y,
nextToLastPoint.x, antepenultimatePosition.y );
const stepFunction: MathFunction = x => {
if ( isDescending ) {
return ( x < penultimatePosition.x ) ? linearOne( x ) : linearTwo( x );
}
else {
return ( x < penultimatePosition.x ) ? linearTwo( x ) : linearOne( x );
}
};
const displacement = p2x - p1x;
const mollifierFunction: MathFunction = x => {
const width = 0.5 * displacement;
if ( Math.abs( x ) < width ) {
return Math.exp( -1 / ( 1 - ( x / width ) ** 2 ) );
}
else {
return 0;
}
};
for ( let x = p1x; x < p2x; x += deltaX ) {
let weight = 0;
let functionWeight = 0;
for ( let dx = -displacement; dx < displacement; dx += deltaX ) {
weight += mollifierFunction( dx );
functionWeight += mollifierFunction( dx ) * stepFunction( x + dx );
}
this.getClosestPointAt( x ).y = functionWeight / weight;
}
}
}
}
}
private linear( x1: number, y1: number, x2: number, y2: number ): MathFunction {
assert && assert( x1 !== x2, 'linear requires different x values' );
return x => ( x - x2 ) * ( y1 - y2 ) / ( x1 - x2 ) + y2;
} |
Signed-off-by: Martin Veillette <[email protected]>
Even with mollifying approach, you can see that the second derivative is a somewhat bizarre function. However, that is probably the best I can do. |
I'll assign this to @amanda-phet to test and review the free-form. I suggest you used the Lab Screen with the second derivative to truly test the free-form. |
Signed-off-by: Martin Veillette <[email protected]>
I should add that the presence of discontinuity points in the derivatives is symptomatic of a failure of the discontinuity detection algorithm rather than an intrinsic problem with free form drag. |
Thanks for clarifying @veillette . I think the free form drag is working very well. |
After adding a drag listener to the graphNode instead of the curve themselves (see #210) we will need to address the behavior of the FreeForm curve manipulation mode as it makes an assumption that the initial drag event is on the curve.
The text was updated successfully, but these errors were encountered: