Turtle Graphics
Logo是麻省理工学院发明的一种编程语言,最初用于在太空中移动机器人。
海龟图形,添加到Logo语言中,允许程序员向屏幕上的“海龟”发出一系列指令,
并在其移动时画出一条线。海龟图形也被添加到许多不同的编程语言中,包括Python,
它是标准库的一部分。
这次实验就是学着用turtle画一些图案,以及对图案进行一些角度计算。很简单,提供了两个函数,forward(units)和turn(degrees) ,forward(units)是指前进多少长度,turn(degrees)是指顺时针转多少度。
- Problem 3: Turtle graphics and drawSquare
该Problem要求使用现有的接口(forward和turn)实现drawSquare方法,要求该方法调用时,根据给定的变长画出一个正方形。使用一个简单的for循环即可解决。
/**
* Draw a square.
*
* @param turtle the turtle context
* @param sideLength length of each side
*/
public static void drawSquare(Turtle turtle, int sideLength) {
for (int i = 0; i < 4; i++) {
turtle.forward(sideLength);
turtle.turn(90);
}
}
- Problem 5: Drawing polygons
该Problem需要实现两个方法:calculateRegularPolygonAngle()和drawRegularPolygon()方法,并使用Junit测试。calculateRegularPolygonAngle()方法要求根据给定的边数作为参数,计算出对应的正多边形的内角。只需要使用公式insideAngle=((sides-2)×180)/sides即可,由于insideAngle为double类型,而参与计算的数字都是int整型,如果直接计算可能会发生截断,需要写成2d或180d即可转为浮点运算。drawRegularPolygon()方法要求以边长与边数作为参数,控制turtle画出正多边形。使用循环即可,计算角度时可调用calculateRegularPolygonAngle()方法计算,唯一要注意的就是turtle旋转的角是外角,而不是计算出来的内角。
/**
* Determine inside angles of a regular polygon.
*
* There is a simple formula for calculating the inside angles of a polygon; you
* should derive it and use it here.
*
* @param sides number of sides, where sides must be > 2
* @return angle in degrees, where 0 <= angle < 360
*/
public static double calculateRegularPolygonAngle(int sides) {
return (sides - 2) * 180.0 / sides;
}
/**
* Given the number of sides, draw a regular polygon.
*
* (0,0) is the lower-left corner of the polygon; use only right-hand turns to
* draw.
*
* @param turtle the turtle context
* @param sides number of sides of the polygon to draw
* @param sideLength length of each side
*/
public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) {
double angle = 180 - calculateRegularPolygonAngle(sides);
for (int i = 0; i < sides; i++) {
turtle.forward(sideLength);
turtle.turn(angle);
}
}
- Problem 6: Calculating Bearings
该Problem需要实现两个方法:calculateBearingToPoint()和calculateBearings()方法,并进行Junit测试。calculateBearingToPoint()方法要求计算在当前点、当前朝向的情况下,顺时针转向目标点所需要转动的角度。给定的参数有当前朝向的度数、当前点的X与Y值和目标点的X与Y值,当前朝向的度数以向上方为0度。基本是个数学问题,比较繁琐,分多种情况讨论即可,需要用到反正切函数Math.atan2方法。calculateBearings()方法的参数为两个List,第一个List中为X的值,第二个为Y的值,需要返回一个List,包含所有需要转动的角度。只需要遍历两个集合,调用calculateBearingToPoint()方法即可,可设置一个变量里路当前朝向的角度。
/**
* Given the current direction, current location, and a target location,
* calculate the Bearing towards the target point.
*
* The return value is the angle input to turn() that would point the turtle in
* the direction of the target point (targetX,targetY), given that the turtle is
* already at the point (currentX,currentY) and is facing at angle
* currentBearing. The angle must be expressed in degrees, where 0 <= angle <
* 360.
*
* HINT: look at http://en.wikipedia.org/wiki/Atan2 and Java's math libraries
*
* @param currentBearing current direction as clockwise from north
* @param currentX current location x-coordinate
* @param currentY current location y-coordinate
* @param targetX target point x-coordinate
* @param targetY target point y-coordinate
* @return adjustment to Bearing (right turn amount) to get to target point,
* must be 0 <= angle < 360
*/
public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY, int targetX,
int targetY) {
double angle = Math.atan2(targetX - currentX, targetY - currentY) * 180 / Math.PI - currentBearing;
if (angle < 0)
angle += 360;
return angle;
}
/**
* Given a sequence of points, calculate the Bearing adjustments needed to get
* from each point to the next.
*
* Assumes that the turtle starts at the first point given, facing up (i.e. 0
* degrees). For each subsequent point, assumes that the turtle is still facing
* in the direction it was facing when it moved to the previous point. You
* should use calculateBearingToPoint() to implement this function.
*
* @param xCoords list of x-coordinates (must be same length as yCoords)
* @param yCoords list of y-coordinates (must be same length as xCoords)
* @return list of Bearing adjustments between points, of size 0 if (# of
* points) == 0, otherwise of size (# of points) - 1
*/
public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
List<Double> list = new ArrayList<>();
int N = xCoords.size();
double face = 0;
int i = 0;
while (i < N - 1) {
double k = calculateBearingToPoint(face, xCoords.get(i), yCoords.get(i), xCoords.get(i + 1),
yCoords.get(i + 1));
list.add(k);
face = (face + k) % 360;
i++;
}
System.out.println(list);
return list;
}
- Problem 7: Convex Hulls
该Problem涉及一个著名的图形学概念:凸包。凸包可以理解为给定的点集的一个子集,该子集中点点连线可以将全集都囊括在内。要求实现的convexHull()方法就是在给定的Point的Set中寻找一个最小的凸包。由于是最小的凸包,如果同一条直线上的多个点都是凸包边界上的点,只取两端的点以保证最小。可使用gift wrapping(礼物包装)算法。从最左上(左下)角的点开始,逐个扫描点集中的点,选取与当前朝向偏角最小的点加入结果集,并将其作为下一个当前点继续循环。如果有多个点都是最小转角的点,则选择距离最远的点即可。
/**
* Given a set of points, compute the convex hull, the smallest convex set that
* contains all the points in a set of input points. The gift-wrapping algorithm
* is one simple approach to this problem, and there are other algorithms too.
*
* @param points a set of points with xCoords and yCoords. It might be empty,
* contain only 1 point, two points or more.
* @return minimal subset of the input points that form the vertices of the
* perimeter of the convex hull
*/
public static Set<Point> convexHull(Set<Point> points) {
Point p0, p1;
p0 = new Point(0, 0);
p1 = new Point(0, 0);
double dis, an1, an2, an3;
ArrayList<Point> list1 = new ArrayList<Point>();
Set<Point> list2 = new HashSet<>();
if (points.size() <= 3) {
return points;
} else {
for (Point p : points) {
dis = p.x() * p.x() + p.y() * p.y();
if (dis > p0.x() * p0.x() + p0.y() * p0.y()) {
p0 = p;
}
}
list1.add(p0);
an1 = 361;
for (Point p : points) {
an2 = calculateBearingToPoint(0, (int) p0.x(), (int) p0.y(), (int) p.x(), (int) p.y());
if (an2 < an1 && (p != p0)) {
an1 = an2;
p1 = p;
}
}
list1.add(p1);
an1 = 361;
for (int i = 0; i < points.size(); i++) {
an3 = calculateBearingToPoint(0, (int) list1.get(list1.size() - 2).x(),
(int) list1.get(list1.size() - 2).y(), (int) list1.get(list1.size() - 1).x(),
(int) list1.get(list1.size() - 1).y());
an1 = 361;
for (Point p : points) {
an2 = calculateBearingToPoint(an3, (int) list1.get(list1.size() - 1).x(),
(int) list1.get(list1.size() - 1).y(), (int) p.x(), (int) p.y());
if (an2 < an1 && (p != list1.get(list1.size() - 1))) {
an1 = an2;
p1 = p;
}
}
if (p1 == list1.get(0)) {
break;
} else {
if (an1 == 0) {
list1.remove(list1.size() - 1);
}
list1.add(p1);
}
}
for (Point p : list1) {
list2.add(p);
}
return list2;
}
}
- Problem 8: Personal art
实现drawPersonalArt(),可以自己画一些有趣的东西,可作出一些简单的图形之后偏移一个较小的角度,并循环,即可作出较美观的花纹。
public static void drawPersonalArt(Turtle turtle) {
for (int i = 0; i < 1000; i++) {
turtle.forward(i / 2);
switch ((i / 10) % 10) {
case 0:
turtle.color(PenColor.BLACK);
break;
case 1:
turtle.color(PenColor.GRAY);
break;
case 2:
turtle.color(PenColor.RED);
break;
case 3:
turtle.color(PenColor.PINK);
break;
case 4:
turtle.color(PenColor.ORANGE);
break;
case 5:
turtle.color(PenColor.YELLOW);
break;
case 6:
turtle.color(PenColor.GREEN);
break;
case 7:
turtle.color(PenColor.CYAN);
break;
case 8:
turtle.color(PenColor.BLUE);
break;
case 9:
turtle.color(PenColor.MAGENTA);
break;
}
turtle.turn(135);
}
}
这个任务中需要注意的内容就是如何用库函数求解偏转角度以及计算结果的精度舍入问题。在Java的Math库中,提供了这样的一个方法:atan2方法。该方法通过传入一个向量,即可计算出其对应的角的大小。这个方法的返回值,实际上是直角坐标系y轴正方向和向量所成角的大小,而且返回值为对应角的弧度值。因而要对这个角有一个正确的认识,并将其单位进行一个简单的转换。