How does AutoCAD calculate end tangents for splines defined only by fit points? - autocad

AutoCAD allows to store SPLINE entities in the DXF files defined only by
fit points, the problem is, that such a spline definition has infinite
numerical correct solutions and Autodesk does not provide the necessary
information to calculate the required parameters from the given fit points.
tl;dr - The missing information are the estimated start- and end tangents
in direction and magnitude for the input tangents to the global B-spline
interpolation with end derivatives, can anyone help to calculate this values?
Complete source code on github.
I use BricsCAD for testing, but "Trueview 2020" shows the same results.
1. Scenario
Only fit points are given, using the global curve interpolation without any constraints to
get a spline defined by control vertices:
# First spline defined by control vertices interpolated from given fit points
s = global_bspline_interpolation(points, degree=3)
msp.add_spline(dxfattribs={'color': 4, 'layer': 'Global Interpolation'}).apply_construction_tool(s)
# Second spline defined only by fit points as reference
spline = msp.add_spline(points, degree=3, dxfattribs={'layer': 'BricsCAD B-spline', 'color': 2})
doc.saveas(DIR / 'fit-points-only.dxf')
The Spline interpolated by BricsCAD from fit points does not match the spline defined by the interpolated
control vertices:
2. Scenario
Beside the fit points I store also the start- and end tangent values in the DXF file.
The interpolation is done by global curve interpolation with end derivatives
(Piegl & Tiller: "The NURBS Book" - chapter 9.2.2).
I chose an arbitrary angle (100 degrees) as start- and end tangents, the tangent
magnitude is estimated by the "Total chord length" method.
m1, m2 = estimate_end_tangent_magnitude(points, method='chord')
start_tangent = Vector.from_deg_angle(100) * m1
end_tangent = Vector.from_deg_angle(-100) * m2
# First spline defined by control vertices interpolated from given fit points and end-tangents
s = global_bspline_interpolation(points, degree=3, tangents=(start_tangent, end_tangent))
msp.add_spline(dxfattribs={'color': 4, 'layer': 'Global Interpolation'}).apply_construction_tool(s)
# Result matches the BricsCAD interpolation if fit points, start- and end
# tangents are stored explicit in the DXF file.
# Second spline defined by fit points as reference
spline = msp.add_spline(points, degree=3, dxfattribs={'layer': 'BricsCAD B-spline', 'color': 2})
# set explicit start- and end tangent as unit vectors
spline.dxf.start_tangent = Vector.from_deg_angle(100)
spline.dxf.end_tangent = Vector.from_deg_angle(-100)
doc.saveas(DIR / 'fit-points-and-tangents.dxf')
The Spline interpolated by BricsCAD now matches exactly the spline defined by the
interpolated control vertices:
Now I know the interpolation method is correct, all I need to render the same spline from fit points
as BricsCAD are the end-tangents in direction and magnitude inferred from the fit points.
3. Scenario
I need the control vertices to render the B-spline, but start- and
end tangents are not stored in the DXF file like in scenario 1.
Estimation of start- and end tangents is required, best result by:
"5 Point Interpolation" from "The NURBS Book", Piegl & Tiller
tangents = estimate_tangents(points, method='5-points')
# Estimated tangent angles: (108.43494882292201, -108.43494882292201) degree
m1, m2 = estimate_end_tangent_magnitude(points, method='chord')
start_tangent = tangents[0].normalize(m1)
end_tangent = tangents[-1].normalize(m2)
# First spline defined by control vertices interpolated from given fit points and end-tangents
s = global_bspline_interpolation(points, degree=3, tangents=(start_tangent, end_tangent))
msp.add_spline(dxfattribs={'color': 4, 'layer': 'Global Interpolation'}).apply_construction_tool(s)
# Second spline defined by fit points as reference, but without explicit start- and end
# tangents to see if my estimations are correct.
msp.add_spline(points, degree=3, dxfattribs={'layer': 'BricsCAD B-spline', 'color': 2})
doc.saveas(DIR / 'tangents-estimated.dxf')
And surprise the estimations are not correct, BricsCAD spline has tangent angles of
101.0035408517495 and -101.0035408517495 degrees.
And the really annoying part is, if I use the BricsCAD angles as input,
the splines still does not match, so I assumed that the tangent magnitude
estimation is different from scenario 2.
4. Theory Check
Following values are calculated from a DXF file saved by BricsCAD
and SPLINE "Method" switched from "fit points" to "control vertices".
From this data I calculated the tangent angles and also the magnitudes,
tangent vector = 2nd control vertex - 1st control vertex
required_angle = 101.0035408517495 # angle of tangent vector in degrees
required_magnitude = m1 * 1.3097943444804256 # magnitude of tangent vector
start_tangent = Vector.from_deg_angle(required_angle, required_magnitude)
end_tangent = Vector.from_deg_angle(-required_angle, required_magnitude)
s = global_bspline_interpolation(points, degree=3, tangents=(start_tangent, end_tangent))
msp.add_spline(dxfattribs={'color': 4, 'layer': 'Global Interpolation'}).apply_construction_tool(s)
msp.add_spline(points, degree=3, dxfattribs={'layer': 'BricsCAD B-spline', 'color': 2})
doc.saveas(DIR / 'theory-check.dxf')
Now the splines match again:
If tangents are given (stored in DXF) the magnitude of the input tangents for the
interpolation function is "total chord length".
Without given tangents the magnitude is different, in this example: m1*1.3097943444804256,
but it is not a constant factor.
The big question is: How to estimate the start- and end tangents in direction and magnitude
like AutoCAD or BricsCAD for splines defined only by fit points?
Thanks in advance,
Manfred

