vectorgraphics / asymptote

2D & 3D TeX-Aware Vector Graphics Language
https://asymptote.sourceforge.io/
GNU General Public License v3.0
533 stars 89 forks source link

geometry.asy shifting circle returns an ellipse #432

Closed pivaldi closed 4 months ago

pivaldi commented 4 months ago

Hello Asymptote team !

I intend to resurect the examples of geometry.asy that I made available on piprime.fr :) However, this minimalist code exemple :

import geometry;
size(5cm);

ellipse c1 = shift(0, 0) * circle(origin, 1);
circle c2 = shift(1, 0) * circle(origin, 1);

draw(c1^^c2);

produces the compilation error

circle c2 = shift(1, 0) * circle(origin, 1);
                        ^
essai.asy: 5.25: cannot cast 'ellipse' to 'circle'

One fix is to explicitly cast the ellipse to circle because it's a circle : circle c2 = (circle) (shift(1, 0) * circle(origin, 1)); but the code becomes annoying to write and to read…

Using, git bisect I found that the commit cd22997a274d034d065b9fcf7afd3ff9bddcb22c introduced this behavior.

There is a simple way to translate a circle without casting ellipse to circle ?

Best regards.

charlesstaats commented 4 months ago

For reference, Here's the bug that inspired that commit:

https://github.com/vectorgraphics/asymptote/issues/119#issuecomment-539815349

For the record, I agree that implicitly casting an ellipse to a circle is probably not a good idea. Adding the following (untested!) definition to geometry.asy would perhaps solve the current issue:

circle operator * (transform t, explicit circle c) {
  return (circle) (t * (ellipse)c);
}

That way the user does not need to worry about explicit casting.

johncbowman commented 4 months ago

Hi Philippe! It's great to hear from you; we are delighted to hear of the plan to resurrect piprime.fr!

A good analogy here is the handling of real (ellipse) and int (circle): an int can be implicitly cast to a real, but an explicit cast is required to convert a real to an int.

An affine transform applied to a circle will in general produce an ellipse, so circle operator * (transform t, explicit circle c) doesn't make sense in general. The shift should be applied only to the center of the circle:

circle c2 = circle(shift(1, 0)*origin, 1);

Alternatively, if the user is certain that the transform has no scaling there will be no information loss in forcing an explicit cast:

circle c2 = (circle) (shift(1, 0) * circle(origin, 1));

pivaldi commented 4 months ago

Thank you very much for yours answers ! You're right, T * circle must return an ellipse and it's to the user to ensure that the transformation leaves "the circle circular" :laughing: . The god news is that geometry.asy checks if the casting of ellipse to circle make sense (line 3301) :

  if(!infb && abs(el.a - el.b) > epsgeo)
    abort("Can not cast ellipse with different axis values to circle");

So, all is right !

Have a god day