001    // This class is based on the FishDisplay class, version 1 August 2002
002    // by Julie Zenekski, Alyce Brady, and Chris Nevison
003    
004    // Original copyright notice:
005    
006    // AP(r) Computer Science Marine Biology Simulation:
007    // The FishDisplay class is copyright(c) 2002 College Entrance
008    // Examination Board (www.collegeboard.com).
009    //
010    // This class is free software; you can redistribute it and/or modify
011    // it under the terms of the GNU General Public License as published by
012    // the Free Software Foundation.
013    //
014    // This class is distributed in the hope that it will be useful,
015    // but WITHOUT ANY WARRANTY; without even the implied warranty of
016    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017    // GNU General Public License for more details.
018    
019    
020    package model.fish.display;
021    
022    import sysModel.fish.IFishDisplay;
023    
024    import java.awt.*;
025    import java.awt.geom.AffineTransform;
026    import java.awt.geom.Ellipse2D;
027    import java.awt.geom.GeneralPath;
028    
029    /**
030     * Parameterized fish display.
031     *
032     * @author Mathias Ricken
033     */
034    public class ParamFishDisplay implements IFishDisplay {
035        private static final double BODY_WIDTH = .55;
036        private static final double BODY_LENGTH = .75;
037        private static final double TAIL_WIDTH = .5;
038        private static final double TAIL_LENGTH = .4;
039        private static final double EYE_SIZE = .08;
040    
041        private static final int GRADIENT_SIZE = 50;
042        private static final AffineTransform ATX = AffineTransform.getScaleInstance(GRADIENT_SIZE, GRADIENT_SIZE);
043    
044        private Shape bodyAndTail;
045        private Shape eye1;
046        private Shape eye2;
047    
048        /**
049         * Make an object that knows how to drawFish simple fish.
050         *
051         * @param bodyWidth  width of the body
052         * @param bodyLength length of the body
053         * @param tailWidth  width of the tail
054         * @param tailLength length of the tail
055         * @param eyeSize    size of the eyes
056         */
057        public ParamFishDisplay(double bodyWidth, double bodyLength, double tailWidth, double tailLength, double eyeSize) {
058            buildPaths(bodyWidth, bodyLength, tailWidth, tailLength, eyeSize);
059        }
060    
061        /**
062         * Default constructor.
063         */
064        public ParamFishDisplay() {
065            this(BODY_WIDTH, BODY_LENGTH, TAIL_WIDTH, TAIL_LENGTH, EYE_SIZE);
066        }
067    
068        /**
069         * Set up the paths used for the fish body, tail, and eyes. Different parameters will change the proportions, and
070         * thereby control the "look" of the fish.  The various parameters should be specified assuming the fish will occupy
071         * a cell of size (1, 1).
072         *
073         * @param bodyWidth  width of the elliptical body
074         * @param bodyLength length of the elliptical body
075         * @param tailWidth  width of the triangular tail
076         * @param tailLength length of the triangular tail
077         * @param eyeSize    diameter of the eye
078         */
079        private void buildPaths(double bodyWidth, double bodyLength, double tailWidth, double tailLength, double eyeSize) {
080            // Build a set of paths for a fish facing North in a unit-length cell.
081            // We will rotate/scale as needed later.
082    
083            float halfFishLength = (float)(bodyLength + tailLength / 3) / 2;
084    
085            // The fish body is an ellipse of the given body width and length.
086            // The ellipse is horizontally centered and slightly above vertical
087            // center (to leave room for tail).
088            Shape body = new Ellipse2D.Double(-bodyWidth / 2, -halfFishLength, bodyWidth, bodyLength);
089    
090            // The fish tail is a triangle overlapping the end of body.
091            GeneralPath tail = new GeneralPath();
092            tail.moveTo(-(float)tailWidth / 2, halfFishLength); // lower left
093            tail.lineTo(0, halfFishLength - (float)tailLength); // top of tail
094            tail.lineTo((float)tailWidth / 2, halfFishLength);  // lower right
095            tail.closePath();
096    
097            // Join body and tail together in one path.
098            tail.append(body, false);
099            bodyAndTail = tail;
100    
101            // The fish eyes are circles.
102            eye1 = new Ellipse2D.Double(-bodyWidth / 4, -halfFishLength + bodyLength / 4, eyeSize, eyeSize);
103            eye2 = new Ellipse2D.Double(+bodyWidth / 4 - eyeSize, -halfFishLength + bodyLength / 4, eyeSize, eyeSize);
104        }
105    
106        /**
107         * Draw the fish facing north on the Graphics2D object. The Graphics2D object has been prepared such that the center
108         * of the fish is at the origin.
109         *
110         * @param g2        drawing surface
111         * @param comp      the component to drawFish on
112         * @param fishColor color of the fish
113         */
114        public void draw(Graphics2D g2, Component comp, Color fishColor) {
115            Color oldColor = g2.getColor();
116    
117            // Stroke outline of fish body and tail in slightly darker color.
118            g2.setPaint(fishColor.darker());
119            g2.draw(bodyAndTail);
120    
121            // Fill fish body and tail with gradient (scale up temporarily to get smooth dither).
122            g2.scale(1.0 / GRADIENT_SIZE, 1.0 / GRADIENT_SIZE);
123            g2.setPaint(new GradientPaint(-GRADIENT_SIZE / 4,
124                -GRADIENT_SIZE / 2,
125                Color.white,
126                GRADIENT_SIZE / 4,
127                GRADIENT_SIZE / 4,
128                fishColor));
129            g2.fill(ATX.createTransformedShape(bodyAndTail));
130            g2.scale(GRADIENT_SIZE, GRADIENT_SIZE);
131    
132            // Fill black circles for the eyes.
133            g2.setPaint(Color.black);
134            g2.fill(eye1);
135            g2.fill(eye2);
136    
137            g2.setColor(oldColor);
138        }
139    }