package fr.orsay.lri.varna.applications.templateEditor;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.util.ArrayList;
import fr.orsay.lri.varna.applications.templateEditor.GraphicalTemplateElement.RelativePosition;
import fr.orsay.lri.varna.exceptions.ExceptionEdgeEndpointAlreadyConnected;
import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate;
import fr.orsay.lri.varna.models.CubicBezierCurve;
import fr.orsay.lri.varna.models.templates.RNATemplate;
import fr.orsay.lri.varna.models.templates.RNATemplate.EdgeEndPointPosition;
import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement;
import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix;
import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateUnpairedSequence;
import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement.EdgeEndPoint;
public class UnpairedRegion extends GraphicalTemplateElement{
private RNATemplateUnpairedSequence _e;
public static final double DEFAULT_VECTOR_LENGTH = 35;
public static final double DEFAULT_VECTOR_DISTANCE = 35;
public UnpairedRegion(double x, double y, RNATemplate tmp)
{
_e = tmp.new RNATemplateUnpairedSequence("");
_e.setVertex5(new Point2D.Double(x,y));
_e.setVertex3(new Point2D.Double(x+DEFAULT_VECTOR_DISTANCE,y));
_e.setInTangentVectorLength(DEFAULT_VECTOR_LENGTH);
_e.setInTangentVectorAngle(-Math.PI/2.0);
_e.setOutTangentVectorLength(DEFAULT_VECTOR_LENGTH);
_e.setOutTangentVectorAngle(-Math.PI/2.0);
updateLength();
}
/**
* Build an UnpairedRegion object from a RNATemplateUnpairedSequence
* object. The RNATemplateUnpairedSequence must be connected to
* an helix on both sides.
*/
public UnpairedRegion(RNATemplateUnpairedSequence templateSequence)
{
_e = templateSequence;
}
public Point2D.Double getEdge5()
{
RelativePosition r = RelativePosition.RP_CONNECT_START5;
Couple<RelativePosition,GraphicalTemplateElement> c = getAttachedElement(r);
return (isAnchored5()? c.second.getEdgePosition(c.first): _e.getVertex5());
}
public Point2D.Double getEdge3()
{
RelativePosition r = RelativePosition.RP_CONNECT_END3;
Couple<RelativePosition,GraphicalTemplateElement> c = getAttachedElement(r);
return (isAnchored3()? c.second.getEdgePosition(c.first): _e.getVertex3());
}
public void setEdge5(Point2D.Double d)
{
_e.setVertex5(d);
updateLength();
}
public void setEdge3(Point2D.Double d)
{
_e.setVertex3(d);
updateLength();
}
public boolean isAnchored5()
{
return (_e.getIn().getOtherElement()!=null);
}
public boolean isAnchored3()
{
return (_e.getOut().getOtherElement()!=null);
}
public static Shape bezToShape(CubicBezierCurve c)
{
GeneralPath p = new GeneralPath();
int nb = 9;
double[] tab = new double[nb];
for (int i=0;i<nb;i++)
{
tab[i] = (c.getApproxCurveLength()*(double)i)/(double)nb;
}
Point2D.Double[] points = c.uniformParam(tab);
System.out.println(points.length);
p.moveTo((float)points[0].x,(float)points[0].y);
for (int i=1;i<nb;i++)
{
Point2D.Double a = points[i];
System.out.println(a);
p.lineTo((float)a.x,(float)a.y);
}
p.lineTo((float)c.getP3().x,(float)c.getP3().y);
return p;
}
public Shape getCurve()
{
Point2D.Double p5 = getEdge5();
Point2D.Double p3 = getEdge3();
Point2D.Double t5 = getControl5();
Point2D.Double t3 = getControl3();
return new CubicCurve2D.Double(p5.x,p5.y,t5.x,t5.y,t3.x,t3.y,p3.x,p3.y);
//CubicBezierCurve c = new CubicBezierCurve( p5, t5, t3, p3, 30);
//return bezToShape(c);
}
private int estimateNumberOfBases()
{
Point2D.Double p5 = getEdge5();
Point2D.Double p3 = getEdge3();
Point2D.Double t5 = getControl5();
Point2D.Double t3 = getControl3();
CubicBezierCurve c = new CubicBezierCurve( p5, t5, t3, p3, 30);
// Extremities don't count as unpaired bases because they are part of the connected helix.
return Math.max((int)Math.round(c.getApproxCurveLength()/Helix.LOOP_DISTANCE)-1, 1);
}
private void updateLength()
{
this._e.setLength(estimateNumberOfBases());
}
public void draw(Graphics2D g2d, boolean selected) {
Point2D.Double p5 = getEdge5();
Point2D.Double p3 = getEdge3();
Point2D.Double t5 = getControl5();
Point2D.Double t3 = getControl3();
if (selected)
{
g2d.setStroke(_dashedStroke);
g2d.setColor(BACKBONE_COLOR);
g2d.draw(getBoundingPolygon());
g2d.setStroke(_solidStroke);
drawAnchor(g2d,t5);
drawAnchor(g2d,t3);
double d5x = (t5.x-p5.x)/(t5.distance(p5));
double d5y = (t5.y-p5.y)/(t5.distance(p5));
double d3x = (t3.x-p3.x)/(t3.distance(p3));
double d3y = (t3.y-p3.y)/(t3.distance(p3));
double shift = -3.5;
Point2D.Double tp5 = new Point2D.Double(t5.x-shift*d5x,t5.y-shift*d5y);
Point2D.Double tp3 = new Point2D.Double(t3.x-shift*d3x,t3.y-shift*d3y);
drawArrow(g2d, p5, tp5, UNPAIRED_ARROW_WIDTH);
drawArrow(g2d, p3, tp3, UNPAIRED_ARROW_WIDTH);
}
g2d.setColor(BACKBONE_COLOR);
g2d.setStroke(_solidStroke);
g2d.draw(getCurve());
// Draw bases on curve:
int n = _e.getLength();
// We choose to approximate the Bezier curve by 10*n straight lines.
CubicBezierCurve bezier = new CubicBezierCurve(p5, t5, t3, p3, 10*n);
double curveLength = bezier.getApproxCurveLength();
double delta_t = curveLength / (n+1);
double[] t = new double[n];
for (int k=0; k<n; k++) {
t[k] = (k+1) * delta_t;
}
Point2D.Double[] sequenceBasesCoords = bezier.uniformParam(t);
for (int k=0; k<n; k++) {
drawBase(g2d, sequenceBasesCoords[k]);
}
if (!isAnchored5())
{drawAnchor5(g2d,p5); }
else
{ drawMagnet(g2d,p5);}
if (!isAnchored3())
{drawAnchor3(g2d,p3); }
else
{ drawMagnet(g2d,p3);}
}
public Point2D.Double getControl5()
{
Point2D.Double p5 = getEdge5();
double angle = _e.getInTangentVectorAngle();
return new Point2D.Double(p5.x+Math.cos(angle)*_e.getInTangentVectorLength(),
p5.y+Math.sin(angle)*_e.getInTangentVectorLength());
}
public Point2D.Double getControl3()
{
Point2D.Double p3 = getEdge3();
double angle = _e.getOutTangentVectorAngle();
return new Point2D.Double(p3.x+Math.cos(angle)*_e.getOutTangentVectorLength(),
p3.y+Math.sin(angle)*_e.getOutTangentVectorLength());
}
public static final double MAX_UNPAIRED_CONTROL_DISTANCE = 10.0;
public static final double UNPAIRED_ARROW_WIDTH = 6.0;
public Polygon getBoundingPolygon() {
Point2D.Double p5 = getEdge5();
Point2D.Double p3 = getEdge3();
Point2D.Double t5 = getControl5();
Point2D.Double t3 = getControl3();
double minx = Math.min(p5.x,Math.min(p3.x,Math.min(t5.x,t3.x)));
double maxx = Math.max(p5.x,Math.max(p3.x,Math.max(t5.x,t3.x)));
double miny = Math.min(p5.y,Math.min(p3.y,Math.min(t5.y,t3.y)));
double maxy = Math.max(p5.y,Math.max(p3.y,Math.max(t5.y,t3.y)));
minx -= 10;
maxx += 10;
miny -= 10;
maxy += 10;
int[] x = {(int)minx,(int)maxx,(int)maxx,(int)minx};
int[] y = {(int)miny,(int)miny,(int)maxy,(int)maxy};
return new Polygon(x,y,4);
}
public RelativePosition getClosestEdge(double x, double y) {
Point2D.Double p = new Point2D.Double(x,y);
Point2D.Double p5 = getEdge5();
Point2D.Double p3 = getEdge3();
Point2D.Double t5 = getControl5();
Point2D.Double t3 = getControl3();
ArrayList<Couple<java.lang.Double,RelativePosition>> v = new ArrayList<Couple<java.lang.Double,RelativePosition>>();
v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(p5),RelativePosition.RP_CONNECT_START5));
v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(p3),RelativePosition.RP_CONNECT_END3));
v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(t5),RelativePosition.RP_EDIT_TANGENT_5));
v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(t3),RelativePosition.RP_EDIT_TANGENT_3));
double dist = java.lang.Double.MAX_VALUE;
RelativePosition r = RelativePosition.RP_OUTER;
for (Couple<java.lang.Double,RelativePosition> c : v)
{
if (c.first<dist)
{
dist = c.first;
r = c.second;
}
}
return r;
}
public RelativePosition getConnectedEdge(RelativePosition edge) {
switch(edge)
{
case RP_CONNECT_START5:
return RelativePosition.RP_CONNECT_END3;
case RP_CONNECT_END3:
return RelativePosition.RP_CONNECT_START5;
default:
return RelativePosition.RP_OUTER;
}
}
public Point2D.Double getEdgePosition(RelativePosition edge)
{
switch(edge)
{
case RP_CONNECT_START5:
return getEdge5();
case RP_CONNECT_END3:
return getEdge3();
case RP_EDIT_TANGENT_5:
return getControl5();
case RP_EDIT_TANGENT_3:
return getControl3();
default:
return getEdge5();
}
}
double v2a(Point2D.Double p)
{
return (double)Math.atan2(p.y, p.x);
}
public void updateControl5(Point2D.Double p)
{
Point2D.Double p5 = getEdge5();
_e.setInTangentVectorLength(p5.distance(p));
Point2D.Double x = new Point2D.Double(p.x-p5.x,p.y-p5.y);
_e.setInTangentVectorAngle(v2a(x));
updateLength();
}
public void updateControl3(Point2D.Double p)
{
Point2D.Double p3 = getEdge3();
_e.setOutTangentVectorLength(p3.distance(p));
Point2D.Double x = new Point2D.Double(p.x-p3.x,p.y-p3.y);
_e.setOutTangentVectorAngle(v2a(x));
updateLength();
}
public void translate(double x, double y) {
_e.getVertex5().x += x;
_e.getVertex5().y += y;
_e.getVertex3().x += x;
_e.getVertex3().y += y;
}
public RelativePosition getRelativePosition(double x, double y) {
RelativePosition rp = getClosestEdge(x, y);
double d = getEdgePosition(rp).distance(new Point2D.Double(x,y));
if (d<MAX_UNPAIRED_CONTROL_DISTANCE)
return rp;
if (getCurve().contains(new Point2D.Double(x,y)))
return RelativePosition.RP_INNER_GENERAL;
return RelativePosition.RP_OUTER;
}
public Shape getArea()
{
return getCurve();
}
public void attach(GraphicalTemplateElement e, RelativePosition edgeOrig, RelativePosition edgeDest) throws ExceptionInvalidRNATemplate
{
super.attach(e,edgeOrig,edgeDest);
if (e instanceof Helix)
{
EdgeEndPoint e1 = this.getEndPoint(edgeOrig);
EdgeEndPoint e2 = e.getEndPoint(edgeDest);
boolean parity1 = this.isIn(edgeOrig);
boolean parity2 = e.isIn(edgeDest);
if ((e1!=null)&&(e2!=null)&&(parity1!=parity2))
{
e1.disconnect();
e2.disconnect();
e1.connectTo(e2);
}
}
}
public EdgeEndPoint getEndPoint(RelativePosition r) {
switch(r)
{
case RP_CONNECT_START5:
return _e.getIn();
case RP_CONNECT_END3:
return _e.getOut();
}
return null;
}
public boolean isIn(RelativePosition r) {
switch(r)
{
case RP_CONNECT_START5:
return true;
case RP_CONNECT_END3:
return false;
}
return true;
}
public void detach(RelativePosition edge)
{
// If the underlying template element is still connected, disconnect it
if (getEndPoint(edge).isConnected())
{
Couple<GraphicalTemplateElement.RelativePosition, GraphicalTemplateElement> c = getAttachedElement(edge);
getEndPoint(edge).disconnect();
}
// Call the parent class detach function, which will also take care to disconnect this other endpoint of this edge
super.detach(edge);
}
public void setEdgePosition(RelativePosition edge, Point2D.Double pos) {
switch(edge)
{
case RP_CONNECT_START5:
setEdge5(pos);
break;
case RP_CONNECT_END3:
setEdge3(pos);
break;
case RP_EDIT_TANGENT_5:
updateControl5(pos);
break;
case RP_EDIT_TANGENT_3:
updateControl3(pos);
break;
}
}
public ArrayList<RelativePosition> getConnectedEdges() {
ArrayList<RelativePosition> result = new ArrayList<RelativePosition>();
result.add(RelativePosition.RP_CONNECT_START5);
result.add(RelativePosition.RP_CONNECT_END3);
return result;
}
public RNATemplateElement getTemplateElement() {
return _e;
}
public RelativePosition relativePositionFromEdgeEndPointPosition(
EdgeEndPointPosition pos) {
switch (pos) {
case IN1:
return RelativePosition.RP_CONNECT_START5;
case OUT1:
return RelativePosition.RP_CONNECT_END3;
default:
return null;
}
}
}