看到情人节快到了,我决定创造一颗心。所以我找到了this heart from mathematica.se:
我在Mathematica中玩弄(解决z,切换一些变量)得到心脏z值的这个等式,给定x和y值(点击查看全尺寸):
我忠实地将这个等式移植到Java,处理了几个越界的情况:
import static java.lang.Math.cbrt;
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;
...
public static double heart(double xi, double yi) {
double x = xi;
double y = -yi;
double temp = 5739562800L * pow(y, 3) + 109051693200L * pow(x, 2) * pow(y, 3)
- 5739562800L * pow(y, 5);
double temp1 = -244019119519584000L * pow(y, 9) + pow(temp, 2);
//
if (temp1 < 0) {
return -1; // this is one possible out of bounds location
// this spot is the location of the problem
}
//
double temp2 = sqrt(temp1);
double temp3 = cbrt(temp + temp2);
if (temp3 != 0) {
double part1 = (36 * cbrt(2) * pow(y, 3)) / temp3;
double part2 = 1 / (10935 * cbrt(2)) * temp3;
double looseparts = 4.0 / 9 - 4.0 / 9 * pow(x, 2) - 4.0 / 9 * pow(y, 2);
double sqrt_body = looseparts + part1 + part2;
if (sqrt_body >= 0) {
return sqrt(sqrt_body);
} else {
return -1; // this works; returns -1 if we are outside the heart
}
} else {
// through trial and error, I discovered that this should
// be an ellipse (or that it is close enough)
return Math.sqrt(Math.pow(2.0 / 3, 2) * (1 - Math.pow(x, 2)));
}
}
唯一的问题是,在temp1 < 0
时,我不能像我一样简单地返回-1
:
if (temp1 < 0) {
return -1; // this is one possible out of bounds location
// this spot is the location of the problem
}
那不是那时心脏的行为。事实上,当我试图制作我的形象时:
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import static java.lang.Math.cbrt;
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;
public class Heart {
public static double scale(int x, int range, double l, double r) {
double width = r - l;
return (double) x / (range - 1) * width + l;
}
public static void main(String[] args) throws IOException {
BufferedImage img = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_RGB);
// this is actually larger than the max heart value
final double max_heart = 0.679;
double max = 0.0;
for (int x = 0; x < img.getWidth(); x++) {
for (int y = 0; y < img.getHeight(); y++) {
double xv = scale(x, img.getWidth(), -1.2, 1.2);
double yv = scale(y, img.getHeight(), -1.3, 1);
double heart = heart(xv, yv); //this isn't an accident
// yes I don't check for the return of -1, but still
// the -1 values return a nice shade of pink: 0xFFADAD
// None of the other values should be negative, as I did
// step through from -1000 to 1000 in python, and there
// were no negatives that were not -1
int r = 0xFF;
int gb = (int) (0xFF * (max_heart - heart));
int rgb = (r << 16) | (gb << 8) | gb;
img.setRGB(x, y, rgb);
}
}
ImageIO.write(img, "png", new File("location"));
}
// heart function clipped; it belongs here
}
我明白了:
看看顶部的那个倾角!我尝试将有问题的-1
更改为.5
,从而产生以下结果:
现在心脏有角。但很明显,满足问题的if
的条件。
如何解决此问题?我不希望在我心中有一个洞,我不想要一颗有角的心。如果我可以将角状物剪成心形,并对其余部分进行适当的着色,那就完全没问题了。理想情况下,心脏的两侧会聚在一起作为一个点(心脏在连接处有一点点),但如果它们像角中所示一样弯曲,那也没关系。我怎样才能做到这一点?
答案 0 :(得分:3)
问题很简单。如果我们看一下那个马蹄形区域,我们会得到想象中的数字。部分原因,它应属于我们的心脏。在那个区域,如果我们要评估我们的函数(通过数学,而不是编程),函数的虚部取消。所以看起来应该是这样的(在Mathematica中生成):
基本上,该部分的功能几乎相同;我们只需要用复数而不是实数来算术。这是一个完全正确的功能:
private static double topOfHeart(double x, double y, double temp, double temp1) {
//complex arithmetic; each double[] is a single number
double[] temp3 = cbrt_complex(temp, sqrt(-temp1));
double[] part1 = polar_reciprocal(temp3);
part1[0] *= 36 * cbrt(2) * pow(y, 3);
double[] part2 = temp3;
part2[0] /= (10935 * cbrt(2));
toRect(part1, part2);
double looseparts = 4.0 / 9 - 4.0 / 9 * pow(x, 2) - 4.0 / 9 * pow(y, 2);
double real_part = looseparts + part1[0] + part2[0];
double imag_part = part1[1] + part2[1];
double[] result = sqrt_complex(real_part, imag_part);
toRect(result);
// theoretically, result[1] == 0 should work, but floating point says otherwise
if (Math.abs(result[1]) < 1e-5) {
return result[0];
}
return -1;
}
/**
* returns a specific cuberoot of this complex number, in polar form
*/
public static double[] cbrt_complex(double a, double b) {
double r = Math.hypot(a, b);
double theta = Math.atan2(b, a);
double cbrt_r = cbrt(r);
double cbrt_theta = 1.0 / 3 * (2 * PI * Math.floor((PI - theta) / (2 * PI)) + theta);
return new double[]{cbrt_r, cbrt_theta};
}
/**
* returns a specific squareroot of this complex number, in polar form
*/
public static double[] sqrt_complex(double a, double b) {
double r = Math.hypot(a, b);
double theta = Math.atan2(b, a);
double sqrt_r = Math.sqrt(r);
double sqrt_theta = 1.0 / 2 * (2 * PI * Math.floor((PI - theta) / (2 * PI)) + theta);
return new double[]{sqrt_r, sqrt_theta};
}
public static double[] polar_reciprocal(double[] polar) {
return new double[]{1 / polar[0], -polar[1]};
}
public static void toRect(double[]... polars) {
for (double[] polar: polars) {
double a = Math.cos(polar[1]) * polar[0];
double b = Math.sin(polar[1]) * polar[0];
polar[0] = a;
polar[1] = b;
}
}
要将此功能加入您的程序,只需更改您的功能即可反映:
if (temp1 < 0) {
return topOfHeart(x, y, temp, temp1);
}
运行它,我们得到了理想的结果:
应该很清楚,这个新函数实现了完全相同的公式。但是每个部分如何运作?
double[] temp3 = cbrt_complex(temp, sqrt(-temp1));
cbrt_complex
采用a + b i
形式的复数。这就是为什么第二个参数只是sqrt(-temp1)
(注意temp1 < 0
,所以我使用-
代替Math.abs
; Math.abs
可能是个更好的主意。 cbrt_complex
以极坐标形式返回复数的立方根:r eiθ
。 We can see from wolframalpha具有正r
和θ
的{{3}},我们可以编写复数的第n个根,如下所示:
这正是cbrt_complex
和sqrt_complex
的代码的工作方式。请注意,两者都采用直角坐标(a + b i
)中的复数并返回极坐标中的复数(r eiθ
)
double[] part1 = polar_reciprocal(temp3);
采用极坐标复数的倒数比矩形复数更容易。如果我们有r eiθ
,它的倒数(这遵循标准的幂规则,幸运的话)就是1/r e-iθ
。这就是为什么我们保持极地形式的原因;极性形式使乘法类型操作更容易,加法类型操作更难,而矩形形式则相反。
请注意,如果我们的极坐标数r eiθ
并且我们希望乘以实数d
,则答案就像d r eiθ
一样简单。
toRect
函数完全按照它的样子执行:它将极坐标复数转换为直角坐标复数。
您可能已经注意到if语句没有检查是否存在 no 虚部,但仅当虚部非常小时才会检查。这是因为我们使用浮点数,因此检查result[1] == 0
可能会失败。
你有!请注意,我们实际上可以使用这个复数运算来实现整个心脏函数,但是避免这种情况可能会更快。