Because when in Europe... Monte-Carlo!
This should work with any shaped tube as long you are given the center points, and
a certain resolution, in your case, should be the size of the polygon that make up the surface, or for example the minimum distance between point along the surface.
Let's say that distance is dis
.
First we make a path along the inside of the tube this is not very important, but i might be necessary to in case the tube bends.
newcpts = cpts[[#]] & /@ (Last@FindShortestTour[cpts]);
We order the points so they are one after in order in a particular direction. It might break down if the bends are too tight. But it is not too important, and it might be completely unnecessary for what we will do.
Next we will make a estimate of the tangent vectors along that curve.
ClearAll[myderivate];
myderivate[x__] :=
Mean@(Subtract @@@ (Reverse /@ Partition[x, 2, 1])/(Length[x] - 1));
mytangcurve = myderivate /@ Partition[newcpts, 3, 1, {2, 2}, {}];
Next we compute the normals of the surface. We use the same from this answer
newareavectors =
SortBy[Flatten[#, 1] &@
Map[Function[{x},
Map[Function[{y}, {y,
If[#.(First@Nearest[newcpts, pts[[y]]] - pts[[y]]) >= 0, -#, #] &[
Cross @@ ((pts[[#]] - pts[[y]]) & /@ DeleteCases[x, y])]}], x]], polys], First];
{thepoints, thenorms} =
Thread[({Mean[#[[1]]], Mean[#[[2]]]} & /@ (Thread /@ GatherBy[newareavectors, #[[1]] &]))];
myquicknormals = #[[2]] & /@ SortBy[Thread[{thepoints /.
Rule @@@ (Thread[{#, Range[Length@#]}] &
[Sort@DeleteDuplicates@Flatten[polys]]), thenorms}], First];
First we create a box of a large enough width, but of thickness dis
.
dis=0.1;
SeedRandom@1;
randpointsofmy =
Thread[{RandomReal[{-1, 1}, 10000], RandomReal[{-1, 1}, 10000],
RandomReal[{-dis/2., dis/2.}, 10000]}];
dis = 0.1
, seems to be right for your polygons.
Now we move this box to each point in the center and check the percentage of points that are inside.
myTransform[points_, translation_, planevector_] :=
Dot[RotationMatrix[VectorAngle[{0, 0, 1}, planevector],
Cross[{0, 0, 1}, planevector]], #] & /@ ( # + translation & /@ points)
and create a boolean to check whether points are inside the object
isitinsideQ[normals_, point_] :=
And @@ ((MapIndexed[VectorAngle[#1, pts[[Last@#2]] - point] < Pi/2. &, normals]));
There are more efficient ways out there, but you got to make sure the points in the
surface polygons are all oriented in the right direction (clockwise or anticlowise).
This should work either way, but you need the inside points for reference to create the normals.
So we can try for one point say newcpts[[1]]
numofpt = N@Count[isitinsideQ[myquicknormals, #] & /@
myTransform[randpointsofmy, newcpts[[1]], mytangcurve[[1]]], True]/(Length@randpointsofmy)
0.1838
The ration of points inside versus outside is just
$$ \frac{V_{cylinder}}{V_{box}} =\frac{\pi r^2}{4}$$
since we our cases the widths of the box were 2. This leads to a radius of
r = First@Select[rr /. Solve[numofpt/1 == (Pi*rr^2)/4, rr], # > 0 &]
0.483758
Or an area of π*r^2
:
0.7352
Now you can do that for each point. I will leave the uncertainty analysis to you.
And just to show you we are getting the right points:
Show[Graphics3D[{Opacity[.4], EdgeForm[None],
GraphicsComplex[pts, Polygon@polys]}, Boxed -> True, Axes -> True],
ListPointPlot3D[newcpts],
Graphics3D[{ Blue,
Point[myTransform[randpointsofmy, newcpts[[1]],
mytangcurve[[1]]]]}],
Graphics3D[{Red, Point[Cases[
myTransform[randpointsofmy, newcpts[[1]], mytangcurve[[1]]],
x_ /; isitinsideQ[myquicknormals, x]]]}]]
