/*
* This file is part of LaTeXDraw.
* Copyright (c) 2005-2017 Arnaud BLOUIN
* LaTeXDraw is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later version.
* LaTeXDraw is distributed without any warranty; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
package net.sf.latexdraw.models.impl;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import net.sf.latexdraw.models.MathUtils;
import net.sf.latexdraw.models.ShapeFactory;
import net.sf.latexdraw.models.interfaces.shape.IControlPointShape;
import net.sf.latexdraw.models.interfaces.shape.ILine;
import net.sf.latexdraw.models.interfaces.shape.IPoint;
import net.sf.latexdraw.models.interfaces.shape.IShape;
import net.sf.latexdraw.models.interfaces.shape.Position;
/**
* An implementation of a abstract shape that contains control points.
* @author Arnaud Blouin
*/
abstract class LAbstractCtrlPointShape extends LModifiablePointsShape implements IControlPointShape {
/** The default balance gap used to balance all the points of the bézier curve. */
protected int DEFAULT_BALANCE_GAP = 50;
/** This vector contains the points which allows to change the angles of the curves */
protected final List<IPoint> firstCtrlPts;
/** Contains the second control points of each points; useful for closed curve. */
protected final List<IPoint> secondCtrlPts;
/**
* Creates the shape.
*/
LAbstractCtrlPointShape() {
super();
firstCtrlPts = new ArrayList<>();
secondCtrlPts = new ArrayList<>();
}
@Override
public void scale(final double prevWidth, final double prevHeight, final Position pos, final Rectangle2D bound) {
super.scale(prevWidth, prevHeight, pos, bound);
scaleSetPoints(firstCtrlPts, prevWidth, prevHeight, pos, bound);
scaleSetPoints(secondCtrlPts, prevWidth, prevHeight, pos, bound);
}
@Override
public void scaleWithRatio(final double prevWidth, final double prevHeight, final Position pos, final Rectangle2D bound) {
super.scaleWithRatio(prevWidth, prevHeight, pos, bound);
scaleSetPointsWithRatio(firstCtrlPts, prevWidth, prevHeight, pos, bound);
scaleSetPointsWithRatio(secondCtrlPts, prevWidth, prevHeight, pos, bound);
}
/**
* Method used by the balance method. Just returns the balanced control points of the given points.
*/
private IPoint[] getBalancedPoints(final IPoint pt, final IPoint prevPt, final IPoint nextPt) {
final ILine line = ShapeFactory.INST.createLine(prevPt, nextPt);
if(line.isHorizontalLine()) {
line.setLine(pt.getX(), pt.getY(), pt.getX() + 10.0, pt.getY());
}else {
final double b = pt.getY() - line.getA() * pt.getX();
line.setLine(pt.getX(), pt.getY(), pt.getX() + 10.0, line.getA() * (pt.getX() + 10.0) + b);
}
return line.findPoints(pt, DEFAULT_BALANCE_GAP);
}
/**
* Method used by the balance method. Just sets the given control points at the given position.
*/
private void setControlPoints(final int position, final IPoint[] ctrlPts) {
if(ctrlPts == null || ctrlPts.length != 2) return;
// If there exists an intersection point between the two lines created using control points and points,
// where is a loop that must be removed by inverting the control points.
// For the first point, the lines are created differently.
final int posPrev = position == 0 ? 1 : position - 1;
final int posNext = position == 0 ? points.size() - 1 : position == points.size() - 1 ? 0 : position + 1;
final ILine line1 = ShapeFactory.INST.createLine(getPtAt(posPrev), ctrlPts[0]);
final ILine line2 = ShapeFactory.INST.createLine(getPtAt(posNext), ctrlPts[1]);
if(line1.getIntersectionSegment(line2) == null) {
firstCtrlPts.get(position).setPoint(ctrlPts[0]);
secondCtrlPts.get(position).setPoint(ctrlPts[1]);
}else {
firstCtrlPts.get(position).setPoint(ctrlPts[1]);
secondCtrlPts.get(position).setPoint(ctrlPts[0]);
}
}
@Override
public void balance() {
final int size = getNbPoints();
if(size < 3) return;//Works only with more than 2 points.
IPoint ptPrev;
IPoint ptNext;
// Balancing all the points except the first and the last one.
for(int i = 1; i < size - 1; i++) {
ptPrev = points.get(i - 1);
ptNext = points.get(i + 1);
setControlPoints(i, getBalancedPoints(points.get(i), ptPrev, ptNext));
}
// Balancing the first and the last points.
ptPrev = points.get(size - 1);
ptNext = points.get(1);
setControlPoints(0, getBalancedPoints(points.get(0), ptPrev, ptNext));
ptPrev = points.get(size - 2);
ptNext = points.get(0);
setControlPoints(size - 1, getBalancedPoints(points.get(size - 1), ptPrev, ptNext));
}
@Override
public IPoint getFirstCtrlPtAt(final int position) {
if(firstCtrlPts.isEmpty() || position < -1 || position >= firstCtrlPts.size()) {
return null;
}
return position == -1 ? firstCtrlPts.get(firstCtrlPts.size() - 1) : firstCtrlPts.get(position);
}
@Override
public List<IPoint> getFirstCtrlPts() {
return firstCtrlPts;
}
@Override
public IPoint getSecondCtrlPtAt(final int position) {
if(secondCtrlPts.isEmpty() || position < -1 || position >= secondCtrlPts.size()) {
return null;
}
return position == -1 ? secondCtrlPts.get(secondCtrlPts.size() - 1) : secondCtrlPts.get(position);
}
@Override
public List<IPoint> getSecondCtrlPts() {
return secondCtrlPts;
}
@Override
public void setXFirstCtrlPt(final double x, final int id) {
if(MathUtils.INST.isValidCoord(x) && id >= 0 && id < firstCtrlPts.size()) {
firstCtrlPts.get(id).setX(x);
}
}
@Override
public void setXSecondCtrlPt(final double x, final int id) {
if(MathUtils.INST.isValidCoord(x) && id >= 0 && id < secondCtrlPts.size()) {
secondCtrlPts.get(id).setX(x);
}
}
@Override
public void setYFirstCtrlPt(final double y, final int id) {
if(MathUtils.INST.isValidCoord(y) && id >= 0 && id < firstCtrlPts.size()) {
firstCtrlPts.get(id).setY(y);
}
}
@Override
public void setYSecondCtrlPt(final double y, final int id) {
if(MathUtils.INST.isValidCoord(y) && id >= 0 && id < secondCtrlPts.size()) {
secondCtrlPts.get(id).setY(y);
}
}
@Override
public void updateSecondControlPoints() {
for(int i = 0, size = points.size(); i < size; i++) {
secondCtrlPts.get(i).setPoint(firstCtrlPts.get(i).centralSymmetry(points.get(i)));
}
}
@Override
public boolean setPoint(final double x, final double y, final int position) {
final IPoint pt = getPtAt(position);
if(pt == null || !MathUtils.INST.isValidPt(x, y)) {
return false;
}
final double tx = x - pt.getX();
final double ty = y - pt.getY();
getFirstCtrlPtAt(position).translate(tx, ty);
getSecondCtrlPtAt(position).translate(tx, ty);
super.setPoint(x, y, position);
return true;
}
@Override
public void setRotationAngle(final double angle) {
if(MathUtils.INST.isValidCoord(angle)) {
final double diff = angle - getRotationAngle();
final IPoint gc = getGravityCentre();
super.setRotationAngle(angle);
firstCtrlPts.forEach(pt -> pt.setPoint(pt.rotatePoint(gc, diff)));
updateSecondControlPoints();
}
}
@Override
public boolean removePoint(final IPoint pt) {
return removePoint(points.indexOf(pt)) != null;
}
@Override
public IPoint removePoint(final int position) {
final IPoint deleted = super.removePoint(position);
if(deleted != null) {
firstCtrlPts.remove(position == -1 ? firstCtrlPts.size() - 1 : position);
secondCtrlPts.remove(position == -1 ? secondCtrlPts.size() - 1 : position);
}
return deleted;
}
@Override
public void addPoint(final IPoint pt, final int position) {
// Adding the control points.
if(MathUtils.INST.isValidPt(pt) && position >= -1 && position < points.size()+1) {
final IPoint ctrlPt = ShapeFactory.INST.createPoint(pt.getX(), pt.getY() + DEFAULT_POSITION_CTRL);
if(position == -1) {
firstCtrlPts.add(ctrlPt);
secondCtrlPts.add(ctrlPt.centralSymmetry(pt));
}else {
firstCtrlPts.add(position, ctrlPt);
secondCtrlPts.add(position, ctrlPt.centralSymmetry(pt));
}
super.addPoint(pt, position);
}
}
@Override
protected void copyPoints(final IShape sh) {
if(sh instanceof IControlPointShape) {
final IControlPointShape cpSh = (IControlPointShape) sh;
List<IPoint> pts = cpSh.getFirstCtrlPts();
firstCtrlPts.clear();
pts.forEach(pt -> firstCtrlPts.add(ShapeFactory.INST.createPoint(pt)));
pts = cpSh.getSecondCtrlPts();
secondCtrlPts.clear();
pts.forEach(pt -> secondCtrlPts.add(ShapeFactory.INST.createPoint(pt)));
}
super.copyPoints(sh);
}
@Override
public void translate(final double tx, final double ty) {
// Translating control points.
if(MathUtils.INST.isValidPt(tx, ty)) {
firstCtrlPts.forEach(pt -> pt.translate(tx, ty));
secondCtrlPts.forEach(pt -> pt.translate(tx, ty));
super.translate(tx, ty);
}
}
}