Let me add another answer. This code is much shorter and faster than my previous one, and the resulting mesh of each face is much cleaner.
The procedure is simple. Triangles are first made from the given face vertices and discretised. Each mesh point is then pushed onto a 2-sphere while its angular positions are maintained.
points = {
{-0.9207, -0.3896, 0.0091},
{-0.8272, 0.5077, -0.2399},
{0.2544, -0.3511, 0.901},
{0.351, 0.6527, 0.6712},
{0.5436, -0.6326, -0.5513},
{0.6016, 0.2317, -0.7643}
};
fs = {{1, 3, 5}, {1, 2, 4, 3}, {1, 2, 6, 5}, {3, 4, 6, 5}, {2, 4, 6}};
colours = RandomColor[Length@fs];
tospherical = CoordinateTransformData["Cartesian" -> "Spherical", "Mapping"];
tocartesian = CoordinateTransformData["Spherical" -> "Cartesian", "Mapping"];
maxcellarea = 1 / 100;
faces = Map[tocartesian, #, {-2}] &[
Map[ReplacePart[tospherical[#], 1 -> 1] &, #, {-2}] &[
(MeshPrimitives[#, 2] &) /@
(DiscretizeRegion[#, MaxCellMeasure -> maxcellarea] &) /@
Polygon /@ (points[[#]] &) /@ fs
]
];
display[f_] := Graphics3D[
MapThread[
{EdgeForm[], Specularity[GrayLevel[0.6], 50], #2, #1} &,
{f, colours}
],
Boxed -> False
];
display[faces]
With maxcellarea = 1/100
:
With maxcellarea = 1/1000000
:
As pointed out by Michael E2 in his answer, faceted shading can be removed by VertexNormals
. The helper function anglesign
below is also suggested by him in #79604.
anglesign[v1_, v2_] := Sign@Det@Prepend[Differences@v1, v2];
displaysmooth[f_] := Graphics3D[
MapThread[
{EdgeForm[], #2, #1} &,
{f, colours}
] /. Polygon[vs_] :> Polygon[
vs,
VertexNormals -> (anglesign[vs, #] # &) /@ vs
],
Boxed -> False
];
With just maxcellarea = 1/1000
, displaysmooth[faces]
gives
Decrease maxcellarea
to smoothen the boundary (and specularity if added).
The sphere-splitting procedure can be visualised as inflating a prism balloon:
Each inflating state can be generated by varying mod
between 0
to 1
in:
prefaces = Map[tospherical, #, {-2}] &[
(MeshPrimitives[#, 2] &) /@
(DiscretizeRegion[#, MaxCellMeasure -> maxcellarea] &) /@
Polygon /@ (points[[#]] &) /@ fs
];
modfaces[mod_] := Map[tocartesian, #, {-2}] &[
Map[
ReplacePart[#, 1 -> #[[1]] + mod (1 - #[[1]])] &,
prefaces,
{-2}
]
];
display[modfaces[1/2]] (* for example *)
What if mod > 1
?
display[modfaces[7]]
Let's get artistic!
By tinkering with the inside of ReplacePart
in modfaces
we can get some interesting deformations of the ball.
modfaces[mod_] := Map[tocartesian, #, {-2}] &[
Map[
ReplacePart[
#,
1 -> 1 - mod Min[
Function[point, EuclideanDistance[point, tocartesian@#]] /@ points
]
] &,
prefaces,
{-2}
]
];
display[modfaces[1]]
modfaces[mod_] := Map[tocartesian, #, {-2}] &[
Map[
ReplacePart[
#,
1 -> 1 - mod Sin@Max[
Function[point, CosineDistance[point, tocartesian@#]] /@ points
]
] &,
prefaces,
{-2}
]
];
display[modfaces[1]]
The possibilities are endless!
Growing faces
points = {
{-0.9207, -0.3896, 0.0091},
{-0.8272, 0.5077, -0.2399},
{0.2544, -0.3511, 0.901},
{0.351, 0.6527, 0.6712},
{0.5436, -0.6326, -0.5513},
{0.6016, 0.2317, -0.7643}
};
fs = {{1, 3, 5}, {1, 2, 4, 3}, {1, 2, 6, 5}, {3, 4, 6, 5}, {2, 4, 6}};
colours = RandomColor[Length@fs];
tospherical = CoordinateTransformData["Cartesian" -> "Spherical", "Mapping"];
tocartesian = CoordinateTransformData["Spherical" -> "Cartesian", "Mapping"];
maxcellarea = 1 / 100000;
(* push the given points to really be on the sphere *)
points = tocartesian /@ (ReplacePart[tospherical[#], 1 -> 1] &) /@ points;
fbs = Partition[Append[#, First@#], 2, 1] & /@ fs;
faceboundaries = Map[points[[#]] &, fbs, {3}];
facevertices = points[[#]] & /@ fs;
flatfaces = Polygon /@ facevertices;
(* tangent plane to a point on a sphere *)
infplane[cartpt_] := With[
{threepts = FindInstance[{x, y, z}.cartpt == 0, {x, y, z}, Reals, 3]},
InfinitePlane[TranslationTransform[cartpt][{x, y, z} /. threepts]]
];
smallfaces[border_] := (
smallfacevertices = MapThread[
Function[
{fv, fbound},
TranslationTransform[
Module[
{
vertex, twoedges, othervertices,
plane, ovpop, vector1, vector2, theta, vertexdisplacement
},
vertex = #;
twoedges = Select[fbound, Function[x, MemberQ[x, vertex]]];
othervertices = Complement[Union @@ twoedges, {vertex}];
plane = infplane[vertex];
(* ovpop: other vertices projected on the plane *)
ovpop = RegionNearest[plane, #] & /@ othervertices;
vector1 = -vertex + ovpop[[1]];
vector2 = -vertex + ovpop[[2]];
theta = VectorAngle[vector1, vector2];
vertexdisplacement = (border / 2) / Sin[theta / 2];
vertexdisplacement Normalize[Normalize[vector1] + Normalize[vector2]]
]
][#] & /@ fv
],
{facevertices, faceboundaries}
];
smallflatfaces = Polygon /@ smallfacevertices;
ff = DiscretizeRegion[#, MaxCellMeasure -> maxcellarea] & /@ smallflatfaces;
ffpolygons = MeshPrimitives[#, 2] & /@ ff;
sfpolygons = Map[ReplacePart[tospherical[#], 1 -> 1] &, ffpolygons, {-2}];
Map[tocartesian, sfpolygons, {-2}]
);
display[f_] := Graphics3D[
MapThread[
{EdgeForm[], Specularity[GrayLevel[0.6], 50], #2, #1} &,
{f, colours}
],
Boxed -> False
];
display[smallfaces[1 / 20]]
Each frame is given by display[smallfaces[x]]
where x
corresponds to the width of the gap.
Reinforcement
So far I haven't made use of the given edges. Now is the time.
In case one fears the faces will fall apart, let me add some reinforcing fasteners.
(For Mathematica 10.1, you can use the newly introduced Subdivide
in lieu of finddiv
.)
(* given edges *)
es = {{1, 2}, {1, 3}, {1, 5}, {2, 4}, {2, 6}, {3, 4}, {3, 5}, {4, 6}, {5, 6}};
(* helper functions *)
arcinterior[{r1_, r2_}, nt_] := Table[
RotationTransform[t VectorAngle[r1, r2], Cross[r1, r2]][r1],
{t, finddiv[0, 1, nt]}
];
finddiv[imin_, imax_, divs_] := With[
{di = (imax - imin)/(divs - 1)},
Range[imin, imax, di]
];
infplane[cartpt_] := With[
{threepts = FindInstance[{x, y, z}.cartpt == 0, {x, y, z}, Reals, 3]},
InfinitePlane[TranslationTransform[cartpt][{x, y, z} /. threepts]]
];
(* set-up *)
origin = {0, 0, 0};
stripwidth = 0.1;
protrusion = 1.04;
pointyend = 1.08;
pointsfar = tocartesian /@ (ReplacePart[tospherical[#], 1 -> protrusion] &) /@ points;
edgevertices = pointsfar[[#]] & /@ es;
slicingsizes = Floor[(EuclideanDistance @@@ edgevertices) / stripwidth];
edgesliced = MapThread[arcinterior[#1, #2] &, {edgevertices, slicingsizes}];
(* constructing reinforcement *)
step1 = Partition[#, 2, 1] & /@ edgesliced;
step2 = (FindInstance[
RegionMember[infplane[#[[1]]], {x, y, z}] &&
RegionMember[infplane[#[[2]]], {x, y, z}] &&
EuclideanDistance[{x, y, z}, #[[1]]] == stripwidth,
{x, y, z},
Reals,
2
] &) /@ # & /@ step1;
step3 = Partition[#, 2, 1] & /@ step2;
step4 = (Module[
{center, pts},
pts = {x, y, z} /. Join[#[[1]], #[[2]]];
center = Mean[pts];
SortBy[pts, (N[ArcTan @@ Rest[# - center]] &)]
] &) /@ # & /@ step3;
pyramids = (Pyramid[Append[#, origin]] &) /@ # & /@ step4;
pricks = (Pyramid[
Append[#, tocartesian[
ReplacePart[tospherical[Mean[#]], 1 -> pointyend]]]
] &
) /@ # & /@ step4;
reinf = Graphics3D[{
{EdgeForm[], GrayLevel[0.3], Specularity[GrayLevel[0.6], 50], pyramids, pricks},
{
Specularity[GrayLevel[0.6], 50],
GrayLevel[0.3],
Sphere[
#,
Sqrt[2] EuclideanDistance @@ ({x, y, z} /. step2[[1, 1]]) / 2
] & /@ pointsfar
}
}];
Show[{display[faces], reinf}]
Or the following for its own dramatic protection:
rootpos = 0.99;
roots = tocartesian /@ (ReplacePart[tospherical[#], 1 -> rootpos] &) /@ points;
edgevertices = roots[[#]] & /@ es;
edgesliced = MapThread[arcinterior[#1, #2] &, {edgevertices, slicingsizes}];
cones = Cone[
{#, tocartesian[ReplacePart[tospherical[#], 1 -> pointyend]]},
Sqrt[2] stripwidth/2
] & /@ (Union @@ edgesliced);
prickles = Graphics3D[{
EdgeForm[],
GrayLevel[0.3],
Specularity[GrayLevel[0.6], 50],
cones
}];
Show[{display[faces], prickles}]
I shall now stop!