/** * Computes the range of substrokes to use when computing matches based on looseness. * When matching, sub strokes are matched up with one another to find the best * matching. But if two substrokes are +/- beyond this range, then the comparison * is short-circuited for some computation savings. * * @param subStrokeCount the substroke count of the input character * @param looseness the looseness, 0-1 * @return the range */ private int getSubStrokesRange(int subStrokeCount) { // Return the maximum if looseness = 1.0. // Otherwise we'd have to ensure that the floating point value led to exactly the right int count. if (looseness == 1.0) { return CharacterDescriptor.MAX_CHARACTER_SUB_STROKE_COUNT; } // We use a CubicCurve that grows slowly at first and then rapidly near the end to the maximum. double y0 = subStrokeCount * 0.25; double ctrl1X = 0.4; double ctrl1Y = 1.5 * y0; double ctrl2X = 0.75; double ctrl2Y = 1.5 * ctrl1Y; double[] solutions = new double[1]; CubicCurve2D curve = new CubicCurve2D(0, y0, ctrl1X, ctrl1Y, ctrl2X, ctrl2Y, 1, CharacterDescriptor.MAX_CHARACTER_SUB_STROKE_COUNT); //CurveUtils.solveCubicCurveForX(curve, looseness, solutions); //double t = solutions[0]; double t = curve.GetFirstSolutionForX(looseness); // We get the t value on the parametrized curve where the x value matches the looseness. // Then we compute the y value for that t. This gives the range. //return (int)Math.Round(CurveUtils.getPointOnCubicCurve(curve, t).getY()); return (int)Math.Round(curve.GetYOnCurve(t)); }
/** * Builds a precomputed array of values to use when getting the score between two substroke lengths. * A ratio less than one is computed for the two lengths, and the score should be the ratio * score table's length. * * @return the length score table */ private static double[] initLengthScoreTable() { // Curve grows rapidly as the ratio grows and levels off quickly. // This is because we don't really expect lengths to vary a lot. // We are really just trying to distinguish between tiny strokes and long strokes. CubicCurve2D curve = new CubicCurve2D(0, 0, 0.25, 1.0, 0.75, 1.0, 1.0, 1.0); return initCubicCurveScoreTable(curve, 100); }
/** * Computes a range of strokes to use based on the given looseness. * Only characters whose number of strokes are within the input number of strokes * +/- this range will be considered during comparison. This helps cut down * on matching cost. * * @param strokeCount the number of input strokes * @param looseness the looseness, 0-1 * @return the range */ private int getStrokesRange(int strokeCount) { // Just return some extreme values if at minimum or maximum. // Helps to avoid possible floating point issues when near the extremes. if (looseness == 0.0) { return 0; } else if (looseness == 1.0) { return CharacterDescriptor.MAX_CHARACTER_STROKE_COUNT; } // We use a CubicCurve that grows slowly at first and then rapidly near the end to the maximum. // This is so a looseness at or near 1.0 will return a range that will consider all characters. double ctrl1X = 0.35; double ctrl1Y = strokeCount * 0.4; double ctrl2X = 0.6; double ctrl2Y = strokeCount; double[] solutions = new double[1]; CubicCurve2D curve = new CubicCurve2D(0, 0, ctrl1X, ctrl1Y, ctrl2X, ctrl2Y, 1, CharacterDescriptor.MAX_CHARACTER_STROKE_COUNT); //CurveUtils.solveCubicCurveForX(curve, looseness, solutions); //double t = solutions[0]; double t = curve.GetFirstSolutionForX(looseness); // We get the t value on the parametrized curve where the x value matches the looseness. // Then we compute the y value for that t. This gives the range. //return (int)Math.Round(CurveUtils.getPointOnCubicCurve(curve, t).getY()); return (int)Math.Round(curve.GetYOnCurve(t)); }
/** * Builds a precomputed array of values to use when getting the score between two substroke directions. * Two directions should differ by 0 - Pi, and the score should be the (difference / Pi) * score table's length * * @return the direction score table */ private static double[] initDirectionScoreTable() { // The curve drops as the difference grows, but rises again some at the end because // a stroke that is 180 degrees from the expected direction maybe OK passable. CubicCurve2D curve = new CubicCurve2D(0, 1.0, 0.5, 1.0, 0.25, -2.0, 1.0, 1.0); return initCubicCurveScoreTable(curve, 100); }
/** * A common helper method to initialize a table of precomputed score values using a cubic curve * @param curve the curve * @param numSamples the number of samples to compute * @return the score array table */ private static double[] initCubicCurveScoreTable(CubicCurve2D curve, int numSamples) { double x1 = curve.X1; double x2 = curve.X2; double range = x2 - x1; double x = x1; double xInc = range / numSamples; // even incrementer to increment x value by when sampling across the curve double[] scoreTable = new double[numSamples]; // For use to pass into with solveCubicCurve double[] solutions = new double[1]; // Sample evenly across the curve and set the samples into the table. for (int i = 0; i < numSamples; i++) { //CurveUtils.solveCubicCurveForX(curve, Math.Min(x, x2), solutions); //double t = solutions[0]; double t = curve.GetFirstSolutionForX(Math.Min(x, x2)); //scoreTable[i] = CurveUtils.getPointOnCubicCurve(curve, t).getY(); scoreTable[i] = curve.GetYOnCurve(t); x += xInc; } return scoreTable; }
private float WeightedEvalForTwoKeys(FRichCurveKey key1, FRichCurveKey key2, float inTime) { var diff = key2.Time - key1.Time; var alpha = (inTime - key1.Time) / diff; var p0 = key1.Value; var p3 = key2.Value; var oneThird = 1f / 3f; var time1 = key1.Time; var time2 = key2.Time; var x = time2 - time1; var angle = Math.Atan(key1.LeaveTangent); var cosAngle = Math.Cos(angle); var sinAngle = Math.Sin(angle); double leaveWeight = key1.LeaveTangentWeight; if (key1.TangentWeightMode is RCTWM_WeightedNone or RCTWM_WeightedArrive) { var leaveTangentNormalized = key1.LeaveTangent; var y = leaveTangentNormalized * x; leaveWeight = Math.Sqrt(x * x + y * y) * oneThird; } var key1TanX = cosAngle * leaveWeight + time1; var key1TanY = sinAngle * leaveWeight + key1.Value; angle = Math.Atan(key2.ArriveTangent); cosAngle = Math.Cos(angle); sinAngle = Math.Cos(angle); double arriveWeight = key2.ArriveTangentWeight; if (key2.TangentWeightMode is RCTWM_WeightedNone or RCTWM_WeightedLeave) { var arriveTangentNormalized = key2.ArriveTangent; var y = arriveTangentNormalized * x; arriveWeight = Math.Sqrt(x * x + y * y) * oneThird; } var key2TanX = -cosAngle * arriveWeight + time2; var key2TanY = -sinAngle * arriveWeight + key2.Value; // Normalize the time range var rangeX = time2 - time1; var dx1 = key1TanX - time1; var dx2 = key2TanX - time1; // Normalize values var normalizedX1 = dx1 / rangeX; var normalizedX2 = dx2 / rangeX; var results = new double[3]; BezierToPower(0.0, normalizedX1, normalizedX2, 1.0, out double[] coeff); coeff[0] -= alpha; var numResults = CubicCurve2D.SolveCubic(ref coeff, ref results); float newInterp; if (numResults == 1) { newInterp = (float)results[0]; } else { newInterp = float.MinValue; foreach (var result in results) { if (result is >= 0.0f and <= 1.0f) { if (newInterp < 0.0f || result > newInterp) { newInterp = (float)result; } } } if (newInterp == float.MinValue) { newInterp = 0f; } } var outVal = BezierInterp(p0, (float)key1TanY, (float)key2TanY, p3, newInterp); return(outVal); }
internal CubicIterator(CubicCurve2D q, AffineTransform at) { this.Cubic = q; this.Affine = at; }
private void Next(bool doNext) { int level; if (HoldIndex >= HoldEnd) { if (doNext) { Src.Next(); } if (Src.Done) { Done_Renamed = true; return; } HoldType = Src.CurrentSegment(Hold); LevelIndex = 0; Levels[0] = 0; } switch (HoldType) { case PathIterator_Fields.SEG_MOVETO: case PathIterator_Fields.SEG_LINETO: Curx = Hold[0]; Cury = Hold[1]; if (HoldType == PathIterator_Fields.SEG_MOVETO) { Movx = Curx; Movy = Cury; } HoldIndex = 0; HoldEnd = 0; break; case PathIterator_Fields.SEG_CLOSE: Curx = Movx; Cury = Movy; HoldIndex = 0; HoldEnd = 0; break; case PathIterator_Fields.SEG_QUADTO: if (HoldIndex >= HoldEnd) { // Move the coordinates to the end of the array. HoldIndex = Hold.Length - 6; HoldEnd = Hold.Length - 2; Hold[HoldIndex + 0] = Curx; Hold[HoldIndex + 1] = Cury; Hold[HoldIndex + 2] = Hold[0]; Hold[HoldIndex + 3] = Hold[1]; Hold[HoldIndex + 4] = Curx = Hold[2]; Hold[HoldIndex + 5] = Cury = Hold[3]; } level = Levels[LevelIndex]; while (level < Limit) { if (QuadCurve2D.GetFlatnessSq(Hold, HoldIndex) < Squareflat) { break; } EnsureHoldCapacity(4); QuadCurve2D.Subdivide(Hold, HoldIndex, Hold, HoldIndex - 4, Hold, HoldIndex); HoldIndex -= 4; // Now that we have subdivided, we have constructed // two curves of one depth lower than the original // curve. One of those curves is in the place of // the former curve and one of them is in the next // set of held coordinate slots. We now set both // curves level values to the next higher level. level++; Levels[LevelIndex] = level; LevelIndex++; Levels[LevelIndex] = level; } // This curve segment is flat enough, or it is too deep // in recursion levels to try to flatten any more. The // two coordinates at holdIndex+4 and holdIndex+5 now // contain the endpoint of the curve which can be the // endpoint of an approximating line segment. HoldIndex += 4; LevelIndex--; break; case PathIterator_Fields.SEG_CUBICTO: if (HoldIndex >= HoldEnd) { // Move the coordinates to the end of the array. HoldIndex = Hold.Length - 8; HoldEnd = Hold.Length - 2; Hold[HoldIndex + 0] = Curx; Hold[HoldIndex + 1] = Cury; Hold[HoldIndex + 2] = Hold[0]; Hold[HoldIndex + 3] = Hold[1]; Hold[HoldIndex + 4] = Hold[2]; Hold[HoldIndex + 5] = Hold[3]; Hold[HoldIndex + 6] = Curx = Hold[4]; Hold[HoldIndex + 7] = Cury = Hold[5]; } level = Levels[LevelIndex]; while (level < Limit) { if (CubicCurve2D.GetFlatnessSq(Hold, HoldIndex) < Squareflat) { break; } EnsureHoldCapacity(6); CubicCurve2D.Subdivide(Hold, HoldIndex, Hold, HoldIndex - 6, Hold, HoldIndex); HoldIndex -= 6; // Now that we have subdivided, we have constructed // two curves of one depth lower than the original // curve. One of those curves is in the place of // the former curve and one of them is in the next // set of held coordinate slots. We now set both // curves level values to the next higher level. level++; Levels[LevelIndex] = level; LevelIndex++; Levels[LevelIndex] = level; } // This curve segment is flat enough, or it is too deep // in recursion levels to try to flatten any more. The // two coordinates at holdIndex+6 and holdIndex+7 now // contain the endpoint of the curve which can be the // endpoint of an approximating line segment. HoldIndex += 6; LevelIndex--; break; } }