openscad / openscad

OpenSCAD - The Programmers Solid 3D CAD Modeller
https://www.openscad.org
Other
6.94k stars 1.2k forks source link

Sphere coordinates should be fixed #462

Open MarkJeronimus opened 11 years ago

MarkJeronimus commented 11 years ago

A common CG sphere has coordinates at integral positions along the longitude and latitude axes, but OpenSCAD spheres have vertices on the latitude axis on half positions instead.

To elaborate, if $fn=12, the sphere has longitude vertices at 0°, 30°, 60°, etc. but on the latitude, the vertices are at 15°, 45°, 75°, etc. creating an unwanted flat top and bottom.

This creates scenarios where geometry doesn't match with other geometry, or geometry ends up too thin. The full discussion with examples is on http://forum.openscad.org/Fix-the-sphere-with-vertex-on-top-td5332.html

Temporary workaround (rotation of a half-circle):

module my_sphere() {
    rotate_extrude()
    intersection() {
        circle();
        translate([0, -1]) square(2);
    }
}

(note that a rotation of a full circle causes a void object when rendering F6).

--- Want to back this issue? **[Post a bounty on it!](https://app.bountysource.com/issues/827491-sphere-coordinates-should-be-fixed?utm_campaign=plugin&utm_content=tracker%2F52063&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://app.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F52063&utm_medium=issues&utm_source=github).
kintel commented 11 years ago

Hm, I think this was fixed once in the past by @GilesBathgate: 9357469cce78a60cb988d904c475eb8b38928827 My main worry is breaking existing designs which depend on the current behaviour. Any comments related to that would be interesting.

clothbot commented 11 years ago

I would have expected at least the projection of the sphere in the Z=0 plane to be fully intersecting with the same-order circle() and projection() cylinder():

$fn=12; difference() { circle();

projection() sphere();

} % projection() cylinder(h=2);

It would certainly make simple overlapping geometries like this easier to do without hulling to eliminate the stair-casing at the interface:

$fn=12; union() { cylinder(); sphere(); }

donbright commented 11 years ago

there are probably quite a few designs that depend on the current sphere code, just as there are many that depend on a 'cylinder' with fn=3 being a triangular prism. so it would probably break things.

however one could imagine a language extension to the sphere() command, like sphere(5,tessellation="delaunay") or sphere(10,tess="triangular"), (i can never remember how to spell tessellation)

for examples of the ways a sphere is approximated using polygons, one can visit

however IIRC the user who started the discussion found a workaround using a rotational extrusion of a circle?

MichaelAtOz commented 11 years ago

If you're gonna do that, make it $ft= rather than tessellation=, allowing $ft to be set globally or passed as a parameter, (and saves typing).

MarkJeronimus commented 11 years ago

In the past, the sphere probably used a hull of circles of carying detail, as can be seen in the wiki images. This is even worse if you ask me.

@clothbot it would make sense if the projection of a sphere in all three cardinal directions is equal to the circle (as stated in different words in the mailing list thread).

I tought about breaking existing designs, but imo it fixes more designs than it breaks. For example, the latest thingy i encountered that suffers from this is www.thingiverse.com/apps/customizer/run?thing_id=21758 which sufers from both staircasing and zig-zagging. People like that aren't going to implement rotational extrusion of a half-circle a by themselves.

@donbright What you call a tessellation of a sphere, 3ds Max calls a Geosphere, and it's distinct from a normal sphere because of it's useful properties.

donbright commented 11 years ago

"If you're gonna do that, make it $ft= rather than tessellation=, allowing $ft to be set globally or passed as a parameter, (and saves typing)."

yeah but then people also want to tessellate the surface of other polyhedrons in different ways, like on a gear maybe they want 'constrained delaunay triangulation' of the face, but on a 3-d bunny rabbit they want 'quadrilateral' surface tessellation. ... so it should be sphere specific if you want all your spheres tessellated in a certain way.

clothbot commented 11 years ago

@MarkJeronimus - Just to clarify, I like (and prefer) the aesthetic of having explicit points at the [0,0] poles. Requiring that the X=0 and Y=0 projected circles preserve $fn (I interpret "equal to the circle" to mean geometric equivalence) would not allow this for all odd values of $fn. It would break a lot of existing designs that expect symmetry about Z=0.

Your my_sphere() example does have this symmetric points-at-poles characteristic, so perhaps that is your intended meaning of "equal to the circle", to just mean explicit points at both poles and that the projected circle about Z=0 be geometrically identical to the circle()?

$fn=11;

module my_sphere() rotate([0,0,180/$fn]) {
    rotate_extrude()
    intersection() {
        circle();
        translate([0, -1]) square(2);
    }
}
// Geometrically equal to the circle
% translate([0,0,-2]) cylinder(h=2);
% my_sphere();
circle();

translate([2,0]) {
    // Not geometrically equal to the circle
    %projection() rotate([90,0,0]) my_sphere();
    circle();
}

translate([0,2]) {
    // Not geometrically equal to the circle
    %projection() rotate([0,90,90]) my_sphere();
    circle();
}
MichaelAtOz commented 11 years ago

@donbright ATM they cant tessellate other polyhedrons. (your same argument goes to $fn etc for a sphere v's a circle or cylinder)

What I was getting at is usability. For a type of design you probably want consistency, having to add tesselation=something as a parameter in every sphere statement would be a PITA, similarly to using $fs etc, much easier to do $fs=0.1 once and use it as a parameter in specific instances where it needs to be different.

So if you foresee other tesselations then $tX (I avoided $t before due animation) ie $ts (& surface lucks out, otherwise $tsp). Or whatever $variable...

Alternatively some global default mechanism?? default(sphere,tesselation=x,$fn=0.1) default(circle,$fn=1) etc could be handy for center= ie default(cube,center=true), or convexity=. Although extending that leads to further lexical/dynamic questions...

MarkJeronimus commented 11 years ago

@clothbot No. The latitude range [-90, 90] is smaller than the longitude range [0, 360] so it makes sense that the number of facets is halved too. This means that the 'projected circle' along the x and y axes should always have an even number of facets, as if $fn is even. The current sphere also has this behavior of you look closely.

My my_sphere doesn't, bit I don't care as I never use odd values for $fn anyway. It's easily fixed anyway if you really wanted.

thehans commented 7 years ago

I thought I would post my version of this sphere implementation.

By generating list of points for a semicircle and rotate_extrude'ing that, you save a boolean operation. And you also don't have to throw away half of the circle points you just computed.

This uses the arc, and fragments functions that I orignally developed for my "Functional OpenSCAD" library which operates directly on vertex data.

module my_sphere(r=1, d) { 
  let(R = d == undef ? r : d/2)
  rotate_extrude()
    polygon(arc(r=R, angle=180, offsetAngle=-90));
}

// Draw a circular arc with center c, radius r, etc.
// "center" parameter centers the sweep of the arc about the offsetAngle (half to each side of it)
// "internal" parameter enables polyhole radius correction
function arc(r=1, angle=360, offsetAngle=0, c=[0,0], center=false, internal=false) = 
  let (
    fragments = ceil((abs(angle) / 360) * fragments(r,$fn)),
    step = angle / fragments,
    a = offsetAngle-(center ? angle/2 : 0),
    R = internal ? r / cos (180 / fragments) : r,
    last = (abs(angle) == 360 ? 1 : 0)
  )
  [ for (i = [0:fragments-last] ) let(a2=i*step+a) c+R*[cos(a2), sin(a2)] ];

function fragments(r=1) = ($fn > 0) ? 
  ($fn >= 3 ? $fn : 3) : 
  ceil(max(min(360.0 / $fa, r*2*PI / $fs), 5));
espr14 commented 4 years ago

This produces more precise sphere with the same number of triangles.

sphere

module sphere(r=1,$fn=$fn){
    n=$fn/2+1;
    efn=2*ceil($fn/2);

    points = concat([[0,0,-r]],
        [for(j=[1:efn/2], i=[0:$fn-1])
            [r*cos((j-efn/4)*360/efn)*cos((i-j/2)*360/$fn),r*cos((j-efn/4)*360/efn)*sin((i-j/2)*360/$fn),r*sin((j-efn/4)*360/efn)]],
        [[0,0,r]]
    );

    faces = concat(
         ///bottom
        [for(i=[0:$fn-2]) [0,i+1,i+2]], [[0,$fn,1]],

        ///even on side
        [for(z=[1:$fn:$fn*(efn/2-2)+1])
            each concat(
                [for(i=[0:$fn-2]) [z+i,z+$fn+i,z+$fn+i+1]],
                [[z+$fn-1,z+$fn+$fn-1,z+$fn]]
            )
        ],

        ///odd on side
        [for(z=[1:$fn:$fn*(efn/2-2)+1])
            each concat(
                [for(i=[0:$fn-2]) [z+i,z+$fn+i+1,z+i+1]],
                [[z+$fn-1,z+$fn,z]]
            )
        ]
        );

    polyhedron(points=points, faces=faces);
}
jordanbrown0 commented 8 months ago

Related to #1990, which is arguably the 2D case of the same thing: when the vertices aren't on the axes, the bounding box is wrong.

nophead commented 8 months ago

Yes I have simply redefined sphere() in NopSCADlib to have points on all six half axes. I have never found a problem with doing that.