I had to face the same problem, and I found that if the tangent information is missing, they use a Cubic B-Spline interpolation of type natural. That is, they impose that the second derivative at the beginning and at the end of the curve is zero.

The 3rd Scenario seems to be solved: SPLINE entities from fit points without given end tangents.
Applying a cubic Bézier curve interpolation seems to be the solution:
There is no visual difference between the BricsCAD/AutoCAD and the ezdxf SPLINE.
The conversion from cubic Bèzier curve to a cubic SPLINE is described here on math.stackexchange.com, and implemented here in ezdxf v0.16 and the source code for the cubic Bézier curve interpolation is here.
This works only for cubic B-splines (the most common used B-spline), and BricsCAD/AutoCAD allow only a degree of 2 or 3 for SPLINE entities defined only by fit points. The only thing missing is an interpolation of quadratic B-splines as quadratic Bézier curves.
Further research showed that quadratic B-splines defined by fit points are loaded into BricsCAD/AutoCAD as cubic B-splines. Addition to the statement above:
BricsCAD and AutoCAD only use a degree of 3 for SPLINE entities defined only by fit points.
The solution for a B-spline without given end tangents is a cubic Bèzier interpolation, no end tangent calculation is needed.
UPDATE: not a solution
Sadly this all works just for small simple B-splines:
yellow: SPLINE by BricsCAD
cyan: Bèzier curve interpolation
magenta: global curve interpolation
The global curve interpolation is the much better solution than the Bèzier curve interpolation. It diverges just at the beginning of the B-spline, where the Bèzier curve interpolation totally fails.
The search for the AutoCAD end tangents continues ...

The SPLINE entity can have optional group codes 12,22,32 for start tangent x,y,z and 13,23,33 for end tangent x,y,z. I've checked source code of netDxf project, and it follows that if fit points only are used to define the spline, then start and end tangent values are to be specified.
From AutoCAD 2012 DXF Reference for SPLINE entity:
12 Start tangent—may be omitted (in WCS) DXF: X value; APP: 3D point
22, 32 DXF: Y and Z values of start tangent—may be omitted (in WCS)
13 End tangent—may be omitted (in WCS) DXF: X value; APP: 3D point
23, 33 DXF: Y and Z values of end tangent—may be omitted (in WCS)
We created a few DXF files yesterday with my colleague in Autocad 2020, including fit-point splines. After export to DXF, the splines were defined by control points and knots. So I have a guess that fit points is something obsolete or UI-only.

Related

Point in Polygon 3d (same plane) algorithm

