Project #8 CMSC 131
Due:  Wednesday 5/10 at 11:00 PM Object-Oriented Programming I
Type of Project: Open Spring 2006

Shape Decorator

(The world's slowest drawing tool)

Objective

To practice inheritance, polymorphism, abstract classes and abstract methods.  You'll also get to practice using formulas from your pre-calculus class as tools for drawing geometrical shapes.  Finally, this project will expose you to the "Decorator" design pattern.

Click here to see a video of the project working.

 

Overview

Note that this project is "Open".  Please remember that this policy allows more leeway in discussing the implementation of the project, but you are still required to do your own work. 

For this project you will write a "shape library", which will be used by a GUI that we are providing.  Below is a diagram of most of the classes you will write.  This type of diagram is called a UML class diagram.  (UML stands for Unified Modeling Language.)  UML diagrams are used by the pros, and you'll learn to draw some of them yourself if you take CMSC 132!


How to Read the UML Class Diagram

It is common with this sort of diagram to omit details occasionally, and to show only those features that are important for the intended audience.  For example, I have left out one of the classes (MyPoint) because it is not part of the inheritance hierarchy, and I also left out the parameter lists for some of the constructors, because they are not very interesting or enlightening.

Each box in the diagram represents a class, and is divided into three regions:

Note that the types of fields (and method return types) come after the name.  For example, "degrees : int" represents an int variable called "degrees", and "getWidth() : int" represents a method named "getWidth" that will return an int.  Strangely, the order of the type and variable name are the other way around (Java style) for parameters of methods, as in "long time".

Things in italics are "abstract", including classes and methods.


Decorator Design Pattern

The relationship among the classes in your shape library is an illustration of a common design pattern called the "Decorator" pattern.  The idea is that we have several "concrete" shapes that can be instantiated any time:  Ellipse, Rectangle, and Triangle.  Typical syntax might be:

            Shape x = new Rectangle(...);   // a plain (undecorated) rectangle

Anytime you have a "Shape" in hand, you can wrap it inside one of the classes that are "Decorators":  Blinking, Shifting, and Rotating.  Each of these Decorator classes has a constructor that accepts a Shape parameter -- the Decorator "wraps" the Shape.  The interesting part is that each of the Decorators Is-A Shape as well, so once you have wrapped a Shape inside a Decorator, you can then wrap that Decorator inside another Decorator, etc.  Typical syntax might be:

            Shape x = new Shifting(new Rotating(new Triangle(...)));    // a shifting, rotating triangle


Specifications

Below are detailed descriptions of the classes and methods you must write.  Note that there is one class that you will write that was not included in the UML class diagram (it's the MyPoint class).  All of the classes you write must go into a package called "shapeLibrary".

1.  MyPoint

This class represents an immutable "point" on the GUI.  We'll use it to specify the locations of our shapes.

State:

Behaviors:

2. Shape

This class must be declared as abstract.  It is the top level class in the inheritance diagram.

State:  (none)

Abstract methods (i.e. declared, but not implemented in the Shape class):

Behaviors:

Below are a couple of methods of the Graphics class that you will need to use for drawing:

  1. setColor(Color c) -- specify the color you want to use while you are drawing (i.e., the color of the Shape)
  2. drawLine(x1, y1, x2, y2)  -- draw a line from the point (x1, y1) to the point (x2, y2)

The drawMe method that you are writing will draw the current object (the Shape) by cycling through EVERY pixel in the bounding rectangle.  You can compute the dimensions and position of this rectangle based on the Shape's center location, width, and height.  For each pixel in the bounding rectangle, you will check whether or not the pixel is inside the Shape by calling the shape's isPointInside() method.  This method will return true if the point is inside the shape, and false otherwise.  If the pixel is inside the shape, then it should be colored.  If the pixel is not inside the shape, then don't color it.  (This technique is very slow, but makes for a great CMSC131 project!)

Unfortunately, Fawzi couldn't figure out how to color a single pixel (if you figure it out, please post to the wiki), so the way I colored each pixel was to draw a line from it to itself.  In other words, to color the pixel at point p, I used:

g.drawLine(p.getX(), p.getY(), p.getX(), p.getY());

3.  ConcreteShape

This class is abstract and extends the Shape class.

State:

Abstract method (not implemented in this class):

Behaviors:

4.  Ellipse

This class extends ConcreteShape.

State:  (none other than what is inherited)

Behaviors:

5.  Triangle

This class is similar to the one above, but for a Triangle shape. The shape of the Triangle will be as drawn below.  The triangle in the diagram is centered at the point (25, 20), has a width of 30 and a height of 20.  The triangle shape is an isosceles triangle, so the corner at the top is directly above the center.  Note that the base of the triangle must be drawn on the bottom (don't accidentally draw the triangle upside-down!)  When implementing the isPointInside method, you may want to consider the interior of the triangle as the solution to a set of three linear inequalities (one for each side of the triangle).  The following formulas may be helpful:

Calculating the slope of a line passing through two given points:

        m = (y1 - y2) / (x1 - x2)      

The equation of a line with slope m, passing through the point (x1, y1).  (You should turn these equations into inequalities.)

        y - y1 = m (x - x1)              

6.  Rectangle

Similar to the previous two classes, but the shape is a rectangle.  When implementing the isPointInside method, you have to check if the point is in the interior of the bounding rectangle.  (Do not simply return "true" -- the method may be called for points that are way outside the bounding rectangle for the Shape -- this will happen in this project when the "rotation" decoration is being used.)

7.  ShapeDecoration

This abstract class extends the Shape class.  It is the base for all of the "Decorations".  The idea is that each Decoration "wraps" a Shape, and at the same time each Decoration "Is-A" Shape.  The subclasses of this class are the actual decorations. They may override some of the methods that are implemented by this class.

State:

Abstract methods (not implemented in this class):

Behaviors:

8.  Blinking

This class extends the ShapeDecoration class.  It wraps a Shape and makes it blink.

State (in addition to the inherited part):

Behaviors:

The first thing this method must do is to call the updateState method for the base Shape.  (That way, if the base Shape is itself a decoration it will also take effect.)  Then compute the value of the expression:     time % 2000 

If this value is less than 1000, set the color to Color.BLACK.  Otherwise, set the color of the current object to the color of the base Shape.

9.  Rotating

This class extends the ShapeDecoration class.  It wraps a Shape and makes it rotate at a rate of 1 degree for every 1/100th of a second that has passed.

State (in addition to the inherited part):

Behaviors:

The first thing this method must do is to call the updateState method for the base Shape.  (That way, if the base Shape is itself a decoration it will also take effect.)  Then set the field degrees to the value:     (int)(time / 10) % 360.

Pass the parameter, p, to the GeometryTools.findPreImage method, along with the other required parameters (take a look at the source-code, provided).  The method will return a MyPoint object, let's call it q.  What you need to do is simply check if q is inside of the base shape.  (Just call the isPointInside method for the base, passing in q.)  If q is in the interior of the base shape, then p must be in the interior of the rotated version, so return true.  If q is not in the interior of the base shape, then p is not in the interior of the rotated version, so return false.

10.  Shifting

This class extends ShapeDecoration.  It will implement the isPointInside method so that numerous equally spaced diagonal lines passing through the object will not be drawn.  (Kind of like thin diagonal stripes with no color.)  As time progresses, these vertical lines will shift, so it looks kind of animated.

State (in addition to the inherited part):

Behaviors:

(p.getX() + p.getY()) % 29 != cycle

If p is in the interior of the base shape, AND this condition holds, then return true;  otherwise return false.

(int)(time / 37)) % 29;


Accuracy in Calculations

Here's a big hint:  You will be doing geometrical calculations based on quantities that are represented as integers -- don't forget that Java will truncate results when you do arithmetic with ints!  Be sure to cast all values to type double before doing the geometrical calculations with them!


Warning:  Potential for "Overflow"

Depending on how you have written your code, you may see strange things happen at rotation angles very close to 90 degrees. The reason is that the x and y coordinates of the MyPoint that you get back from the "findPreImage" method can be very large (or very large negative values). This can lead to "overflow errors" if you try to do arithmetic or other operations on them. If you're having trouble with this, I suggest hardcoding into your isPointInside methods something that checks if the x and/or y coordinates are really big (or really big negative values), and if so, just return false.


Running the GUI

To run the GUI that we have provided, run the main method in the Driver class, which is in the "drawingMachine" package.


Grading

The testing for this project will be different from previous projects.  You will submit your project as usual, and the submit server will compile it.  But there won't be any tests done at that time! Your project will be tested by us AFTER the due date.  Don't worry:  if you get the GUI that is provided to work properly with your project, then you should be fine.  Don't forget that you still have to SUBMIT your project on time, even though you will not get much feedback from the Submit Server, other than being able to verify that your project was successfully received and whether or not it compiled.  You will also get the usual "FindBugs" warnings if your code contains any bugs that the "FindBugs" utility is able to detect.  (We've been providing this feedback all semester, in case you didn't notice, or in case your code was so good that you never saw any of the FindBugs warning messages!)


Challenge Problem

The challenge problem is to come up with your own interesting Decoration for the shapes.  You must call your class ChallengeProblemDecoration.   It must extend the ShapeDecoration class, and it must have a constructor that looks like:

public ChallengeProblemDecoration(Shape base)

You can just include this class in your shapeLibrary package along with the rest of your files, and submit as usual.

Web Accessibility