package org.concord.energy2d.model; import java.awt.Color; import java.awt.Shape; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import org.concord.energy2d.util.XmlCharacterEncoder; /** * Trees are expensive to calculate. So we use additional variables (x, y) for setting locations and avoiding recalculation of shapes when moving them. * * @author Charles Xie * */ public class Tree extends Manipulable { public final static byte REGULAR = 0; public final static byte PINE = 1; private byte type = PINE; private float x; // the x coordinate of the upper-left corner private float y; // the y coordinate of the upper-left corner private Color color = Color.GREEN.darker(); private Rectangle2D.Float boundingBox; /** Construct a tree based on the specified bounding box, which must start from (0, 0). If not, the (x, y) will be ignored. */ public Tree(Shape bb, byte type) { super(bb); if (!(bb instanceof Rectangle2D.Float)) throw new IllegalArgumentException("Shape must be a Rectangle2D.Float"); setType(type); Rectangle2D.Float r = (Rectangle2D.Float) bb; setDimension(r.width, r.height); } public static Area getShape(Rectangle2D.Float r, byte type) { // the positions and sizes of the circles must ensure that r is the bounding box Area a = new Area(new Rectangle2D.Float(r.x + r.width * 0.45f, r.y + r.height * 0.5f, r.width * 0.1f, r.height * 0.5f)); switch (type) { case REGULAR: float p = Math.min(r.width, r.height) * 0.6f; float q = p * 0.8f; a.add(new Area(new Ellipse2D.Float(r.x + (r.width - p) * 0.5f, r.y, p, p))); a.add(new Area(new Ellipse2D.Float(r.x, r.y + p * 0.8f, q, q))); a.add(new Area(new Ellipse2D.Float(r.x + r.width - q, r.y + p * 0.8f, q, q))); break; case PINE: GeneralPath path = new GeneralPath(); path.moveTo(r.x + r.width * 0.5f, r.y); path.lineTo(r.x + r.width * 0.3f, r.y + r.height * 0.3f); path.lineTo(r.x + r.width * 0.7f, r.y + r.height * 0.3f); a.add(new Area(path)); path.reset(); path.moveTo(r.x + r.width * 0.5f, r.y + r.height * 0.2f); path.lineTo(r.x + r.width * 0.2f, r.y + r.height * 0.5f); path.lineTo(r.x + r.width * 0.8f, r.y + r.height * 0.5f); a.add(new Area(path)); path.reset(); path.moveTo(r.x + r.width * 0.5f, r.y + r.height * 0.3f); path.lineTo(r.x, r.y + r.height * 0.8f); path.lineTo(r.x + r.width, r.y + r.height * 0.8f); a.add(new Area(path)); break; } return a; } public void setColor(Color color) { this.color = color; } public Color getColor() { return color; } @Override public void translateBy(float dx, float dy) { x += dx; y += dy; } public void setLocation(float x, float y) { this.x = x; this.y = y; } @Override public Point2D.Float getCenter() { Rectangle2D bound = getShape().getBounds2D(); return new Point2D.Float((float) bound.getCenterX() + x, (float) bound.getCenterY() + y); } @Override public boolean contains(float rx, float ry) { return getShape().contains(rx - x, ry - y); } public void setX(float x) { this.x = x; } public float getX() { return x; } public void setY(float y) { this.y = y; } public float getY() { return y; } public void setType(byte type) { this.type = type; } public byte getType() { return type; } public void setDimension(float w, float h) { boundingBox = new Rectangle2D.Float(0, 0, w, h); setShape(getShape(boundingBox, type)); } public float getWidth() { return boundingBox.width; } public float getHeight() { return boundingBox.height; } @Override public Tree duplicate(float x, float y) { Tree t = new Tree(new Rectangle2D.Float(0, 0, boundingBox.width, boundingBox.height), type); t.setLabel(getLabel()); t.color = color; t.setX(x - boundingBox.width / 2); // offset to the center, since this method is called to paste. t.setY(y - boundingBox.height / 2); return t; } @Override public Tree duplicate() { Tree t = new Tree(new Rectangle2D.Float(0, 0, boundingBox.width, boundingBox.height), type); t.setLabel(getLabel()); t.color = color; t.x = x; t.y = y; return t; } public String toXml() { XmlCharacterEncoder xce = new XmlCharacterEncoder(); String xml = "<tree"; String uid = getUid(); if (uid != null && !uid.trim().equals("")) xml += " uid=\"" + xce.encode(uid) + "\""; String label = getLabel(); if (label != null && !label.trim().equals("")) xml += " label=\"" + xce.encode(label) + "\""; if (!Color.GREEN.darker().equals(color)) xml += " color=\"" + Integer.toHexString(0x00ffffff & color.getRGB()) + "\""; xml += " x=\"" + x + "\""; xml += " y=\"" + y + "\""; xml += " width=\"" + boundingBox.width + "\""; xml += " height=\"" + boundingBox.height + "\""; xml += " type=\"" + type + "\"/>"; return xml; } }