/*
Copyright 2006 Rene Grothmann, modified by Eric Hakenholz
This file is part of C.a.R. software.
C.a.R. is a 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, version 3 of the License.
C.a.R. is distributed in the hope that it will be useful,
but 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.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package rene.zirkel.objects;
// file: PrimitiveCircleObject.java
import java.awt.Color;
import java.awt.Rectangle;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import rene.gui.Global;
import rene.util.xml.XmlWriter;
import rene.zirkel.ZirkelCanvas;
import rene.zirkel.construction.Construction;
import rene.zirkel.construction.Count;
import rene.zirkel.expression.Expression;
import rene.zirkel.expression.ExpressionColor;
import rene.zirkel.graphics.MyGraphics;
import rene.zirkel.graphics.MainGraphics;
import rene.zirkel.structures.Coordinates;
public class PrimitiveCircleObject extends ConstructionObject implements
PointonObject, InsideObject {
protected double X, Y, R;
static Count N=new Count();
boolean Partial=false;
PointObject Dep[]; // array for points, depending on the circle for partial
// display
int NDep; // number of points in Dep
PointObject M; // The midpoint
boolean Filled=false;
String StartPtName=null, EndPtName=null; // Border points name for Arcs
PointObject StartPt=null, EndPt=null; // Border points for Arcs
// PointObject RealCorrespondingStartPt=null; // for 3 pts Arcs only
boolean isArc3pts=false;
double A1, A2, A;
boolean Arc=true;
// Seulement pour les droites hyperboliques :
private double oldStartX=Double.NaN;
private double oldStartY=Double.NaN;
public PrimitiveCircleObject(final Construction c, final PointObject p) {
super(c);
setColor(ColorIndex, SpecialColor);
M=p;
Unit=Global.getParameter("unit.length", "");
}
public void validate() {
super.validate();
// Gestion de la continuité des droites sur le cercle horizon :
if (isDPLineObject()) {
// Premier passage, mémorisation de l'extrémité Start de l'arc
if (Double.isNaN(oldStartX)) {
oldStartX=getStart().getX();
oldStartY=getStart().getY();
} else {
final double d1=(oldStartX-getStart().getX())*(oldStartX-getStart().getX())
+(oldStartY-getStart().getY())*(oldStartY-getStart().getY());
final double d2=(oldStartX-getEnd().getX())*(oldStartX-getEnd().getX())
+(oldStartY-getEnd().getY())*(oldStartY-getEnd().getY());
// Si Start et End se sont échangés, on remet dans l'ordre (cela fonctionne
// car ce sont des arcs 180°) :
if (d20&&Partial
&&!hasRange()) // partial display
{
// System.out.println("partial display");
for (int i=0; i0&&Partial) {
double d=Math.abs(Math.sqrt(x*x+y*y)-R);
Value=Math.abs(zc.col(zc.minX()+d)-zc.col(zc.minX()));
if (Math.abs(zc.col(zc.minX()+d)-zc.col(zc.minX()))>=zc.selectionSize()) {
return false;
}
d=Math.PI/18;
double a=Math.atan2(y, x);
if (a<0) {
a+=2*Math.PI;
}
for (int i=0; i2*Math.PI) {
h-=2*Math.PI;
}
if (h<-2*Math.PI) {
h+=2*Math.PI;
}
if (Math.abs(h)=2*Math.PI) {
A-=2*Math.PI;
}
if (A<0) {
A+=2*Math.PI;
}
if (!Obtuse&&A>Math.PI) {
A1=A2;
A=2*Math.PI-A;
A2=A1+A;
}
if (A*Rc1.R+c2.R+1e-10) {
return null;
}
if (r<=1e-10) {
return new Coordinates(c1.X, c1.Y, c1.X, c1.Y);
}
final double l=(r*r+c1.R*c1.R-c2.R*c2.R)/(2*r);
dx/=r;
dy/=r;
final double x=c1.X+l*dx, y=c1.Y+l*dy;
double h=c1.R*c1.R-l*l;
if (h<-1e-10) {
return null;
}
if (h<0) {
h=0;
} else {
h=Math.sqrt(h);
}
return new Coordinates(x+h*dy, y-h*dx, x-h*dy, y+h*dx);
}
@Override
public boolean equals(final ConstructionObject o) {
if (!(o instanceof PrimitiveCircleObject)||!o.valid()) {
return false;
}
final PrimitiveCircleObject l=(PrimitiveCircleObject) o;
return equals(X, l.X)&&equals(Y, l.Y)&&equals(R, l.R);
}
@Override
public void setPartial(final boolean flag) {
if (flag==Partial) {
return;
}
Partial=flag;
if (flag) // depending objects no longer needed
{
Dep=new PointObject[16];
NDep=0;
} else {
Dep=null;
}
}
/**
* Add a point that depends on the circle. Dep is used for partial display.
*
* @param p
*/
public void addDep(final PointObject p) {
if (!Partial||hasRange()||Dep==null||NDep>=Dep.length) {
return;
}
Dep[NDep++]=p;
}
@Override
public void clearCircleDep() {
NDep=0;
}
@Override
public boolean isPartial() {
return Partial;
}
@Override
public void printArgs(final XmlWriter xml) {
xml.printArg("midpoint", M.getName());
if (Partial) {
xml.printArg("partial", "true");
}
if (Filled) {
xml.printArg("filled", "true");
}
if (getStart()!=null) {
xml.printArg("start", getStart().getName());
}
if (getEnd()!=null) {
xml.printArg("end", getEnd().getName());
}
if (!Obtuse) {
xml.printArg("acute", "true");
}
if (!Arc) {
xml.printArg("chord", "true");
}
super.printArgs(xml);
}
/**
* Need to setup the Dep array.
*/
@Override
public ConstructionObject copy(final double x, final double y) {
final PrimitiveCircleObject o=(PrimitiveCircleObject) super.copy(0, 0);
if (o.Partial) {
o.Dep=new PointObject[16];
o.NDep=0;
} else {
o.Dep=null;
}
return o;
}
// public void setDefaults ()
// { super.setDefaults();
// setPartial(Cn.Partial);
// }
/**
* A circle depends on its midpoint at least. Other circles depen on more
* points! No circle depends on Start and End.
*/
@Override
public Enumeration depending() {
super.depending();
DL.add(M);
return DL.elements();
}
/**
* A circle will mark the midpoint as secondary parameter.
*/
@Override
public Enumeration secondaryParams() {
DL.reset();
return depset(M);
}
@Override
public void toggleHidden() {
if (Hidden) {
Hidden=false;
} else {
if (Partial) {
setPartial(false);
Hidden=true;
} else {
setPartial(true);
}
}
}
public PointObject getP1() {
return M;
}
@Override
public void setFilled(final boolean flag) {
Filled=flag;
}
@Override
public boolean isFilled() {
return Filled;
}
@Override
public boolean isFilledForSelect() {
return Filled;
}
@Override
public void translate() {
M=(PointObject) M.getTranslation();
if (hasRange()) {
StartPt=(PointObject) StartPt.getTranslation();
EndPt=(PointObject) EndPt.getTranslation();
StartPtName=StartPt.getName();
EndPtName=EndPt.getName();
}
}
public void setRange(final String s1, final String s2) {
StartPtName=s1;
EndPtName=s2;
setRange((PointObject) Cn.find(StartPtName), (PointObject) Cn.find(EndPtName));
}
public void setRange(PointObject p1, PointObject p2) {
StartPt=p1;
EndPt=p2;
}
public PointObject getStart() {
if ((StartPt==null)&&(StartPtName!=null)) {
StartPt=(PointObject) Cn.find(StartPtName);
}
return StartPt;
}
public PointObject getEnd() {
if ((EndPt==null)&&(EndPtName!=null)) {
EndPt=(PointObject) Cn.find(EndPtName);
}
return EndPt;
}
public double getA1() {
return A1;
}
public double getA2() {
return A2;
}
public boolean hasRange() {
return getStart()!=null&&getEnd()!=null;
}
public void clearRange() {
StartPt=EndPt=null;
StartPtName=EndPtName=null;
}
@Override
public boolean maybeTransparent() {
return true;
}
@Override
public boolean locallyLike(final ConstructionObject o) {
if (!(o instanceof PrimitiveCircleObject)) {
return false;
}
return (equals(X, ((PrimitiveCircleObject) o).X)
&&equals(Y, ((PrimitiveCircleObject) o).Y)&&equals(R,
((PrimitiveCircleObject) o).R));
}
public boolean getArc() {
return Arc;
}
public void setArc(final boolean flag) {
Arc=flag;
}
public void detectArc3Pts() {
/* L'arc défini par trois points est issue d'une macro-construction.
* Par nécessité, les extrémités de l'arc sont des points construits
* qui "Sautent" en s'échangeant lorsque le cercle support change de
* "courbure". La formule en x se trouvant par exemple dans le getStart
* de l'arc est : if(a(C,A,B)<180,x(A),x(C))
* Pour tester le produit vectoriel OA^OC, il faut récupérer les "vraies"
* extrémités A et C, à savoir les points qui ont servi à créer l'arc.
* Cela se fait par une regExp.
*/
if (hasRange()) {
String st=getStart().getEX();
String reg="\\Qif(a(\\E[^,]*,[^,]*,[^\\)]*\\Q)<180,x(\\E([^\\)]*)\\Q),x(\\E([^\\)]*)\\)\\)";
Matcher m=Pattern.compile(reg).matcher(st);
// if (m.find()) {
// PointObject RealStartPt=(PointObject) Cn.find(m.group(1));
// PointObject RealEndPt=(PointObject) Cn.find(m.group(2));
// if (getStart().equals(RealStartPt)) {
// RealCorrespondingStartPt=StartPt;
// } else {
// RealCorrespondingStartPt=EndPt;
// }
// isArc3pts=true;
// System.out.println(RealCorrespondingStartPt.getName()+" "+getStart().getName());
// }
isArc3pts=m.find();
}
}
// Pour les arcs de cercles, calcule l'angle A=SOE avec S=start et E=end
public double computeSOE() {
A1=Math.atan2(getStart().getY()-Y, getStart().getX()-X);
if (A1<0) {
A1+=2*Math.PI;
}
A2=Math.atan2(getEnd().getY()-Y, getEnd().getX()-X);
if (A2<0) {
A2+=2*Math.PI;
}
if (A2Math.PI+1e-10) {
A1=A2;
if (A1>=2*Math.PI) {
A1-=2*Math.PI;
}
A=2*Math.PI-A;
A2=A1+A;
}
if (Partial) {
A1-=10/180.0*Math.PI;
A+=20/180.0*Math.PI;
}
return A;
}
// Pour les arcs de cercles, calcule l'angle A=SOP avec S=start et P=le point passé en param
public double computeSOP(PointObject P) {
double AS=Math.atan2(getStart().getY()-Y, getStart().getX()-X);
if (AS<0) {
AS+=2*Math.PI;
}
double AP=Math.atan2(P.getY()-Y, P.getX()-X);
if (AP<0) {
AP+=2*Math.PI;
}
if (APMath.PI+1e-10) {
AA=2*Math.PI-AA;
}
if (Partial) {
AA+=20/180.0*Math.PI;
}
return AA;
}
/**
* Test, if the projection of (x,y) to the arc contains that point.
*/
public boolean contains(final double x, final double y) {
if (!hasRange()) {
return true;
}
computeSOE();
double a=Math.atan2(y-Y, x-X);
if (a<0) {
a+=2*Math.PI;
}
double d=a-A1;
if (d<0) {
d+=2*Math.PI;
}
return da2) {
if (2*Math.PI-(Alpha-a1)a2) {
if (2*Math.PI-(Alpha-a1)d1) {
x0=x1;
y0=y1;
}
}
// Coordonnées du point P :
P.setXY(x0, y0);
if (!Obtuse) {
// S'il s'agit d'un arc180, il faut que Start,P et End
// soient sur l'arc dans le même ordre, on étudie donc
// le produit des déterminants :
if (det(getStart(), getEnd())*det(getStart(), P)<0) {
k=-k;
P.setAlpha(k);
sop=soe*k;
// Rotation du vecteur OS d'angle sop :
start=getStart();
xx=(start.getX()-getX())*Math.cos(sop)-(start.getY()-getY())*Math.sin(sop);
yy=(start.getX()-getX())*Math.sin(sop)+(start.getY()-getY())*Math.cos(sop);
// Coordonnées du point P :
P.setXY(getX()+xx*unit, getY()+yy*unit);
}
}
// System.out.println("**** detSOE="+det(getStart(), getEnd())+" : detSOP="+det(getStart(), P));
// System.out.println("k="+k);
} else {
final double dx=P.getX()-getX(), dy=P.getY()-getY();
final double r=Math.sqrt(dx*dx+dy*dy);
double X=0, Y=0;
if (r<1e-10) {
X=getX()+getR();
Y=getY();
} else {
X=getX()+dx/r*getR();
Y=getY()+dy/r*getR();
}
if (hasRange()&&getStart()!=P&&getEnd()!=P) {
double Alpha=P.getAlpha();
if (Alpha<0) {
Alpha+=2*Math.PI;
}
if (Alpha>=2*Math.PI) {
Alpha-=2*Math.PI;
}
computeSOE();
final double a1=getA1(), a2=getA2();
if (Alphaa2) {
if (2*Math.PI-(Alpha-a1)