I have a point and a polygon in the same plane in 3d space and now I want to check whether or not the point is in the polygon or not.
Is there an easy way to change the algorithm from this thread Point in Polygon Algorithm to work for 3d space?
Or are there other algorithms that can solve this problem easily?
If there are not, would the following idea work:
Check if the plane is the XZ-plane or the YZ-plane, if yes, ignore the other axis (i.e. for the XZ-plane ignore the y values) and use the pip algorithm from the before mentioned thread. And if no, just ignore the z values of the point and the polygon and use the pip algorithm.
there are 2 "basic" ways of testing planar concave polygon:
convert to set of convex ones and test direction of cross product between point and all faces
the conversion to convex polygon is not as easy but its doable either by triangulation or clipping ear or what ever method... After that just check the cross products... so if your convex polygon has vertexes p0,p1,p2,...,p(n-1) and testing point p then
d0 = cross( p-p0 , p0-p(n-1) );
for (i=1;i<n;i++)
{
di = cross( p-p(i), p(i)-p(i-1) );
if ( dot ( d0 , di ) <=0.0 ) return false;
}
return true;
so just check all the polygons and return OR of the subresults
use hit test
You cast ray from your point in any direction parallel to your plane and count the number of hits you ray has done with the edges of polygon. If its even point is outside if its odd point is inside. The link in your question uses this algo. However in 3D you need to change the direction so it still is inside plane... for example by using single edge of polygon dir=p1-p0 as your direction. You also have to code some rules for cases when your ray hits Vertex directly so its counted just once instead of multiple times. Also the hit must be computed in 3D so you need axis/line intersection. It can be found here:
Cone to box collision
just look for line closest(axis a0,line l1) function. It returns line that is the closest connection between line and axis. Then just simply check if the two points are the same or not (length of the line is zero).
Now to simplify this you might port your 3D data into 2D
That can get rid of some accuracy problems related to rounding to the plane...
You are doing this by just ignoring one coordinate. That is simple but it might bring some rounding problems also the result has different shape (scaled differently in each axis) so the metrics is not the same anymore which might bring other problems latter if this is used for other purposes or any kind of thresholding is used.
There is a better method. We need any 2 basis vectors u,v that are perpendicular to each and are inside your plane and one point o inside the plane. That is easy just:
o = p0; // any point from the polygon
u = p1-p0; // any edge of polygon
u /= |u|; // normalize
v = p2-p1; // any other edge of polygon
v /= |v|; // normalize
for (;fabs(dot(u,v)>0.75;) // if too parallel
{
v=(p(1+rand(n-1))-p0); // chose random "edge"
v /= |v|; // normalize
}
v=cross(u,v); // make u,v perpendicular
v=cross(v,u); // and inside the plane
v /= |v|; // normalize just in case because of rounding the size might not be unit anymore
Now to convert point p(x,y,z) from 3D to 2D (x,y) just do:
x = dot(p-o,u);
y = dot(p-o,v);
to convert back to 3D:
p = o + x*u + y*v;
With this way of conversion the metrics is the same so the length of polygon edges and size of polygon will not change ...

How can you iterate linearly through a 3D grid?

Assume we have a 3D grid that spans some 3D space. This grid is made out of cubes, the cubes need not have integer length, they can have any possible floating point length.
Our goal is, given a point and a direction, to check linearly each cube in our path once and exactly once.
So if this was just a regular 3D array and the direction is say in the X direction, starting at position (1,2,0) the algorithm would be:
for(i in number of cubes)
{
grid[1+i][2][0]
}
But of course the origin and the direction are arbitrary and floating point numbers, so it's not as easy as iterating through only one dimension of a 3D array. And the fact the side lengths of the cubes are also arbitrary floats makes it slightly harder as well.
Assume that your cube side lengths are s = (sx, sy, sz), your ray direction is d = (dx, dy, dz), and your starting point is p = (px, py, pz). Then, the ray that you want to traverse is r(t) = p + t * d, where t is an arbitrary positive number.
Let's focus on a single dimension. If you are currently at the lower boundary of a cube, then the step length dt that you need to make on your ray in order to get to the upper boundary of the cube is: dt = s / d. And we can calculate this step length for each of the three dimensions, i.e. dt is also a 3D vector.
Now, the idea is as follows: Find the cell where the ray's starting point lies in and find the parameter values t where the first intersection with the grid occurs per dimension. Then, you can incrementally find the parameter values where you switch from one cube to the next for each dimension. Sort the changes by the respective t value and just iterate.
Some more details:
cell = floor(p - gridLowerBound) / s <-- the / is component-wise division
I will only cover the case where the direction is positive. There are some minor changes if you go in the negative direction but I am sure that you can do these.
Find the first intersections per dimension (nextIntersection is a 3D vector):
nextIntersection = ((cell + (1, 1, 1)) * s - p) / d
And calculate the step length:
dt = s / d
Now, just iterate:
if(nextIntersection.x < nextIntersection.y && nextIntersection.x < nextIntersection.z)
cell.x++
nextIntersection.x += dt.x
else if(nextIntersection.y < nextIntersection.z)
cell.y++
nextIntersection.y += dt.y
else
cell.z++
nextIntersection.z += dt.z
end if
if cell is outside of grid
terminate
I have omitted the case where two or three cells are changed at the same time. The above code will only change one at a time. If you need this, feel free to adapt the code accordingly.
Well if you are working with floats, you can make the equation for the line in direction specifiedd. Which is parameterized by t. Because in between any two floats there is a finite number of points, you can simply check each of these points which cube they are in easily cause you have point (x,y,z) whose components should be in, a respective interval defining a cube.
The issue gets a little bit harder if you consider intervals that are, dense.
The key here is even with floats this is a discrete problem of searching. The fact that the equation of a line between any two points is a discrete set of points means you merely need to check them all to the cube intervals. What's better is there is a symmetry (a line) allowing you to enumerate each point easily with arithmetic expression, one after another for checking.
Also perhaps consider integer case first as it is same but slightly simpler in determining the discrete points as it is a line in Z_2^8?

In a restricted space with n dimension, how to find the coordinates of p points, so that they are as far as possible from each other?

For example, in a 2D space, with x [0 ; 1] and y [0 ; 1]. For p = 4, intuitively, I will place each point at each corner of the square.
But what can be the general algorithm?
Edit: The algorithm needs modification if dimensions are not orthogonal to eachother
To uniformly place the points as described in your example you could do something like this:
var combinedSize = 0
for each dimension d in d0..dn {
combinedSize += d.length;
}
val listOfDistancesBetweenPointsAlongEachDimension = new List
for each d dimension d0..dn {
val percentageOfWholeDimensionSize = d.length/combinedSize
val pointsToPlaceAlongThisDimension = percentageOfWholeDimensionSize * numberOfPoints
listOfDistancesBetweenPointsAlongEachDimension[d.index] = d.length/(pointsToPlaceAlongThisDimension - 1)
}
Run on your example it gives:
combinedSize = 2
percentageOfWholeDimensionSize = 1 / 2
pointsToPlaceAlongThisDimension = 0.5 * 4
listOfDistancesBetweenPointsAlongEachDimension[0] = 1 / (2 - 1)
listOfDistancesBetweenPointsAlongEachDimension[1] = 1 / (2 - 1)
note: The minus 1 deals with the inclusive interval, allowing points at both endpoints of the dimension
2D case
In 2D (n=2) the solution is to place your p points evenly on some circle. If you want also to define the distance d between points then the circle should have radius around:
2*Pi*r = ~p*d
r = ~(p*d)/(2*Pi)
To be more precise you should use circumference of regular p-point polygon instead of circle circumference (I am too lazy to do that). Or you can compute the distance of produced points and scale up/down as needed instead.
So each point p(i) can be defined as:
p(i).x = r*cos((i*2.0*Pi)/p)
p(i).y = r*sin((i*2.0*Pi)/p)
3D case
Just use sphere instead of circle.
ND case
Use ND hypersphere instead of circle.
So your question boils down to place p "equidistant" points to a n-D hypersphere (either surface or volume). As you can see 2D case is simple, but in 3D this starts to be a problem. See:
Make a sphere with equidistant vertices
sphere subdivision triangulation
As you can see there are quite a few approaches to do this (there are much more of them even using Fibonacci sequence generated spiral) which are more or less hard to grasp or implement.
However If you want to generalize this into ND space you need to chose general approach. I would try to do something like this:
Place p uniformly distributed place inside bounding hypersphere
each point should have position,velocity and acceleration vectors. You can also place the points randomly (just ensure none are at the same position)...
For each p compute acceleration
each p should retract any other point (opposite of gravity).
update position
just do a Newton D'Alembert physics simulation in ND. Do not forget to include some dampening of speed so the simulation will stop in time. Bound the position and speed to the sphere so points will not cross it's border nor they would reflect the speed inwards.
loop #2 until max speed of any p crosses some threshold
This will more or less accurately place p points on the circumference of ND hypersphere. So you got minimal distance d between them. If you got some special dependency between n and p then there might be better configurations then this but for arbitrary numbers I think this approach should be safe enough.
Now by modifying #2 rules you can achieve 2 different outcomes. One filling hypersphere surface (by placing massive negative mass into center of surface) and second filling its volume. For these two options also the radius will be different. For one you need to use surface and for the other volume...
Here example of similar simulation used to solve a geometry problem:
How to implement a constraint solver for 2-D geometry?
Here preview of 3D surface case:
The number on top is the max abs speed of particles used to determine the simulations stopped and the white-ish lines are speed vectors. You need to carefully select the acceleration and dampening coefficients so the simulation is fast ...

Normal of a 3D tangent

So I have 3 points in a 3D space with a curve passing through the points. I have found the tangent of the point in the middle by averaging the two points either side of it, but I want to find the Normal at the point in the middle. How would I do this without knowing the equation of the line?
`P(1) = (0,1,0)
P(2) = (2,2,2)
p(3) = (4,4,4)
Tangent at P(2) = (4,3,4)`
Thanks!
A = P2 - P1 ... vector between 2 points on the curve (one is the middle point .. P2)
normal = A x tangent
but as MBo pointed out there are infinite number of normals (all are perpendicular to the curve lying on the same plane)
above equation gives one perpendicular to curve and to that A vector

What algorithm determines the nearness of a point to a Bezier curve?

I wish to determine when a point (mouse position) in on, or near a curve defined by a series of B-Spline control points.
The information I will have for the B-Spline is the list of n control points (in x,y coordinates). The list of control points can be of any length (>= 4) and define a B-spline consisting of (n−1)/3 cubic Bezier curves. The Bezier curves are are all cubic. I wish to set a parameter k,(in pixels) of the distance defined to be "near" the curve. If the mouse position is within k pixels of the curve then I need to return true, otherwise false.
Is there an algorithm that gives me this information. Any solution does not need to be precise - I am working to a tolerance of 1 pixel (or coordinate).
I have found the following questions seem to offer some help, but do not answer my exact question. In particular the first reference seems to be a solution only for 4 control points, and does not take into account the nearness factor I wish to define.
Position of a point relative to a Bezier curve
Intersection between bezier curve and a line segment
EDIT:
An example curve:
e, 63.068, 127.26
29.124, 284.61
25.066, 258.56
20.926, 212.47
34, 176
38.706, 162.87
46.556, 149.82
54.393, 138.78
The description of the format is: "Every edge is assigned a pos attribute, which consists of a list of 3n + 1 locations. These are B-spline control points: points p0, p1, p2, p3 are the first Bezier spline, p3, p4, p5, p6 are the second, etc. Points are represented by two integers separated by a comma, representing the X and Y coordinates of the location specified in points (1/72 of an inch). In the pos attribute, the list of control points might be preceded by a start point ps and/or an end point pe. These have the usual position representation with a "s," or "e," prefix, respectively."
EDIT2: Further explanation of the "e" point (and s if present).
In the pos attribute, the list of control points might be preceded by a start
point ps and/or an end point pe. These have the usual position representation with a
"s," or "e," prefix, respectively. A start point is present if there is an arrow at p0.
In this case, the arrow is from p0 to ps, where ps is actually on the node’s boundary.
The length and direction of the arrowhead is given by the vector (ps −p0). If there
is no arrow, p0 is on the node’s boundary. Similarly, the point pe designates an
arrow at the other end of the edge, connecting to the last spline point.
You may do this analitically, but a little math is needed.
A Bezier curve can be expressed in terms of the Bernstein Basis. Here I'll use Mathematica, that provides good support for the math involved.
So if you have the points:
pts = {{0, -1}, {1, 1}, {2, -1}, {3, 1}};
The eq. for the Bezier curve is:
f[t_] := Sum[pts[[i + 1]] BernsteinBasis[3, i, t], {i, 0, 3}];
Keep in mind that I am using the Bernstein basis for convenience, but ANY parametric representation of the Bezier curve would do.
Which gives:
Now to find the minimum distance to a point (say {3,-1}, for example) you have to minimize the function:
d[t_] := Norm[{3, -1} - f[t]];
For doing that you need a minimization algorithm. I have one handy, so:
NMinimize[{d[t], 0 <= t <= 1}, t]
gives:
{1.3475, {t -> 0.771653}}
And that is it.
HTH!
Edit Regarding your edit "B-spline with consisting of (n−1)/3 cubic Bezier curves."
If you constructed a piecewise B-spline representation you should iterate on all segments to find the minima. If you joined the pieces on a continuous parameter, then this same approach will do.
Edit
Solving your curve. I disregard the first point because I really didn't understand what it is.
I solved it using standard Bsplines instead of the mathematica features, for the sake of clarity.
Clear["Global`*"];
(*first define the points *)
pts = {{
29.124, 284.61}, {
25.066, 258.56}, {
20.926, 212.47}, {
34, 176}, {
38.706, 162.87}, {
46.556, 149.82}, {
54.393, 138.78}};
(*define a bspline template function *)
b[t_, p0_, p1_, p2_, p3_] :=
(1-t)^3 p0 + 3 (1-t)^2 t p1 + 3 (1-t) t^2 p2 + t^3 p3;
(* define two bsplines *)
b1[t_] := b[t, pts[[1]], pts[[2]], pts[[3]], pts[[4]]];
b2[t_] := b[t, pts[[4]], pts[[5]], pts[[6]], pts[[7]]];
(* Lets see the curve *)
Show[Graphics[{Red, Point[pts], Green, Line[pts]}, Axes -> True],
ParametricPlot[BSplineFunction[pts][t], {t, 0, 1}]]
.
( Rotated ! for screen space saving )
(*Now define the distance from any point u to a point in our Bezier*)
d[u_, t_] := If[(0 <= t <= 1), Norm[u - b1[t]], Norm[u - b2[t - 1]]];
(*Define a function that find the minimum distance from any point u \
to our curve*)
h[u_] := NMinimize[{d[u, t], 0.0001 <= t <= 1.9999}, t];
(*Lets test it ! *)
Plot3D[h[{x, y}][[1]], {x, 20, 55}, {y, 130, 300}]
This plot is the (minimum) distance from any point in space to our curve (of course the value over the curve is zero):
First, render the curve to a bitmap (black and white) with your favourite algorithm. Then, whenever you need, determine the nearest pixel to the mouse position using information from this question. You can modify the searching function so that it will return distance, so you can easilly compare it with your requirements. This method gives you the distance with tolerance of 1-2 pixels, which will do, I guess.
Definition: distance from a point to a line segment = distance from the original point to the closest point still on the segment.
Assumption: an algo to compute the distance from a point to a segment is known (e.g. compute the intercept with the segment of the normal to the segment passing through the original point. If the intersection is outside the segment, pick the closest end-point of the segment)
use the deCasteljau algo and subdivide your cubics until getting to a good enough daisy-chain of linear segments. Supplementary info the "Bezier curve flattening" section
consider the minimum of the distances between your point and the resulted segments as the distance from your point to the curve. Repeat for all the curves in your set.
Refinement at point 2: don't compute the actual distance, but the square of it, getting the minimum square distance is good enough - saves a sqrt call/segment.
Computation effort: empirically a cubic curve with a maximum extent (i.e. bounding box) of 200-300 results in about 64 line segments when flattened to a maximum tolerance of 0.5 (approx good enough for the naked eye).
Each deCasteljau step requires 12 division-by-2 and 12 additions.
Flatness evaluation - 8 multiplications + 4 additions (if using the TaxiCab distance to evaluate a distance)
the evaluation of point-to-segment distance requires at max 12 multiplications and 11 additions - but this will be a rare case in the context of Bezier flattening, I'd expect an average of 6 multiplications and 9 additions.
So, assuming a very bad case (100 straight segments/cubic), you finish in finding your distance with a cost of approx 2600 multiplications + 2500 additions per considered cubic.
Disclaimers:
don't ask me for a demonstration on the numbers in
the computational effort evaluation above,
I'll answer with "Use the source-code" (note: Java implementation).
other approaches may be possible and maybe less costly.
Regards,
Adrian Colomitchi

Resources