/**************************************************************************** ** COPYRIGHT (C): 1997 Cay S. Horstmann. All Rights Reserved. ** PROJECT: Practical OO Development with C++ and Java ** FILE: shapes.cpp ** PURPOSE: graphical shapes ** VERSION 1.0 ** PROGRAMMERS: Cay Horstmann (CSH), Jiaoyang Zhou (JYZ) ** RELEASE DATE: 3-15-97 (CSH) ** UPDATE HISTORY: ****************************************************************************/ #include "setup.h" #include #include EXPORT #include "graphics.h" #define min(a, b) (((a) < (b)) ? (a) : (b)) #define max(a, b) (((a) > (b)) ? (a) : (b)) EXPORT class Point; EXPORT class Segment; EXPORT class Shape { public: virtual void scale(Point center, double s) = 0; virtual void move(int x, int y) = 0; virtual void read(istream& is) = 0; virtual void plot(Graphics& g) const = 0; virtual void print(ostream& os) const = 0; virtual Shape* clone() const = 0; virtual ~Shape() {} }; /*-------------------------------------------------------------*/ EXPORT class Point : public Shape { public: Point(int xx =0, int yy =0); virtual void scale(Point center, double s); virtual void move(int x, int y); virtual void read(istream& is); virtual void plot(Graphics& g) const; virtual void print(ostream& os) const; virtual Shape* clone() const; int x() const; int y() const; double distance(Point b) const; double angle(Point b) const; #ifdef PRACTICALOO_COMPILER_UNREASONABLY_INSISTS_ON_COMPARISON_OPERATORS_FOR_VECTORS bool operator<(const Point& b) const { return _x < b._x || _x == b._x && _y < b._y; } bool operator==(const Point& b) const { return _x == b._x && _y == b._y; } #endif private: int _x; int _y; }; /*-------------------------------------------------------------*/ EXPORT class Rectangle : public Shape { public: Rectangle(); Rectangle(Point, Point); virtual void scale(Point center, double s); virtual void move(int x, int y); virtual void read(istream& is); virtual void plot(Graphics& g) const; virtual void print(ostream& os) const; virtual Shape* clone() const; Point topleft() const; Point bottomright() const; Point center() const; bool is_inside(Point p) const; Point boundary_point(Point p) const; private: Point _topleft; Point _bottomright; }; /*-------------------------------------------------------------*/ EXPORT class FilledRect : public Rectangle { public: FilledRect(); FilledRect(Point, Point, Color); virtual void plot(Graphics& g) const; private: Color _color; }; /*-------------------------------------------------------------*/ EXPORT class Segment : public Shape { public: Segment(); Segment(Point f, Point t); virtual void scale(Point center, double s); virtual void move(int x, int y); virtual void read(istream& is); virtual void plot(Graphics& g) const; virtual void print(ostream& os) const; virtual Shape* clone() const; Point center() const; bool intersect(Segment s, Point& p) const; Point closest(Point p) const; double distance(Point p) const; Point from() const; Point to() const; private: Point _from; Point _to; }; /*-------------------------------------------------------------*/ EXPORT class Ellipse : public Shape { public: Ellipse(); Ellipse(Point, int, int); virtual void scale(Point center, double s); virtual void move(int x, int y); virtual void read(istream& is); virtual void plot(Graphics& g) const; virtual void print(ostream& os) const; virtual Shape* clone() const; Point center() const; int xradius() const; int yradius() const; bool is_inside(Point p) const; Point boundary_point(Point p) const; private: Point _center; int _xradius; int _yradius; }; /*-------------------------------------------------------------*/ EXPORT class Polygon : public Shape { public: Polygon(); Polygon(int); void set_corner(int, Point); virtual void scale(Point center, double s); virtual void move(int x, int y); virtual void read(istream& is); virtual void plot(Graphics& g) const; virtual void print(ostream& os) const; virtual Shape* clone() const; Point corner(int) const; Point center() const; private: #ifndef PRACTICALOO_MUST_USE_ALLOCATOR vector _corners; #else vector > _corners; #endif }; /*-------------------------------------------------------------*/ EXPORT class Text : public Shape { public: Text() {} Text(Point, string); virtual void scale(Point center, double s); virtual void move(int x, int y); virtual void read(istream& is); virtual void plot(Graphics& g) const; virtual void print(ostream& os) const; virtual Shape* clone() const; private: Point _start; string _text; }; /*-------------------------------------------------------------*/ EXPORT class ScalableText : public Text { public: ScalableText(); ScalableText(Point, string, double); virtual void scale(Point center, double s); virtual void read(istream& is); virtual void plot(Graphics& g) const; virtual void print(ostream& os) const; virtual Shape* clone() const; Rectangle extent(Graphics& g) const; private: double _size; }; /*-------------------------------------------------------------*/ EXPORT inline bool approx_eq(double x, double y) /* PURPOSE: tests whether two floating point numbers are approximately equal RETURNS: true iff |x-y| approx. 0 */ { const double EPS = 0.000001; return fabs(x - y) < EPS; } /*.............................................................*/ EXPORT inline bool approx_le(double x, double y) /* PURPOSE: compares two floating point numbers, allowing for roundoff RETURNS: true iff x < y + roundoff */ { const double EPS = 0.000001; return x - y < EPS; } /*-------------------------------------------------------------*/ Point::Point(int xx, int yy) : _x(xx), _y(yy) {} /*.............................................................*/ void Point::plot(Graphics& g) const { g.drawOval(_x - 2, _y - 2, 5, 5); } /*.............................................................*/ void Point::print(ostream& os) const { os << "(" << _x << "," << _y << ")"; } /*.............................................................*/ Shape* Point::clone() const { return new Point(*this); } /*.............................................................*/ void Point::read(istream& is) { char ch; is >> ch; if (is.fail()) return; if (ch != '(') { is.clear(ios::failbit); return; } is >> _x; is >> ch; if (is.fail()) return; if (ch != ',') { is.clear(ios::failbit); return; } is >> _y; is >> ch; if (is.fail()) return; if (ch != ')') { is.clear(ios::failbit); return; } } /*.............................................................*/ void Point::scale(Point center, double s) { _x = center._x + (int)((_x - center._x) * s); _y = center._y + (int)((_y - center._y) * s); } /*.............................................................*/ void Point::move(int x, int y) { _x += x; _y += y; } /*.............................................................*/ double Point::distance(Point b) const /* RETURNS: the Euclidean distance between this point and b */ { double dx = _x - b._x; double dy = _y - b._y; return sqrt(dx * dx + dy * dy); } /*.............................................................*/ double Point::angle(Point b) const /* RETURNS: the angle between the x axis and the line joining this point and b */ { return atan2((double)(b._y - _y), (double)(b._x - _x)); } /*.............................................................*/ EXPORT inline int Point::x() const { return _x; } /*.............................................................*/ EXPORT inline int Point::y() const { return _y; } /*-------------------------------------------------------------*/ Rectangle::Rectangle() {} /*.............................................................*/ Rectangle::Rectangle(Point p, Point q) /* RECEIVES: p, q - two corner points */ : _topleft(min(p.x(), q.x()), min(p.y(), q.y())), _bottomright(max(p.x(), q.x()), max(p.y(), q.y())) {} /*.............................................................*/ void Rectangle::plot(Graphics& g) const { g.drawRect(_topleft.x(), _topleft.y(), _bottomright.x() - _topleft.x(), _bottomright.y() - _topleft.y()); } /*.............................................................*/ void Rectangle::print(ostream& os) const { _topleft.print(os); _bottomright.print(os); } /*.............................................................*/ Shape* Rectangle::clone() const { return new Rectangle(*this); } /*.............................................................*/ void Rectangle::read(istream& is) { _topleft.read(is); _bottomright.read(is); } /*.............................................................*/ void Rectangle::scale(Point center, double s) { _topleft.scale(center, s); _bottomright.scale(center, s); } /*.............................................................*/ void Rectangle::move(int x, int y) { _topleft.move(x, y); _bottomright.move(x, y); } /*.............................................................*/ bool Rectangle::is_inside(Point p) const /* RETURNS: true iff p is inside this rectangle */ { return _topleft.x() <= p.x() && p.x() <= _bottomright.x() && _topleft.y() <= p.y() && p.y() <= _bottomright.y(); } /*.............................................................*/ Point Rectangle::topleft() const { return _topleft; } /*.............................................................*/ Point Rectangle::bottomright() const { return _bottomright; } /*.............................................................*/ Point Rectangle::center() const { return Point((int)((_topleft.x() + _bottomright.x()) / 2.0), (int)((_topleft.y() + _bottomright.y()) / 2.0)); } /*.............................................................*/ Point Rectangle::boundary_point(Point p) const /* PURPOSE: Compute a point on the boundary of the shape RECEIVES: p - a point distinct from the center RETURNS: the intersection of the line joining the center and p with the boundary REMARKS: The computation is easiest to understand if the rectangle is the unit square. Let p = (x, y). Then the boundary point b is (x / |y|, sign(y)) if |x| <= |y| (sign(x), y / |x|) if |x| >= |y| For a general rectangle, first subtract the center from the coordinates and then divide by xsize/2, ysize/2. */ { Point c = center(); int x = p.x() - c.x(); int y = p.y() - c.y(); if (approx_eq(x, 0) && approx_eq(y, 0)) return c; double xs2 = (_bottomright.x() - _topleft.x()) / 2.0; double ys2 = (_bottomright.y() - _topleft.y()) / 2.0; double xys = x * ys2; double yxs = y * xs2; if (fabs(xys) <= fabs(yxs)) // meets the top or bottom { double bx = xys / y; if (y > 0) return Point((int)(c.x() + bx), (int)(c.y() + ys2)); else return Point((int)(c.x() - bx), (int)(c.y() - ys2)); } else // meets the left or right { double by = yxs / x; if (x > 0) return Point((int)(c.x() + xs2), (int)(c.y() + by)); else return Point((int)(c.x() - xs2), (int)(c.y() - by)); } } /*-------------------------------------------------------------*/ FilledRect::FilledRect() : _color(Color::black) {} /*.............................................................*/ FilledRect::FilledRect(Point p, Point q, Color b) : _color(b), Rectangle(p, q) {} /*.............................................................*/ void FilledRect::plot(Graphics& g) const { g.setColor(_color); g.fillRect(topleft().x(), topleft().y(), bottomright().x() - topleft().x(), bottomright().y() - topleft().y()); g.setColor(Color::black); } /*-------------------------------------------------------------*/ Segment::Segment() {} /*.............................................................*/ Segment::Segment(Point f, Point t) : _from(f), _to(t) {} /*.............................................................*/ Point Segment::from() const { return _from; } /*.............................................................*/ Point Segment::to() const { return _to; } /*.............................................................*/ void Segment::plot(Graphics& g) const { g.drawLine(_from.x(), _from.y(), _to.x(), _to.y()); } /*.............................................................*/ void Segment::print(ostream& os) const { _from.print(os); _to.print(os); } /*.............................................................*/ Shape* Segment::clone() const { return new Segment(*this); } /*.............................................................*/ void Segment::read(istream& is) { _from.read(is); _to.read(is); } /*.............................................................*/ void Segment::scale(Point center, double s) { _from.scale(center, s); _to.scale(center, s); } /*.............................................................*/ void Segment::move(int x, int y) { _from.move(x, y); _to.move(x, y); } /*.............................................................*/ Point Segment::center() const { return Point((int)((_from.x() + _to.x()) / 2.0), (int)((_from.y()+ _to.y()) / 2.0)); } /*.............................................................*/ bool Segment::intersect(Segment seg, Point& p) const /* PURPOSE: Computes whether two segments intersect RECEIVES: seg - another segment p (OUT) - the point of intersection RETURNS: true iff this segment intersects b REMARKS: The line joining a and b is s * a + (1 - s) * b. The line joining c and d is t * c + (1 - t) * d. We solve for (s,t) in s * a + (1 - s) * b = t * c + (1 - t) * d by solving the system of two linear equations. Then we check whether the solution fulfills 0 <= s <= 1 and 0 <= t <= 1. */ { Point a = _from; Point b = _to; Point c = seg._from; Point d = seg._to; int msx, mtx, msy, mty; // the matrix to invert int rx, ry; // the right hand side of the system msx = a.x() - b.x(); mtx = -c.x() + d.x(); msy = a.y() - b.y(); mty = -c.y() + d.y(); rx = -b.x() + d.x(); ry = -b.y() + d.y(); int det = msx * mty - msy * mtx; if (approx_eq(det, 0)) return false; // the lines are parallel double s, t; // the solutions of the system s = (mty * rx - mtx * ry) / det; t = (-msy * rx + msx * ry) / det; if (!approx_le(0, s) || !approx_le(s, 1) || !approx_le(0, t) || !approx_le(t, 1)) return false; // intersection out of range p = Point((int)(t * c.x() + (1 - t) * d.x()), (int)(t * c.y() + (1 - t) * d.y())); return true; } /*.............................................................*/ double Segment::distance(Point p) const /* PURPOSE: compute the distance between a point and a segment RETURNS: the distance between p and this segment */ { return p.distance(closest(p)); } /*.............................................................*/ Point Segment::closest(Point p) const /* PURPOSE: compute the closest point to a given point on a line segment RETURNS: the closest point to p on the line segment REMARKS: First make a the origin, i.e. subtract a from both p and b. If that makes b = 0, then ab is just a point, and the result is a. The projection of p onto b is b/. (<.,.> is scalar product.) This lies on the line segment if 0 <= / <= 1. If < 0, the closest point is a, if > 1, it is b. */ { Point a = _from; Point b = _to; p.move(-a.x(), -a.y()); Point r = b; b.move(-a.x(), -a.y()); int blen = b.x() * b.x() + b.y() * b.y(); if (approx_eq(blen, 0)) return a; double proj = (b.x() * p.x() + b.y() * p.y())/blen; if (approx_le(1, proj)) return r; if (approx_le(proj, 0)) return a; a.move((int)(b.x() * proj), (int)(b.y() * proj)); return a; } /*-------------------------------------------------------------*/ Ellipse::Ellipse() {} /*.............................................................*/ Ellipse::Ellipse(Point c, int a, int b) : _center(c), _xradius(a), _yradius(b) {} /*.............................................................*/ int Ellipse::xradius() const { return _xradius; } /*.............................................................*/ int Ellipse::yradius() const { return _yradius; } /*.............................................................*/ void Ellipse::plot(Graphics& g) const { g.drawOval(_center.x(), _center.y(), _xradius, _yradius); } /*.............................................................*/ void Ellipse::print(ostream& os) const { _center.print(os); os << " " << _xradius << " " << _yradius; } /*.............................................................*/ Shape* Ellipse::clone() const { return new Ellipse(*this); } /*.............................................................*/ void Ellipse::read(istream& is) { _center.read(is); is >> _xradius >> _yradius; } /*.............................................................*/ void Ellipse::scale(Point center, double s) { _center.scale(center, s); _xradius =(int)(_xradius * s); _yradius =(int)(_yradius * s); } /*.............................................................*/ void Ellipse::move(int x, int y) { _center.move(x, y); } /*.............................................................*/ Point Ellipse::center() const { return _center; } /*.............................................................*/ bool Ellipse::is_inside(Point p) const { double x = (p.x() - _center.x())/(double)_xradius; double y = (p.y() - _center.y())/(double)_yradius; return approx_le(x * x + y * y, 1); } /*.............................................................*/ Point Ellipse::boundary_point(Point p) const /* PURPOSE: Compute a point on the boundary of the shape RECEIVES: p - a point distinct from the center RETURNS: the intersection of the line joining the center and p with the boundary */ { double a = _center.angle(p); Point r = _center; r.move((int)(_xradius * cos(a)), (int)(_yradius * sin(a))); return r; } /*-------------------------------------------------------------*/ Polygon::Polygon() /* REMARKS: Makes a useless null polygon */ {} /*.............................................................*/ Polygon::Polygon(int n) /* RECEIVES: n - the number of vertices */ : _corners(n) {} /*.............................................................*/ Point Polygon::corner(int i) const { return _corners[i]; } /*.............................................................*/ void Polygon::set_corner(int i , Point p) { if (0 <= i && i < _corners.size()) _corners[i] = p; } /*.............................................................*/ void Polygon::plot(Graphics& g) const { for (int i = 0; i < _corners.size(); i++) { int j = i + 1; if (j > _corners.size() - 1) j = 0; Point p = _corners[i]; Point q = _corners[j]; g.drawLine(p.x(), p.y(), q.x(), q.y()); } } /*.............................................................*/ void Polygon::print(ostream& os) const { os << _corners.size() << " "; for (int i = 0; i < _corners.size(); i++) { _corners[i].print(os); } } /*.............................................................*/ Shape* Polygon::clone() const { return new Polygon(*this); } /*.............................................................*/ void Polygon::read(istream& is) { int n; is >> n; #ifndef PRACTICALOO_MUST_USE_ALLOCATOR _corners = vector(n); #else _corners = vector >(n); #endif for (int i = 0; i < n; i++) { _corners[i].read(is); } } /*.............................................................*/ void Polygon::scale(Point center, double s) { for (int i = 0; i < _corners.size(); i++) _corners[i].scale(center, s); } /*.............................................................*/ void Polygon::move(int x, int y) { for (int i = 0; i < _corners.size(); i++) _corners[i].move(x, y); } /*.............................................................*/ Point Polygon::center() const { assert(_corners.size() > 0); Point p; for (int i = 0; i < _corners.size(); i++) p = Point(p.x() + _corners[i].x(), p.y() + _corners[i].y()); return Point(p.x() / _corners.size(), p.y() + _corners.size()); } /*-------------------------------------------------------------*/ Text::Text(Point s, string t) : _start(s), _text(t) {} /*.............................................................*/ void Text::plot(Graphics& g) const { g.drawString(_text, _start.x(), _start.y()); } /*.............................................................*/ void Text::scale(Point center, double s) { _start.scale(center, s); } /*.............................................................*/ void Text::move(int x, int y) { _start.move(x, y); } /*.............................................................*/ void read_delimited(istream& is, string& s) /* PURPOSE: Read a "" delimited string (including spaces) from a stream. Embedded " and \ are quoted by \ */ { char ch; s = ""; is >> ch; if (ch != '"') { is.clear(ios::failbit); return; } const int BUFSIZE = 80; char buffer[BUFSIZE]; int i = 0; for(;;) { ch = is.get(); if (is.fail()) return; // expect closing " if (ch == '"') { buffer[i] = 0; s += buffer; return; } if (ch == '\\') { ch = is.get(); // \ is escape for embedded " if (is.fail()) return; // expect escapee } buffer[i] = ch; i++; if (i >= BUFSIZE - 1) { buffer[i] = 0; s += buffer; i = 0; } } } /*.............................................................*/ void Text::read(istream& is) { _start.read(is); read_delimited(is, _text); } /*.............................................................*/ void write_delimited(ostream& os, const string& s) /* PURPOSE: write a "" delimited string (including spaces) to a stream. Embedded " and \ are quoted by \ */ { os.put('"'); for (int i = 0; i < s.length(); i++) { char ch = s[i]; if (ch == '"' || ch == '\\') os.put('\\'); os.put(ch); } os.put('"'); } /*.............................................................*/ void Text::print(ostream& os) const { _start.print(os); os << " "; write_delimited(os, _text); } /*.............................................................*/ Shape* Text::clone() const { return new Text(*this); } /*-------------------------------------------------------------*/ ScalableText::ScalableText() : _size(1) {} /*.............................................................*/ ScalableText::ScalableText(Point start, string t, double size) : Text(start, t), _size(size) {} /*.............................................................*/ void ScalableText::plot(Graphics& g) const { Font f = g.getFont(); Font newfont(f.getName(), (int)(_size + 0.5), f.getStyle()); g.setFont(newfont); Text::plot(g); g.setFont(f); } /*.............................................................*/ void ScalableText::scale(Point center, double s) { Text::scale(center, s); _size = _size * s; } /*.............................................................*/ void ScalableText::read(istream& is) { Text::read(is); is >> _size; } /*.............................................................*/ void ScalableText::print(ostream& os) const { Text::print(os); os << " " << _size; } /*.............................................................*/ Shape* ScalableText::clone() const { return new ScalableText(*this); }