From 6dd81a3507eeb47bb755197c0042cf6d31e4313b Mon Sep 17 00:00:00 2001 From: opelly27 <35671196+opelly27@users.noreply.github.com> Date: Thu, 2 Sep 2021 15:13:51 -0700 Subject: [PATCH] First upload --- Body.class | Bin 0 -> 1878 bytes Body.java | 152 +++++++ Space.class | Bin 0 -> 1206 bytes Space.java | 50 +++ StdDraw.class | Bin 0 -> 20027 bytes StdDraw.java | 1118 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1320 insertions(+) create mode 100644 Body.class create mode 100644 Body.java create mode 100644 Space.class create mode 100644 Space.java create mode 100644 StdDraw.class create mode 100644 StdDraw.java diff --git a/Body.class b/Body.class new file mode 100644 index 0000000000000000000000000000000000000000..2ef4398c90c2aafb1d24e43da9fb612bab5a1fba GIT binary patch literal 1878 zcmb7E-%}G;6#j0q*=*uXKnSpyYJe(i7HcXNYl={m6dMGrjRvW0XOGZL7##pBh{sDdJ_~0MY2ix!NCS)efi#xOT-gC}(zw_gsd;fa-=PLjUco>Ec z{vb4o0Ca)D$Ig+HsyNkB>dwyNVxcLZUoTh7%_RZ9mCT1=AQVJc;uNHScC)s7Ea0m@ z6%ZL^j7khBi?4pH#M2T(u6XoRi7|;0Pjszg5~fSD?=%_)&Ilk()O!+VaZVsuDmEX~ zYYo?RMp-5#COejGTJ&XRp}sIKU^<8y2@6SqXg7@wr}-TT>b36$0#+uIB+&)TN?gPT zOrY`PfH`WGt2dC6_)s}8l!#tiL3Zn;1rRWa--=~3q^sDwXRCC zpu8AJOMKD^cY`VFi?6qy|Lr@DDC3gEr-~q?Ts^8C6pBiFL*iyfyX|UK-7*rNxpjNw zmhqOv=bp&oi(3Mbm0DGW(9AoPLxy26QGUrhQ(LL+QZuk#t`;{B_jig1_ne)I!Zd5S z=0Uky5{OyJ^=`#H?SfZ?T)kM_WkDI_s){jJa4OD$faF}*Y%2xA*28w_vBG6S^~t>dvet=RtA=aGT>grMvL6$$?4x4+uNiLeMq? zEktZH*utP~hFTc5XIdEjogje?j^_~M7=^|=5F*_GPP5;K5XUek5F=_6ciQxE&g^&i zhsK|s5a_OIoFjXSql&|U?nXCnzd$@Y%OS0CnMmtgC(?#xYNZ&aUgEr|n?`$CcnLcl zHp9Q2P@f@gh8kJ)a4bCm9p{62du&vV?u6doW&c^+T$(fNjneoF*cQvX{Re!rOhhj^i0 z)^>1b$)Qd0P(d+{q?1h#I;#1lOI@}B0Cy0+{ALwW= v^a(-t`$AWGp@)R#`$AWHp~t+!TP&(~Eyh_WRV>lH?OvgNqI#PKAHMnzSSt@L literal 0 HcmV?d00001 diff --git a/Body.java b/Body.java new file mode 100644 index 0000000..a56d01f --- /dev/null +++ b/Body.java @@ -0,0 +1,152 @@ +public class Body { + + public static final double G = 6.673e-11; + private double mass; + private double px; + private double py; + private double vx; + private double vy; + private double fx; + private double fy; + + public Body(double mass, double px, double py, double vx, double vy){ + this.px = px; + this.py = py; + this.vx = vx; + this.vy = vy; + this.mass = mass; + } + + + @Override + public String toString() { + return ""; + } + + public double getDistance(Body otherBody){ + + double x1 = this.getXpos(); + double x2 = otherBody.getXpos(); + double y1 = this.getYpos(); + double y2 = otherBody.getYpos(); + + + double distance = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); + + + return distance; + } + + public double getSpeed(){ + + return Math.sqrt((this.getXvelo() * this.getXvelo()) + (this.getYvelo() * this.getYvelo())); + } + + public double getForceScalar(Body otherBody){ + + double distance = this.getDistance(otherBody); + double m1 = this.getMass(); + double m2 = otherBody.getMass(); + + double force = G * ((m1 * m2) / distance); + + return force; + } + + public double getXforce(Body otherBody){ + + double distance = otherBody.getXpos() - this.getXpos(); + double m1 = this.getMass(); + double m2 = otherBody.getMass(); + + double force = G * ((m1 * m2) / distance); + + return force; + + + } + + public double getYforce(Body otherBody){ + + double distance = otherBody.getYpos() - this.getYpos(); + double m1 = this.getMass(); + double m2 = otherBody.getMass(); + + double force = G * ((m1 * m2) / distance); + + return force; + } + + public void calcNetForce(Body[] bodies){ + double netXForce = 0; + double netYForce = 0; + for(int i = 0; i < bodies.length; i ++){ + if(this != bodies[i]){ + netXForce = netXForce + this.getXforce(bodies[i]); + netYForce = netYForce + this.getYforce(bodies[i]); + } + } + + this.fx = netXForce; + this.fy = netYForce; + } + + public void updateVelocity(){ + // TODO: Implament updateVelocity + } + + public double getXpos(){ + return this.px; + } + + public double getYpos(){ + return this.py; + } + + public double getXvelo(){ + return this.vx; + } + + public double getYvelo(){ + return this.vy; + } + + public double getMass(){ + return this.mass; + } + + public double getNetXforce(){ + return this.fx; + } + + public double getNetYforce(){ + return this.fy; + } + + public static void main(String[] args){ + + // Body testBody = new Body(15000, 0, 0, 10, 10); + // Body testBody2 = new Body(27000, 2, 1, 10, 10); + // Body testBody3 = new Body(29530, 4, 2, 10, 10); + // Body testBody4 = new Body(29530000, -4, 4, 10, 10); + // Body[] bodies = new Body[4]; + // bodies[0] = testBody; + // bodies[1] = testBody2; + // bodies[2] = testBody3; + // bodies[3] = testBody4; + + // System.out.println(testBody.getSpeed()); + // System.out.println(testBody.getDistance(testBody2)); + // System.out.println(testBody.getXforce(testBody2)); + // System.out.println(testBody.getYforce(testBody2)); + + // testBody.calcNetForce(bodies); + // System.out.println(testBody.fx); + // System.out.println(testBody.fy); + + + + + } + +} diff --git a/Space.class b/Space.class new file mode 100644 index 0000000000000000000000000000000000000000..163a2485333647e17577434993ca8c2f7089264c GIT binary patch literal 1206 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 draw. This class provides a basic capability for + * creating drawings with your programs. It uses a simple graphics model that + * allows you to create drawings consisting of points, lines, and curves + * in a window on your computer and to save the drawings to a file. + *

