Fix Cylinder(h, 0, r) to support cone with apex at bottom (#1541)

* fix upside down cone using cylinder

* minor changes

* minor fixes
This commit is contained in:
Anshul
2026-02-27 14:48:10 +08:00
committed by GitHub
parent 7332399d5e
commit 552103eec8
2 changed files with 40 additions and 2 deletions
+14 -2
View File
@@ -148,7 +148,8 @@ Manifold Manifold::Cube(vec3 size, bool center) {
* form cones if both radii are specified.
*
* @param height Z-extent
* @param radiusLow Radius of bottom circle. Must be positive.
* @param radiusLow Radius of bottom circle. Must be non-negative. If zero,
* radiusHigh must be positive and a cone with apex at the bottom is created.
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
* radiusLow.
* @param circularSegments How many line segments to use around the circle.
@@ -158,9 +159,20 @@ Manifold Manifold::Cube(vec3 size, bool center) {
*/
Manifold Manifold::Cylinder(double height, double radiusLow, double radiusHigh,
int circularSegments, bool center) {
if (height <= 0.0 || radiusLow <= 0.0) {
if (height <= 0.0 || radiusLow < 0.0) {
return Invalid();
}
if (radiusLow == 0.0) {
if (radiusHigh <= 0.0) {
return Invalid();
}
// Cone with apex at bottom: create the centered apex-at-top version and
// mirror it
Manifold cone = Cylinder(height, radiusHigh, 0.0, circularSegments, true);
cone = cone.Mirror(vec3(0.0, 0.0, 1.0));
if (!center) cone = cone.Translate(vec3(0.0, 0.0, height / 2.0));
return cone.AsOriginal();
}
const double scale = radiusHigh >= 0.0 ? radiusHigh / radiusLow : 1.0;
const double radius = fmax(radiusLow, radiusHigh);
const int n = circularSegments > 2 ? circularSegments
+26
View File
@@ -294,6 +294,30 @@ TEST(Manifold, Cylinder) {
EXPECT_EQ(cylinder.NumTri(), 4 * n - 4);
}
TEST(Manifold, CylinderZeroRadiusLow) {
// cylinder(h, 0, r) should produce a cone with apex at z=0 and base at z=h.
// Use enough segments for accurate volume.
const int n = 256;
const double h = 5.0, r = 3.0;
Manifold coneApexBottom = Manifold::Cylinder(h, 0.0, r, n);
Manifold coneApexTop = Manifold::Cylinder(h, r, 0.0, n);
EXPECT_EQ(coneApexBottom.Status(), Manifold::Error::NoError);
EXPECT_FALSE(coneApexBottom.IsEmpty());
// Both cones must have equal total volume.
const double totalVol = coneApexTop.Volume();
EXPECT_NEAR(coneApexBottom.Volume(), totalVol, 1e-6);
// Differentiate orientation by intersecting with the bottom half (z in
// [0, h/2]). A sub-cone scaled by 1/2 in all dimensions has 1/8 the volume,
// so apex-at-bottom gives V/8 and apex-at-top gives 7*V/8.
Manifold slicer = Manifold::Cube(vec3(2 * r + 1, 2 * r + 1, h / 2))
.Translate(vec3(-(r + 0.5), -(r + 0.5), 0.0));
EXPECT_NEAR((coneApexBottom ^ slicer).Volume(), totalVol / 8.0, 0.01);
EXPECT_NEAR((coneApexTop ^ slicer).Volume(), 7.0 * totalVol / 8.0, 0.01);
}
TEST(Manifold, Extrude) {
Polygons polys = SquareHole();
Manifold donut = Manifold::Extrude(polys, 1.0, 3);
@@ -813,6 +837,8 @@ TEST(Manifold, Invalid) {
EXPECT_EQ(Manifold::Sphere(0).Status(), invalid);
EXPECT_EQ(Manifold::Cylinder(0, 5).Status(), invalid);
EXPECT_EQ(Manifold::Cylinder(2, -5).Status(), invalid);
EXPECT_EQ(Manifold::Cylinder(2, 0).Status(), invalid);
EXPECT_EQ(Manifold::Cylinder(2, 0, 0).Status(), invalid);
EXPECT_EQ(Manifold::Cube(vec3(0.0)).Status(), invalid);
EXPECT_EQ(Manifold::Cube({-1, 1, 1}).Status(), invalid);
EXPECT_EQ(Manifold::Extrude(circ.ToPolygons(), 0.).Status(), invalid);