@leeminardi , nicely done.
I do believe that an iterative approach has the best applicability overall. With regard to tangency analysis, that methodology should handle b-splines, Non Uniform, Rational or otherwise.
The Computer Science 3621 link you posted previously, though, does present an interesting aspect of Bezier and B-Splines. Much of the higher math presented on that page can be distilled down to a simple graphical representation.
In the attached screencast, I rearrange the control cage of a Degree 3 spline to create a degree 2 spline that is the hodograph (Tangency Map) of the former. With the help of the attached lisp, the hodograph spline can be selected at points that intersect a desired tangency direction and record the parameter of that point. That same parameter of the Degree 3 spline will have that tangency.
My crude lisp routine only works with splines that qualify as Bezier. The C# routine can process spline with Control points greater than Degree + 1, though they do need to be Non Rational.
[CommandMethod("splMM")]
static public void SplineMM()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
PromptSelectionOptions pso = new PromptSelectionOptions();
pso.SelectEverythingInAperture = true;
pso.SinglePickInSpace = true;
pso.MessageForRemoval = "\nMust be a planar, degree 3 spline: ";
pso.MessageForAdding = "\nSelect single spline: ";
TypedValue[] filter = { new TypedValue(0, "SPLINE"),
new TypedValue(71, 3),
new TypedValue(-4, "&="),
new TypedValue(70, 8)};
SelectionFilter selFilter = new SelectionFilter(filter);
PromptSelectionResult res = ed.GetSelection(pso, selFilter);
if (res.Status == PromptStatus.OK)
{
ObjectId[] selSet = res.Value.GetObjectIds();
using (Transaction tr = db.TransactionManager.StartTransaction())
{
Spline spl = tr.GetObject(selSet[0], OpenMode.ForRead) as Spline;
if (spl == null) return;
BlockTableRecord currSpace = tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord;
Point2dCollection p2dColl = new Point2dCollection();
KnotCollection kc = new KnotCollection();
kc.Add(0.0);
kc.Add(0.0);
kc.Add(0.0);
kc.Add(1.0);
kc.Add(1.0);
kc.Add(1.0);
NurbCurve2d nc2d = ProcessSpline2d(spl);
//p2dColl.Add(nc2d.StartPoint);
//p2dColl.Add(nc2d.EndPoint);
NurbCurve2d[] c3ds = PrepCurve(nc2d);
int ubnd = c3ds.GetUpperBound(0);
for (int i = 0; i <= ubnd; i++)
{
critPts(c3ds[i], p2dColl, kc);
}
foreach (Point2d p2d in p2dColl)
{
Point3d p3d = new Point3d(p2d.X, p2d.Y, 0.0);
DBPoint dbpt = new DBPoint(p3d);
currSpace.AppendEntity(dbpt);
dbpt.SetDatabaseDefaults();
tr.AddNewlyCreatedDBObject(dbpt, true);
}
tr.Commit();
}
}
}
private static NurbCurve2d[] PrepCurve(NurbCurve2d cSpline)
{
NurbCurve2d[] nc2ds;
int deg = cSpline.Degree;
int cpCount = cSpline.NumControlPoints;
int IntrvlCount = cpCount + deg;
NurbCurve2dData nc2dD = cSpline.DefinitionData;
KnotCollection kc = nc2dD.Knots;
DoubleCollection dc = kc.GetDistinctKnots();
int knotCount = dc.Count;
nc2ds = new NurbCurve2d[knotCount - 1];
NurbCurve2d trimmed;
for (int i = 0; i < knotCount - 1; i++)
{
trimmed = cSpline.Clone() as NurbCurve2d;
trimmed.HardTrimByParams(dc[i], dc[i + 1]);
nc2ds[i] = trimmed;
}
return nc2ds;
}
private static NurbCurve2d ProcessSpline2d(Spline spl)
{
NurbCurve2d nc2d;
NurbsData nd = spl.NurbsData;
DoubleCollection dc = nd.GetKnots();
KnotCollection kc = new KnotCollection();
foreach (Double d in dc)
{
kc.Add(d);
}
Point3dCollection p3cCP = nd.GetControlPoints();
if (nd.Rational)
{
DoubleCollection dcw = nd.GetWeights();
nc2d = new NurbCurve2d(nd.Degree, kc, Gen2DPtsNurbData(p3cCP), dcw, nd.Periodic);
}
else
{
nc2d = new NurbCurve2d(nd.Degree, kc, Gen2DPtsNurbData(p3cCP), nd.Periodic);
}
spl.Dispose();
return nc2d;
}
private static Point2dCollection Gen2DPtsNurbData(Point3dCollection FitPts)
{
Point2dCollection p2dc = new Point2dCollection();
foreach (Point3d pt in FitPts)
{
p2dc.Add(new Point2d(pt.X, pt.Y));
}
return p2dc;
}
private static void critPts(NurbCurve2d nc2d, Point2dCollection p2dc, KnotCollection kc)
{
NurbCurve2d dervGraph = Hodo(nc2d, kc);
Line2d horz = new Line2d(new Point2d(0, 0), new Point2d(1, 0));
Line2d vert = new Line2d(new Point2d(0, 0), new Point2d(0, 1));
Interval intvl = nc2d.GetInterval();
Double stParam = intvl.LowerBound;
Double len = intvl.Length;
DoubleCollection dc = CritPtsParams(dervGraph, horz);
foreach (Double param in dc)
{
Double adjustedParam = stParam + (param * len);
Point2d interim = nc2d.EvaluatePoint(adjustedParam);//Debug Step
p2dc.Add(interim);
}
dc = CritPtsParams(dervGraph, vert);
foreach (Double param in dc)
{
Double adjustedParam = stParam + (param * len);
Point2d interim = nc2d.EvaluatePoint(adjustedParam);//Debug Step
p2dc.Add(interim);
}
}
private static NurbCurve2d Hodo(NurbCurve2d nc2d, KnotCollection kc)
{
Point2dCollection p2dc = new Point2dCollection();
int count = nc2d.NumControlPoints - 1;
for (int i = 0; i < count; i++)
{
Point2d p1 = nc2d.GetControlPointAt(i);
Point2d p2 = nc2d.GetControlPointAt(i + 1);
Vector2d p2d = nc2d.GetControlPointAt(i).GetVectorTo(nc2d.GetControlPointAt(i + 1));
p2dc.Add(new Point2d(p2d.X, p2d.Y));
}
NurbCurve2d graph = new NurbCurve2d(2, kc, p2dc, false);
return graph;
}
private static DoubleCollection CritPtsParams(NurbCurve2d nc2d, Line2d Axis)
{
CurveCurveIntersector2d cci2d = new CurveCurveIntersector2d(nc2d, Axis);
int intPts = cci2d.NumberOfIntersectionPoints;
DoubleCollection dc = new DoubleCollection(intPts);
for (int i = 0; i < intPts; i++)
{
dc.Add(cci2d.GetIntersectionParameters(i)[0]);
}
return dc;
}
(defun c:PickParam (/)
(vl-load-com)
(setq ent (car (entsel "\nSelect Hodograph Curve: "))
curve-Obj (vlax-ename->vla-object ent)
)
(initget 1)
(setq p (getpoint "\nPick Location at desired Tangency: "))
(setq db (vlax-curve-getParamAtPoint curve-Obj (vlax-curve-getclosestpointto curve-Obj p)))
(print db)
(setq ent2 (car (entsel "\nSelect Curve for Analysis: "))
curve-Obj2 (vlax-ename->vla-object ent2)
)
(setq pp (vlax-curve-getPointAtParam curve-Obj2 db))
(command "_point" pp)
(princ)
)
************************************************************
May your cursor always snap to the location intended.