+ * For additional documentation, see Section 1.5 of + * Introduction to Programming in Java: An Interdisciplinary Approach by Robert Sedgewick and Kevin Wayne. + * + * @author Robert Sedgewick + * @author Kevin Wayne + */ +public final class StdDraw implements ActionListener, MouseListener, MouseMotionListener, KeyListener { + + // pre-defined colors + public static final Color BLACK = Color.BLACK; + public static final Color BLUE = Color.BLUE; + public static final Color CYAN = Color.CYAN; + public static final Color DARK_GRAY = Color.DARK_GRAY; + public static final Color GRAY = Color.GRAY; + public static final Color GREEN = Color.GREEN; + public static final Color LIGHT_GRAY = Color.LIGHT_GRAY; + public static final Color MAGENTA = Color.MAGENTA; + public static final Color ORANGE = Color.ORANGE; + public static final Color PINK = Color.PINK; + public static final Color RED = Color.RED; + public static final Color WHITE = Color.WHITE; + public static final Color YELLOW = Color.YELLOW; + + /** + * Shade of blue used in Introduction to Programming in Java. + * It is Pantone 300U. The RGB values are approximately (9, 90, 166). + */ + public static final Color BOOK_BLUE = new Color( 9, 90, 166); + public static final Color BOOK_LIGHT_BLUE = new Color(103, 198, 243); + + /** + * Shade of red used in Algorithms 4th edition. + * It is Pantone 1805U. The RGB values are approximately (150, 35, 31). + */ + public static final Color BOOK_RED = new Color(150, 35, 31); + + // default colors + private static final Color DEFAULT_PEN_COLOR = BLACK; + private static final Color DEFAULT_CLEAR_COLOR = WHITE; + + // current pen color + private static Color penColor; + + // default canvas size is DEFAULT_SIZE-by-DEFAULT_SIZE + private static final int DEFAULT_SIZE = 512; + private static int width = DEFAULT_SIZE; + private static int height = DEFAULT_SIZE; + + // default pen radius + private static final double DEFAULT_PEN_RADIUS = 0.002; + + // current pen radius + private static double penRadius; + + // show we draw immediately or wait until next show? + private static boolean defer = false; + + // boundary of drawing canvas, 5% border + private static final double BORDER = 0.05; + private static final double DEFAULT_XMIN = 0.0; + private static final double DEFAULT_XMAX = 1.0; + private static final double DEFAULT_YMIN = 0.0; + private static final double DEFAULT_YMAX = 1.0; + private static double xmin, ymin, xmax, ymax; + + // for synchronization + private static Object mouseLock = new Object(); + private static Object keyLock = new Object(); + + // default font + private static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 16); + + // current font + private static Font font; + + // double buffered graphics + private static BufferedImage offscreenImage, onscreenImage; + private static Graphics2D offscreen, onscreen; + + // singleton for callbacks: avoids generation of extra .class files + private static StdDraw std = new StdDraw(); + + // the frame for drawing to the screen + private static JFrame frame; + + // mouse state + private static boolean mousePressed = false; + private static double mouseX = 0; + private static double mouseY = 0; + + // queue of typed key characters + private static LinkedList keysTyped = new LinkedList(); + + // set of key codes currently pressed down + private static TreeSet keysDown = new TreeSet(); + + + // singleton pattern: client can't instantiate + private StdDraw() { } + + + // static initializer + static { init(); } + + /** + * Set the window size to the default size 512-by-512 pixels. + */ + public static void setCanvasSize() { + setCanvasSize(DEFAULT_SIZE, DEFAULT_SIZE); + } + + /** + * Set the window size to w-by-h pixels. + * + * @param w the width as a number of pixels + * @param h the height as a number of pixels + * @throws a IllegalArgumentException if the width or height is 0 or negative + */ + public static void setCanvasSize(int w, int h) { + if (w < 1 || h < 1) throw new IllegalArgumentException("width and height must be positive"); + width = w; + height = h; + init(); + } + + // init + private static void init() { + if (frame != null) frame.setVisible(false); + frame = new JFrame(); + offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + onscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + offscreen = offscreenImage.createGraphics(); + onscreen = onscreenImage.createGraphics(); + setXscale(); + setYscale(); + offscreen.setColor(DEFAULT_CLEAR_COLOR); + offscreen.fillRect(0, 0, width, height); + setPenColor(); + setPenRadius(); + setFont(); + clear(); + + // add antialiasing + RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + offscreen.addRenderingHints(hints); + + // frame stuff + ImageIcon icon = new ImageIcon(onscreenImage); + JLabel draw = new JLabel(icon); + + draw.addMouseListener(std); + draw.addMouseMotionListener(std); + + frame.setContentPane(draw); + frame.addKeyListener(std); // JLabel cannot get keyboard focus + frame.setResizable(false); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // closes all windows + // frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // closes only current window + frame.setTitle("Standard Draw"); + frame.setJMenuBar(createMenuBar()); + frame.pack(); + frame.requestFocusInWindow(); + frame.setVisible(true); + } + + // create the menu bar (changed to private) + private static JMenuBar createMenuBar() { + JMenuBar menuBar = new JMenuBar(); + JMenu menu = new JMenu("File"); + menuBar.add(menu); + JMenuItem menuItem1 = new JMenuItem(" Save... "); + menuItem1.addActionListener(std); + menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + menu.add(menuItem1); + return menuBar; + } + + + /************************************************************************* + * User and screen coordinate systems + *************************************************************************/ + + /** + * Set the x-scale to be the default (between 0.0 and 1.0). + */ + public static void setXscale() { setXscale(DEFAULT_XMIN, DEFAULT_XMAX); } + + /** + * Set the y-scale to be the default (between 0.0 and 1.0). + */ + public static void setYscale() { setYscale(DEFAULT_YMIN, DEFAULT_YMAX); } + + /** + * Set the x-scale (a 10% border is added to the values) + * @param min the minimum value of the x-scale + * @param max the maximum value of the x-scale + */ + public static void setXscale(double min, double max) { + double size = max - min; + synchronized (mouseLock) { + xmin = min - BORDER * size; + xmax = max + BORDER * size; + } + } + + /** + * Set the y-scale (a 10% border is added to the values). + * @param min the minimum value of the y-scale + * @param max the maximum value of the y-scale + */ + public static void setYscale(double min, double max) { + double size = max - min; + synchronized (mouseLock) { + ymin = min - BORDER * size; + ymax = max + BORDER * size; + } + } + + /** + * Set the x-scale and y-scale (a 10% border is added to the values) + * @param min the minimum value of the x- and y-scales + * @param max the maximum value of the x- and y-scales + */ + public static void setScale(double min, double max) { + double size = max - min; + synchronized (mouseLock) { + xmin = min - BORDER * size; + xmax = max + BORDER * size; + ymin = min - BORDER * size; + ymax = max + BORDER * size; + } + } + + // helper functions that scale from user coordinates to screen coordinates and back + private static double scaleX(double x) { return width * (x - xmin) / (xmax - xmin); } + private static double scaleY(double y) { return height * (ymax - y) / (ymax - ymin); } + private static double factorX(double w) { return w * width / Math.abs(xmax - xmin); } + private static double factorY(double h) { return h * height / Math.abs(ymax - ymin); } + private static double userX(double x) { return xmin + x * (xmax - xmin) / width; } + private static double userY(double y) { return ymax - y * (ymax - ymin) / height; } + + + /** + * Clear the screen to the default color (white). + */ + public static void clear() { clear(DEFAULT_CLEAR_COLOR); } + /** + * Clear the screen to the given color. + * @param color the Color to make the background + */ + public static void clear(Color color) { + offscreen.setColor(color); + offscreen.fillRect(0, 0, width, height); + offscreen.setColor(penColor); + draw(); + } + + /** + * Get the current pen radius. + */ + public static double getPenRadius() { return penRadius; } + + /** + * Set the pen size to the default (.002). + */ + public static void setPenRadius() { setPenRadius(DEFAULT_PEN_RADIUS); } + /** + * Set the radius of the pen to the given size. + * @param r the radius of the pen + * @throws IllegalArgumentException if r is negative + */ + public static void setPenRadius(double r) { + if (r < 0) throw new IllegalArgumentException("pen radius must be nonnegative"); + penRadius = r; + float scaledPenRadius = (float) (r * DEFAULT_SIZE); + BasicStroke stroke = new BasicStroke(scaledPenRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); + // BasicStroke stroke = new BasicStroke(scaledPenRadius); + offscreen.setStroke(stroke); + } + + /** + * Get the current pen color. + */ + public static Color getPenColor() { return penColor; } + + /** + * Set the pen color to the default color (black). + */ + public static void setPenColor() { setPenColor(DEFAULT_PEN_COLOR); } + + /** + * Set the pen color to the given color. The available pen colors are + * BLACK, BLUE, CYAN, DARK_GRAY, GRAY, GREEN, LIGHT_GRAY, MAGENTA, + * ORANGE, PINK, RED, WHITE, and YELLOW. + * @param color the Color to make the pen + */ + public static void setPenColor(Color color) { + penColor = color; + offscreen.setColor(penColor); + } + + /** + * Set the pen color to the given RGB color. + * @param red the amount of red (between 0 and 255) + * @param green the amount of green (between 0 and 255) + * @param blue the amount of blue (between 0 and 255) + * @throws IllegalArgumentException if the amount of red, green, or blue are outside prescribed range + */ + public static void setPenColor(int red, int green, int blue) { + if (red < 0 || red >= 256) throw new IllegalArgumentException("amount of red must be between 0 and 255"); + if (green < 0 || green >= 256) throw new IllegalArgumentException("amount of red must be between 0 and 255"); + if (blue < 0 || blue >= 256) throw new IllegalArgumentException("amount of red must be between 0 and 255"); + setPenColor(new Color(red, green, blue)); + } + + /** + * Get the current font. + */ + public static Font getFont() { return font; } + + /** + * Set the font to the default font (sans serif, 16 point). + */ + public static void setFont() { setFont(DEFAULT_FONT); } + + /** + * Set the font to the given value. + * @param f the font to make text + */ + public static void setFont(Font f) { font = f; } + + + /************************************************************************* + * Drawing geometric shapes. + *************************************************************************/ + + /** + * Draw a line from (x0, y0) to (x1, y1). + * @param x0 the x-coordinate of the starting point + * @param y0 the y-coordinate of the starting point + * @param x1 the x-coordinate of the destination point + * @param y1 the y-coordinate of the destination point + */ + public static void line(double x0, double y0, double x1, double y1) { + offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1))); + draw(); + } + + /** + * Draw one pixel at (x, y). + * @param x the x-coordinate of the pixel + * @param y the y-coordinate of the pixel + */ + private static void pixel(double x, double y) { + offscreen.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1); + } + + /** + * Draw a point at (x, y). + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + */ + public static void point(double x, double y) { + double xs = scaleX(x); + double ys = scaleY(y); + double r = penRadius; + float scaledPenRadius = (float) (r * DEFAULT_SIZE); + + // double ws = factorX(2*r); + // double hs = factorY(2*r); + // if (ws <= 1 && hs <= 1) pixel(x, y); + if (scaledPenRadius <= 1) pixel(x, y); + else offscreen.fill(new Ellipse2D.Double(xs - scaledPenRadius/2, ys - scaledPenRadius/2, + scaledPenRadius, scaledPenRadius)); + draw(); + } + + /** + * Draw a circle of radius r, centered on (x, y). + * @param x the x-coordinate of the center of the circle + * @param y the y-coordinate of the center of the circle + * @param r the radius of the circle + * @throws IllegalArgumentException if the radius of the circle is negative + */ + public static void circle(double x, double y, double r) { + if (r < 0) throw new IllegalArgumentException("circle radius must be nonnegative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*r); + double hs = factorY(2*r); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + /** + * Draw filled circle of radius r, centered on (x, y). + * @param x the x-coordinate of the center of the circle + * @param y the y-coordinate of the center of the circle + * @param r the radius of the circle + * @throws IllegalArgumentException if the radius of the circle is negative + */ + public static void filledCircle(double x, double y, double r) { + if (r < 0) throw new IllegalArgumentException("circle radius must be nonnegative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*r); + double hs = factorY(2*r); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + + /** + * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y). + * @param x the x-coordinate of the center of the ellipse + * @param y the y-coordinate of the center of the ellipse + * @param semiMajorAxis is the semimajor axis of the ellipse + * @param semiMinorAxis is the semiminor axis of the ellipse + * @throws IllegalArgumentException if either of the axes are negative + */ + public static void ellipse(double x, double y, double semiMajorAxis, double semiMinorAxis) { + if (semiMajorAxis < 0) throw new IllegalArgumentException("ellipse semimajor axis must be nonnegative"); + if (semiMinorAxis < 0) throw new IllegalArgumentException("ellipse semiminor axis must be nonnegative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*semiMajorAxis); + double hs = factorY(2*semiMinorAxis); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + /** + * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y). + * @param x the x-coordinate of the center of the ellipse + * @param y the y-coordinate of the center of the ellipse + * @param semiMajorAxis is the semimajor axis of the ellipse + * @param semiMinorAxis is the semiminor axis of the ellipse + * @throws IllegalArgumentException if either of the axes are negative + */ + public static void filledEllipse(double x, double y, double semiMajorAxis, double semiMinorAxis) { + if (semiMajorAxis < 0) throw new IllegalArgumentException("ellipse semimajor axis must be nonnegative"); + if (semiMinorAxis < 0) throw new IllegalArgumentException("ellipse semiminor axis must be nonnegative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*semiMajorAxis); + double hs = factorY(2*semiMinorAxis); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + + /** + * Draw an arc of radius r, centered on (x, y), from angle1 to angle2 (in degrees). + * @param x the x-coordinate of the center of the circle + * @param y the y-coordinate of the center of the circle + * @param r the radius of the circle + * @param angle1 the starting angle. 0 would mean an arc beginning at 3 o'clock. + * @param angle2 the angle at the end of the arc. For example, if + * you want a 90 degree arc, then angle2 should be angle1 + 90. + * @throws IllegalArgumentException if the radius of the circle is negative + */ + public static void arc(double x, double y, double r, double angle1, double angle2) { + if (r < 0) throw new IllegalArgumentException("arc radius must be nonnegative"); + while (angle2 < angle1) angle2 += 360; + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*r); + double hs = factorY(2*r); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.draw(new Arc2D.Double(xs - ws/2, ys - hs/2, ws, hs, angle1, angle2 - angle1, Arc2D.OPEN)); + draw(); + } + + /** + * Draw a square of side length 2r, centered on (x, y). + * @param x the x-coordinate of the center of the square + * @param y the y-coordinate of the center of the square + * @param r radius is half the length of any side of the square + * @throws IllegalArgumentException if r is negative + */ + public static void square(double x, double y, double r) { + if (r < 0) throw new IllegalArgumentException("square side length must be nonnegative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*r); + double hs = factorY(2*r); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + /** + * Draw a filled square of side length 2r, centered on (x, y). + * @param x the x-coordinate of the center of the square + * @param y the y-coordinate of the center of the square + * @param r radius is half the length of any side of the square + * @throws IllegalArgumentException if r is negative + */ + public static void filledSquare(double x, double y, double r) { + if (r < 0) throw new IllegalArgumentException("square side length must be nonnegative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*r); + double hs = factorY(2*r); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + + /** + * Draw a rectangle of given half width and half height, centered on (x, y). + * @param x the x-coordinate of the center of the rectangle + * @param y the y-coordinate of the center of the rectangle + * @param halfWidth is half the width of the rectangle + * @param halfHeight is half the height of the rectangle + * @throws IllegalArgumentException if halfWidth or halfHeight is negative + */ + public static void rectangle(double x, double y, double halfWidth, double halfHeight) { + if (halfWidth < 0) throw new IllegalArgumentException("half width must be nonnegative"); + if (halfHeight < 0) throw new IllegalArgumentException("half height must be nonnegative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*halfWidth); + double hs = factorY(2*halfHeight); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + /** + * Draw a filled rectangle of given half width and half height, centered on (x, y). + * @param x the x-coordinate of the center of the rectangle + * @param y the y-coordinate of the center of the rectangle + * @param halfWidth is half the width of the rectangle + * @param halfHeight is half the height of the rectangle + * @throws IllegalArgumentException if halfWidth or halfHeight is negative + */ + public static void filledRectangle(double x, double y, double halfWidth, double halfHeight) { + if (halfWidth < 0) throw new IllegalArgumentException("half width must be nonnegative"); + if (halfHeight < 0) throw new IllegalArgumentException("half height must be nonnegative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*halfWidth); + double hs = factorY(2*halfHeight); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + + /** + * Draw a polygon with the given (x[i], y[i]) coordinates. + * @param x an array of all the x-coordindates of the polygon + * @param y an array of all the y-coordindates of the polygon + */ + public static void polygon(double[] x, double[] y) { + int N = x.length; + GeneralPath path = new GeneralPath(); + path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0])); + for (int i = 0; i < N; i++) + path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i])); + path.closePath(); + offscreen.draw(path); + draw(); + } + + /** + * Draw a filled polygon with the given (x[i], y[i]) coordinates. + * @param x an array of all the x-coordindates of the polygon + * @param y an array of all the y-coordindates of the polygon + */ + public static void filledPolygon(double[] x, double[] y) { + int N = x.length; + GeneralPath path = new GeneralPath(); + path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0])); + for (int i = 0; i < N; i++) + path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i])); + path.closePath(); + offscreen.fill(path); + draw(); + } + + + + /************************************************************************* + * Drawing images. + *************************************************************************/ + + // get an image from the given filename + private static Image getImage(String filename) { + + // to read from file + ImageIcon icon = new ImageIcon(filename); + + // try to read from URL + if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { + try { + URL url = new URL(filename); + icon = new ImageIcon(url); + } catch (Exception e) { /* not a url */ } + } + + // in case file is inside a .jar + if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { + URL url = StdDraw.class.getResource(filename); + if (url == null) throw new IllegalArgumentException("image " + filename + " not found"); + icon = new ImageIcon(url); + } + + return icon.getImage(); + } + + /** + * Draw picture (gif, jpg, or png) centered on (x, y). + * @param x the center x-coordinate of the image + * @param y the center y-coordinate of the image + * @param s the name of the image/picture, e.g., "ball.gif" + * @throws IllegalArgumentException if the image is corrupt + */ + public static void picture(double x, double y, String s) { + Image image = getImage(s); + double xs = scaleX(x); + double ys = scaleY(y); + int ws = image.getWidth(null); + int hs = image.getHeight(null); + if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + s + " is corrupt"); + + offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null); + draw(); + } + + /** + * Draw picture (gif, jpg, or png) centered on (x, y), + * rotated given number of degrees + * @param x the center x-coordinate of the image + * @param y the center y-coordinate of the image + * @param s the name of the image/picture, e.g., "ball.gif" + * @param degrees is the number of degrees to rotate counterclockwise + * @throws IllegalArgumentException if the image is corrupt + */ + public static void picture(double x, double y, String s, double degrees) { + Image image = getImage(s); + double xs = scaleX(x); + double ys = scaleY(y); + int ws = image.getWidth(null); + int hs = image.getHeight(null); + if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + s + " is corrupt"); + + offscreen.rotate(Math.toRadians(-degrees), xs, ys); + offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null); + offscreen.rotate(Math.toRadians(+degrees), xs, ys); + + draw(); + } + + /** + * Draw picture (gif, jpg, or png) centered on (x, y), rescaled to w-by-h. + * @param x the center x coordinate of the image + * @param y the center y coordinate of the image + * @param s the name of the image/picture, e.g., "ball.gif" + * @param w the width of the image + * @param h the height of the image + * @throws IllegalArgumentException if the width height are negative + * @throws IllegalArgumentException if the image is corrupt + */ + public static void picture(double x, double y, String s, double w, double h) { + Image image = getImage(s); + double xs = scaleX(x); + double ys = scaleY(y); + if (w < 0) throw new IllegalArgumentException("width is negative: " + w); + if (h < 0) throw new IllegalArgumentException("height is negative: " + h); + double ws = factorX(w); + double hs = factorY(h); + if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + s + " is corrupt"); + if (ws <= 1 && hs <= 1) pixel(x, y); + else { + offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), + (int) Math.round(ys - hs/2.0), + (int) Math.round(ws), + (int) Math.round(hs), null); + } + draw(); + } + + + /** + * Draw picture (gif, jpg, or png) centered on (x, y), rotated + * given number of degrees, rescaled to w-by-h. + * @param x the center x-coordinate of the image + * @param y the center y-coordinate of the image + * @param s the name of the image/picture, e.g., "ball.gif" + * @param w the width of the image + * @param h the height of the image + * @param degrees is the number of degrees to rotate counterclockwise + * @throws IllegalArgumentException if the image is corrupt + */ + public static void picture(double x, double y, String s, double w, double h, double degrees) { + Image image = getImage(s); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(w); + double hs = factorY(h); + if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + s + " is corrupt"); + if (ws <= 1 && hs <= 1) pixel(x, y); + + offscreen.rotate(Math.toRadians(-degrees), xs, ys); + offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), + (int) Math.round(ys - hs/2.0), + (int) Math.round(ws), + (int) Math.round(hs), null); + offscreen.rotate(Math.toRadians(+degrees), xs, ys); + + draw(); + } + + + /************************************************************************* + * Drawing text. + *************************************************************************/ + + /** + * Write the given text string in the current font, centered on (x, y). + * @param x the center x-coordinate of the text + * @param y the center y-coordinate of the text + * @param s the text + */ + public static void text(double x, double y, String s) { + offscreen.setFont(font); + FontMetrics metrics = offscreen.getFontMetrics(); + double xs = scaleX(x); + double ys = scaleY(y); + int ws = metrics.stringWidth(s); + int hs = metrics.getDescent(); + offscreen.drawString(s, (float) (xs - ws/2.0), (float) (ys + hs)); + draw(); + } + + /** + * Write the given text string in the current font, centered on (x, y) and + * rotated by the specified number of degrees + * @param x the center x-coordinate of the text + * @param y the center y-coordinate of the text + * @param s the text + * @param degrees is the number of degrees to rotate counterclockwise + */ + public static void text(double x, double y, String s, double degrees) { + double xs = scaleX(x); + double ys = scaleY(y); + offscreen.rotate(Math.toRadians(-degrees), xs, ys); + text(x, y, s); + offscreen.rotate(Math.toRadians(+degrees), xs, ys); + } + + + /** + * Write the given text string in the current font, left-aligned at (x, y). + * @param x the x-coordinate of the text + * @param y the y-coordinate of the text + * @param s the text + */ + public static void textLeft(double x, double y, String s) { + offscreen.setFont(font); + FontMetrics metrics = offscreen.getFontMetrics(); + double xs = scaleX(x); + double ys = scaleY(y); + int hs = metrics.getDescent(); + offscreen.drawString(s, (float) (xs), (float) (ys + hs)); + draw(); + } + + /** + * Write the given text string in the current font, right-aligned at (x, y). + * @param x the x-coordinate of the text + * @param y the y-coordinate of the text + * @param s the text + */ + public static void textRight(double x, double y, String s) { + offscreen.setFont(font); + FontMetrics metrics = offscreen.getFontMetrics(); + double xs = scaleX(x); + double ys = scaleY(y); + int ws = metrics.stringWidth(s); + int hs = metrics.getDescent(); + offscreen.drawString(s, (float) (xs - ws), (float) (ys + hs)); + draw(); + } + + + + /** + * Display on screen, pause for t milliseconds, and turn on + * animation mode: subsequent calls to + * drawing methods such as line(), circle(), and square() + * will not be displayed on screen until the next call to show(). + * This is useful for producing animations (clear the screen, draw a bunch of shapes, + * display on screen for a fixed amount of time, and repeat). It also speeds up + * drawing a huge number of shapes (call show(0) to defer drawing + * on screen, draw the shapes, and call show(0) to display them all + * on screen at once). + * @param t number of milliseconds + */ + public static void show(int t) { + defer = false; + draw(); + try { Thread.sleep(t); } + catch (InterruptedException e) { System.out.println("Error sleeping"); } + defer = true; + } + + /** + * Display on-screen and turn off animation mode: + * subsequent calls to + * drawing methods such as line(), circle(), and square() + * will be displayed on screen when called. This is the default. + */ + public static void show() { + defer = false; + draw(); + } + + // draw onscreen if defer is false + private static void draw() { + if (defer) return; + onscreen.drawImage(offscreenImage, 0, 0, null); + frame.repaint(); + } + + + /************************************************************************* + * Save drawing to a file. + *************************************************************************/ + + /** + * Save onscreen image to file - suffix must be png, jpg, or gif. + * @param filename the name of the file with one of the required suffixes + */ + public static void save(String filename) { + File file = new File(filename); + String suffix = filename.substring(filename.lastIndexOf('.') + 1); + + // png files + if (suffix.toLowerCase().equals("png")) { + try { ImageIO.write(onscreenImage, suffix, file); } + catch (IOException e) { e.printStackTrace(); } + } + + // need to change from ARGB to RGB for jpeg + // reference: http://archives.java.sun.com/cgi-bin/wa?A2=ind0404&L=java2d-interest&D=0&P=2727 + else if (suffix.toLowerCase().equals("jpg")) { + WritableRaster raster = onscreenImage.getRaster(); + WritableRaster newRaster; + newRaster = raster.createWritableChild(0, 0, width, height, 0, 0, new int[] {0, 1, 2}); + DirectColorModel cm = (DirectColorModel) onscreenImage.getColorModel(); + DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(), + cm.getRedMask(), + cm.getGreenMask(), + cm.getBlueMask()); + BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false, null); + try { ImageIO.write(rgbBuffer, suffix, file); } + catch (IOException e) { e.printStackTrace(); } + } + + else { + System.out.println("Invalid image file type: " + suffix); + } + } + + + /** + * This method cannot be called directly. + */ + public void actionPerformed(ActionEvent e) { + FileDialog chooser = new FileDialog(StdDraw.frame, "Use a .png or .jpg extension", FileDialog.SAVE); + chooser.setVisible(true); + String filename = chooser.getFile(); + if (filename != null) { + StdDraw.save(chooser.getDirectory() + File.separator + chooser.getFile()); + } + } + + + /************************************************************************* + * Mouse interactions. + *************************************************************************/ + + /** + * Is the mouse being pressed? + * @return true or false + */ + public static boolean mousePressed() { + synchronized (mouseLock) { + return mousePressed; + } + } + + /** + * What is the x-coordinate of the mouse? + * @return the value of the x-coordinate of the mouse + */ + public static double mouseX() { + synchronized (mouseLock) { + return mouseX; + } + } + + /** + * What is the y-coordinate of the mouse? + * @return the value of the y-coordinate of the mouse + */ + public static double mouseY() { + synchronized (mouseLock) { + return mouseY; + } + } + + + /** + * This method cannot be called directly. + */ + public void mouseClicked(MouseEvent e) { } + + /** + * This method cannot be called directly. + */ + public void mouseEntered(MouseEvent e) { } + + /** + * This method cannot be called directly. + */ + public void mouseExited(MouseEvent e) { } + + /** + * This method cannot be called directly. + */ + public void mousePressed(MouseEvent e) { + synchronized (mouseLock) { + mouseX = StdDraw.userX(e.getX()); + mouseY = StdDraw.userY(e.getY()); + mousePressed = true; + } + } + + /** + * This method cannot be called directly. + */ + public void mouseReleased(MouseEvent e) { + synchronized (mouseLock) { + mousePressed = false; + } + } + + /** + * This method cannot be called directly. + */ + public void mouseDragged(MouseEvent e) { + synchronized (mouseLock) { + mouseX = StdDraw.userX(e.getX()); + mouseY = StdDraw.userY(e.getY()); + } + } + + /** + * This method cannot be called directly. + */ + public void mouseMoved(MouseEvent e) { + synchronized (mouseLock) { + mouseX = StdDraw.userX(e.getX()); + mouseY = StdDraw.userY(e.getY()); + } + } + + + /************************************************************************* + * Keyboard interactions. + *************************************************************************/ + + /** + * Has the user typed a key? + * @return true if the user has typed a key, false otherwise + */ + public static boolean hasNextKeyTyped() { + synchronized (keyLock) { + return !keysTyped.isEmpty(); + } + } + + /** + * What is the next key that was typed by the user? This method returns + * a Unicode character corresponding to the key typed (such as 'a' or 'A'). + * It cannot identify action keys (such as F1 + * and arrow keys) or modifier keys (such as control). + * @return the next Unicode key typed + */ + public static char nextKeyTyped() { + synchronized (keyLock) { + return keysTyped.removeLast(); + } + } + + /** + * Is the keycode currently being pressed? This method takes as an argument + * the keycode (corresponding to a physical key). It can handle action keys + * (such as F1 and arrow keys) and modifier keys (such as shift and control). + * See KeyEvent.java + * for a description of key codes. + * @return true if keycode is currently being pressed, false otherwise + */ + public static boolean isKeyPressed(int keycode) { + synchronized (keyLock) { + return keysDown.contains(keycode); + } + } + + + /** + * This method cannot be called directly. + */ + public void keyTyped(KeyEvent e) { + synchronized (keyLock) { + keysTyped.addFirst(e.getKeyChar()); + } + } + + /** + * This method cannot be called directly. + */ + public void keyPressed(KeyEvent e) { + synchronized (keyLock) { + keysDown.add(e.getKeyCode()); + } + } + + /** + * This method cannot be called directly. + */ + public void keyReleased(KeyEvent e) { + synchronized (keyLock) { + keysDown.remove(e.getKeyCode()); + } + } + + + + + /** + * Test client. + */ + public static void main(String[] args) { + StdDraw.square(.2, .8, .1); + StdDraw.filledSquare(.8, .8, .2); + StdDraw.circle(.8, .2, .2); + + StdDraw.setPenColor(StdDraw.BOOK_RED); + StdDraw.setPenRadius(.02); + StdDraw.arc(.8, .2, .1, 200, 45); + + // draw a blue diamond + StdDraw.setPenRadius(); + StdDraw.setPenColor(StdDraw.BOOK_BLUE); + double[] x = { .1, .2, .3, .2 }; + double[] y = { .2, .3, .2, .1 }; + StdDraw.filledPolygon(x, y); + + // text + StdDraw.setPenColor(StdDraw.BLACK); + StdDraw.text(0.2, 0.5, "black text"); + StdDraw.setPenColor(StdDraw.WHITE); + StdDraw.text(0.8, 0.8, "white text"); + } + +} \ No newline at end of file