package es.iiia.shapegrammar.shape;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import es.iiia.shapegrammar.model.GeometryModel;
import es.iiia.shapegrammar.model.NodeModel;
import es.iiia.shapegrammar.model.ShapeGrammarModel;
import es.iiia.shapegrammar.shape.guides.LineDefinition;
import es.iiia.shapegrammar.utils.ShapeTools;
import es.iiia.shapegrammar.utils.XmlUtils;
public class ShapeModel extends GeometryModel implements Cloneable {
private int id;
private String description;
private Rectangle2D bounds;
private ArrayList<Point2D> points;
private ArrayList<LineModel> lines;
private ArrayList<LineModel> maximalLines;
private ArrayList<MarkerModel> markers;
private ArrayList<CarrierModel> carriers;
private ArrayList<IntersectionModel> intersections;
private ArrayList<CurveModel> curves;
private AffineTransform initialTransform;
// [start] Constructors
public ShapeModel() {
super();
}
public ShapeModel(ShapeGrammarModel parent) {
super();
this.setParent(parent);
this.description = "";
}
public ShapeModel(ShapeGrammarModel grammar, Element configuration) {
// init parts
this.setParent(grammar);
// set id
this.id = Integer.parseInt(configuration.getAttribute("id"));
// update ticket
grammar.updateTicket(this.id);
super.setName(XmlUtils.getTextValue(configuration, "name"));
this.description = XmlUtils.getTextValue(configuration, "description");
// init startShape
if (configuration.getAttribute("main").equals("true")) {
this.getGrammar().setStartShape(this);
}
// select all shapes within this shape
NodeList content = configuration.getElementsByTagName("content").item(0).getChildNodes();
// browse through elements and create shapes
String name;
for (int i = 0; i < content.getLength(); i++) {
name = content.item(i).getNodeName();
if (name.equals("polyLine")) {
this.addChild(new LineModel((Element) content.item(i)));
} else if (name.equals("lineGroup")) {
LineGroupModel lineGroup = new LineGroupModel((Element) content.item(i));
this.addChild(lineGroup);
} else if (name.equals("marker")) {
MarkerModel marker = new MarkerModel((Element) content.item(i));
this.addMarker(marker);
} else if (name.equals("quadCurve")) {
CurveModel curve = new CurveModel((Element) content.item(i));
this.addChild(curve);
} else if (name.equals(BezierCurveModel.XmlName)) {
BezierCurveModel curve = new BezierCurveModel((Element) content.item(i));
this.addChild(curve);
}
}
}
// [end]
// [start] Getters / Setters
public ShapeGrammarModel getGrammar() {
if (this.getRootModel() != null && this.getRootModel() instanceof ShapeGrammarModel) {
return (ShapeGrammarModel) this.getRootModel();
}
return null;
}
public AffineTransform getInitialTransform() {
return initialTransform;
}
public void setInitialTransform(AffineTransform initialTransform) {
this.initialTransform = initialTransform;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
// public Rectangle2D getBounds() {
// if (this.bounds == null) {
// //this.bounds = new PrecisionRectangle();
//
// if (this.getChildrenArray().size() > 0) {
// this.bounds = (Rectangle) ((GeometryModel)this.getChildrenArray().get(0)).getBounds().clone();
// }
//
// for (NodeModel geom : this.getChildrenArray()) {
// this.bounds.add(((GeometryModel) geom).getBounds());
// }
// }
// return this.bounds;
// }
public ArrayList<Point2D> getPoints() {
if (this.points == null || this.points.size() == 0) {
this.points = new ArrayList<Point2D>();
for (NodeModel model : this.getChildrenArray()) {
if (model instanceof LineModel) {
LineModel line = (LineModel) model;
for (int i=0; i<line.getPoints().size(); i++) {
this.points.add((Point2D) line.getPoints().get(i).clone());
}
}
}
}
return points;
}
public void setPoints(ArrayList<Point2D> points) {
// TODO: Optimize
this.points.clear();
for (int i = 0; i < points.size(); i++) {
this.points.add((Point2D) points.get(i).clone());
}
}
public ArrayList<MarkerModel> getMarkers() {
if (this.markers == null) {
this.markers = new ArrayList<MarkerModel>();
for (NodeModel model : this.getChildrenArray()) {
if (model instanceof MarkerModel) {
markers.add((MarkerModel) model);
}
}
return markers;
}
return this.markers;
}
private ArrayList<CurveModel> getCurves() {
if (this.curves == null) {
this.curves = new ArrayList<CurveModel>();
for (NodeModel model : this.getChildrenArray()) {
if (model instanceof CurveModel) {
curves.add((CurveModel) model);
}
}
}
return curves;
}
public ArrayList<LineModel> getLines() {
if (this.lines == null) {
this.lines = new ArrayList<LineModel>();
for (NodeModel model : this.getChildrenArray()) {
if (model instanceof LineModel) {
for (LineModel line : ((LineModel) model).getLines()) {
//System.out.println(line);
this.lines.add((LineModel) line.clone());
}
} else if (model instanceof LineGroupModel) {
for (NodeModel subline : model.getChildrenArray()) {
if (subline instanceof LineModel) {
for (LineModel line : ((LineModel) subline).getLines()) {
this.lines.add((LineModel) line.clone());
}
}
}
}
}
}
return this.lines;
}
// [end]
// [start] Structure Modifiers
public boolean addChild(GeometryModel child) {
boolean b = this.getChildrenArray().add(child);
if (b) {
child.setParent(this);
this.reset();
getListeners().firePropertyChange(PROPERTY_ADD, null, child);
}
return b;
}
// markers are stored in separate collection
public boolean addMarker(MarkerModel child) {
boolean b = this.addChild(child);
if (b) {
child.setParent(this);
this.getMarkers().add(child);
}
return b;
}
public boolean removeChild(GeometryModel child) {
boolean b = this.getChildrenArray().remove(child);
if (b)
this.reset();
getListeners().firePropertyChange(PROPERTY_REMOVE, child, null);
return b;
}
protected ShapeModel createInstance() {
return new ShapeModel(this.getGrammar());
}
public ShapeModel clone() {
return this.clone(false);
}
public ShapeModel clone(boolean forceParent) {
ShapeModel model = this.createInstance();
model.setId(this.getId());
model.setName(this.getName() + "_clone");
model.setDescription(this.getDescription());
// clone all subparts
for (NodeModel part : this.getChildrenArray()) {
GeometryModel copy = ((GeometryModel) part).clone();
model.addChild(copy);
// this is option to override the parent to be the original one from the copy
// otherwise the parent is set to be the "model"
// this is used when cloning subshape and lines keep parents their maximal lines
if (forceParent) {
copy.setParent(part.getParent());
}
}
// clone initial transform
if (this.initialTransform != null) {
model.setInitialTransform(new AffineTransform(this.initialTransform));
}
return model;
}
public void merge(ShapeModel shape) {
this.merge(shape, true);
}
public void merge(ShapeModel shape, boolean addMarkers) {
// add geometry
for (NodeModel model : shape.clone().getChildrenArray()) {
if (!addMarkers && model instanceof MarkerModel) {
continue;
}
this.addChild(model);
}
this.reset();
}
// [end]
// [start] Layout Modifiers
public void moveToCenter() {
// move to center
int centerX = (int) this.getBounds().getCenterX();
int centerY = (int) this.getBounds().getCenterY();
this.translate(-centerX, -centerY);
}
public void translate(double x, double y) {
for (NodeModel part : this.getChildrenArray()) {
part.translate(x, y);
}
this.reset();
}
public void rotate(double theta, Point2D rotationCenter) {
for (NodeModel part : this.getChildrenArray()) {
part.rotate(rotationCenter, theta);
}
this.reset();
}
public void scale(double x, double y) {
for (NodeModel part : this.getChildrenArray()) {
part.scale(x, y);
}
this.reset();
}
public void transform(AffineTransform transform) {
for (NodeModel part : this.getChildrenArray()) {
part.transform(transform);
}
this.reset();
}
// [end]
// [start] Subshape Detection Methods
public void maximize() {
// Get the list of curves
ArrayList<CurveModel> curves = this.getCurves();
// Get List of maximal lines
ArrayList<LineModel> maximals = this.getMaximalLines();
// Get Markers
ArrayList<MarkerModel> markers = this.getMarkers();
// Clean everything
this.getChildrenArray().clear();
// Add curves
for (CurveModel curve : curves) {
this.getChildrenArray().add(curve);
}
// Add markers
for (MarkerModel marker : markers) {
this.getChildrenArray().add(marker);
}
// Add maximal lines
for (LineModel maximalLine : maximals) {
this.getChildrenArray().add(maximalLine);
}
// We reinitialize the list of maximal lines
this.lines = null;
this.points = null;
}
// properties
public ArrayList<IntersectionModel> getIntersections() {
return getIntersections(0, true);
}
public ArrayList<IntersectionModel> getIntersections(double angle) {
return getIntersections(angle, true);
}
public ArrayList<IntersectionModel> getIntersections(double angle, boolean includeOuter) {
if (this.intersections == null || this.intersections.size() == 0) {
this.intersections = new ArrayList<IntersectionModel>();
IntersectionModel intersection;
boolean exists;
IntersectionModel.naming = 0;
for (int i = 0; i < this.getCarriers().size(); i++) {
for (int j = 0; j < this.getCarriers().size(); j++) {
if (i <= j) continue;
intersection = new IntersectionModel(this.getCarriers().get(i), this.getCarriers().get(j), includeOuter);
// // test if intersection point has been added
// // TODO: Maybe some other way ?
// exists = false;
// for (IntersectionModel intersect : this.intersections) {
// if (intersect.getIntersection().getX() == intersection.getIntersection().getX() &&
// intersect.getIntersection().getY() == intersection.getIntersection().getY()) {
// exists = true;
// }
// }
if (intersection.getIntersection() != null &&
(angle == 0 || intersection.getAngle() == angle)) {
// if (!exists) {
intersections.add(intersection);
// }
}
}
}
// sort intersections by raising angle
Collections.sort(this.intersections);
}
return this.intersections;
}
public ArrayList<CarrierModel> getCarriers() {
if (this.carriers == null) {
// init carriers
this.carriers = new ArrayList<CarrierModel>();
CarrierModel found;
// System.out.println("STARTING SEARCH FOR CARRIERS");
for (LineModel line : this.getMaximalLines()) {
// search if we've already found this carrier
found = this.findCarrier(line.getDefinition());
if (found != null) {
found.addLine(line);
// System.out.println("EXISTING CARRIER FOR " + line.getDefinition().toString());
} else {
this.carriers.add(new CarrierModel(line));
// System.out.println("NEW CARRIER: " + line.getDefinition().toString());
}
}
// System.out.println("END SEARCH CARRIERS: " + this.carriers.size());
}
return this.carriers;
}
public CarrierModel findCarrier(LineDefinition definition) {
// TODO: Maybe binary search ?
for (CarrierModel car : this.carriers) {
if (car.getDefinition().equals(definition)) {
return car;
}
}
return null;
}
private ArrayList<LineModel> getMaximalLines() {
if (this.maximalLines == null || this.maximalLines.size() == 0) {
this.maximalLines = this.cloneLines();
try {
ShapeTools.createMaximalLines(this.maximalLines);
} catch (Exception e) {
e.printStackTrace();
}
}
return this.maximalLines;
}
// public String toString() {
// return this.getName();
//// StringBuffer bf = new StringBuffer();
//// bf.append(this.getName() + ": ");
//// for (LineModel line : this.getLines()) {
//// bf.append(line.toString() + "->");
//// }
//// return bf.toString() + "\r\n";
// }
// private methods
private ArrayList<LineModel> cloneLines() {
ArrayList<LineModel> target = new ArrayList<LineModel>();
for (LineModel model : this.getLines()) {
target.add(model.clone());
}
return target;
}
public void reset() {
this.bounds = null;
this.points = null;
this.lines = null;
this.maximalLines = null;
this.carriers = null;
this.intersections = null;
this.markers = null;
this.curves = null;
}
// [start] Overridden Methods
@Override
public String toString() {
StringBuffer bf = new StringBuffer();
for (LineModel line : this.getLines()) {
bf.append(line.toString());
bf.append(",");
}
return bf.toString();
}
public int compareTo(Object o) {
if (this.id < ((ShapeModel) o).id)
return -1;
else if (this.id > ((ShapeModel) o).id)
return 1;
return 0;
}
public boolean isEqualTo(ShapeModel shape) {
// we do this by comparing the lines
ArrayList<LineModel> myLines = (ArrayList<LineModel>) this.getLines().clone();
ArrayList<LineModel> hisLines = (ArrayList<LineModel>) shape.getLines().clone();
// do initial check
if (myLines.size() != hisLines.size()) {
return false;
}
// now compare lines
boolean found;
LineModel myLine, hisLine;
for (int i=myLines.size()-1; i>=0; i--) {
found = false;
myLine = myLines.get(i);
for (int j=hisLines.size()-1; j>=0; j--) {
hisLine = hisLines.get(j);
// now search for this line;
if (myLine.isEqualTo(hisLine)) {
found = true;
myLines.remove(myLine);
hisLines.remove(hisLine);
break;
}
}
// now check
if (!found) {
return false;
}
}
// we needed to use all of the lines
if (myLines.size() != 0 || hisLines.size() != 0) {
return false;
}
return true;
}
// [end]
// [start] Persistence Methods
public Element getXml() {
Element root = XmlUtils.createXml("shape");
Document owner = root.getOwnerDocument();
root.setAttribute("id", Integer.toString(this.getId()));
// check if it's main element
if (this.getGrammar() != null && this.getGrammar().getStartShape() == this) {
root.setAttribute("main", "true");
}
Node child = root.appendChild(owner.createElement("name"));
child.appendChild(owner.createTextNode(this.getName()));
child = root.appendChild(owner.createElement("description"));
child.appendChild(owner.createTextNode(this.getDescription()));
child = root.appendChild(owner.createElement("content"));
for (NodeModel geom : this.getChildrenArray()) {
child.appendChild(owner.importNode(((GeometryModel)geom).getXml(), true));
}
return root;
}
// [end]
}