package org.geogebra.common.geogebra3D.kernel3D.geos;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.geos.FromMeta;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoNumberValue;
import org.geogebra.common.kernel.integration.EllipticArcLength;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.kernelND.GeoQuadric3DPartInterface;
import org.geogebra.common.plugin.GeoClass;
import org.geogebra.common.util.debug.Log;
/**
* Class for part of a quadric (e.g. side of a limited cone, cylinder, ...)
*
* @author mathieu
*
*/
public class GeoQuadric3DPart extends GeoQuadric3D implements GeoNumberValue,
FromMeta, GeoQuadric3DPartInterface, GeoQuadric3DLimitedOrPart {
/** min value for limites */
private double min;
/** max value for limites */
private double max;
private double[] tmpDouble2bis = new double[2];
/**
* constructor
*
* @param c
* construction
*/
public GeoQuadric3DPart(Construction c) {
super(c);
}
/**
* Copy constructor
*
* @param quadric
* original
*/
public GeoQuadric3DPart(GeoQuadric3DPart quadric) {
super(quadric);
}
@Override
public void set(GeoElementND geo) {
super.set(geo);
GeoQuadric3DPart quadric = (GeoQuadric3DPart) geo;
setLimits(quadric.bottom, quadric.top);
area = quadric.getArea();
}
/**
* sets the min and max values for limits
*
* @param min
* limit for bottom
* @param max
* limit for top
*/
@Override
public void setLimits(double min, double max) {
bottom = min;
top = max;
if (min < max) {
this.min = min;
this.max = max;
} else {
this.min = max;
this.max = min;
}
}
private double bottom, top;
@Override
public double getBottomParameter() {
return bottom;
}
@Override
public double getTopParameter() {
return top;
}
@Override
public double getMinParameter(int index) {
if (index == 1) {
return min;
}
return super.getMinParameter(index);
}
@Override
public double getMaxParameter(int index) {
if (index == 1) {
return max;
}
return super.getMaxParameter(index);
}
@Override
public void set(Coords origin, Coords direction, Coords eigen, double r,
double r2) {
switch (type) {
default:
case QUADRIC_CYLINDER:
setCylinder(origin, direction, eigen, r, r2);
break;
case QUADRIC_HYPERBOLIC_CYLINDER:
setHyperbolicCylinder(origin, direction, eigen, r, r2);
break;
case QUADRIC_PARABOLIC_CYLINDER:
setParabolicCylinder(origin, direction, eigen, r2);
break;
case QUADRIC_CONE:
setCone(origin, direction, eigen, r, r2);
break;
}
}
@Override
public GeoClass getGeoClassType() {
return GeoClass.QUADRIC_PART;
}
@Override
public String toValueString(StringTemplate tpl) {
switch (type) {
case QUADRIC_CYLINDER:
case QUADRIC_CONE:
return kernel.format(area, tpl);
default:
Log.debug("todo-GeoQuadric3DPart");
return "?";
}
}
@Override
protected StringBuilder buildValueString(StringTemplate tpl) {
return new StringBuilder(toValueString(tpl));
}
@Override
public GeoElement copy() {
return new GeoQuadric3DPart(this);
}
// ////////////////////////
// REGION
// ////////////////////////
@Override
protected void getNormalProjectionParameters(Coords coords,
double[] parameters) {
super.getNormalProjectionParameters(coords, parameters);
if (parameters[1] < getMinParameter(1)) {
parameters[1] = getMinParameter(1);
} else if (parameters[1] > getMaxParameter(1)) {
parameters[1] = getMaxParameter(1);
}
}
@Override
protected Coords[] getProjection(Coords willingCoords,
Coords willingDirection, double t1, double t2) {
if (Kernel.isGreater(t2, t1)) {
return getProjectionSorted(willingCoords, willingDirection, t1, t2);
}
if (Kernel.isGreater(t1, t2)) {
return getProjectionSorted(willingCoords, willingDirection, t2, t1);
}
return super.getProjection(willingCoords, willingDirection, t1, t2);
}
@Override
protected boolean checkParameters(double[] parameters) {
if (Kernel.isGreater(getMinParameter(1), parameters[1])) {
parameters[1] = getMinParameter(1);
return false;
}
if (Kernel.isGreater(parameters[1], getMaxParameter(1))) {
parameters[1] = getMaxParameter(1);
return false;
}
return super.checkParameters(parameters);
}
/**
* try with t1, then with t2, assuming t1 < t2
*
* @param willingCoords
* willing coords
* @param willingDirection
* willing direction
* @param t1
* first possible parameter
* @param t2
* second possible parameter
* @return closest point
*/
private Coords[] getProjectionSorted(Coords willingCoords,
Coords willingDirection, double t1, double t2) {
super.getNormalProjectionParameters(
willingCoords.add(willingDirection.mul(t1)), tmpDouble2);
// check if first parameters are inside
if (Kernel.isGreater(getMinParameter(1), tmpDouble2[1])) {
tmpDouble2[1] = getMinParameter(1);
} else if (Kernel.isGreater(tmpDouble2[1], getMaxParameter(1))) {
tmpDouble2[1] = getMaxParameter(1);
} else {
return new Coords[] { getPoint(tmpDouble2[0], tmpDouble2[1]),
new Coords(tmpDouble2) }; // first
// parameters
// are
// inside
}
// first parameters are outside, check second parameters
super.getNormalProjectionParameters(
willingCoords.add(willingDirection.mul(t2)), tmpDouble2bis);
if (Kernel.isGreater(getMinParameter(1), tmpDouble2bis[1])) {
tmpDouble2bis[1] = getMinParameter(1);
} else if (Kernel.isGreater(tmpDouble2bis[1], getMaxParameter(1))) {
tmpDouble2bis[1] = getMaxParameter(1);
} else {
return new Coords[] { getPoint(tmpDouble2bis[0], tmpDouble2bis[1]),
new Coords(tmpDouble2bis) }; // first
// parameters
// are
// inside
}
// first and second parameters are outside: check nearest limit point
Coords l1 = getPoint(tmpDouble2[0], tmpDouble2[1]);
Coords l2 = getPoint(tmpDouble2bis[0], tmpDouble2bis[1]);
double d1 = l1.distLine(willingCoords, willingDirection);
double d2 = l2.distLine(willingCoords, willingDirection);
if (Kernel.isGreater(d1, d2)) {
return new Coords[] { getPoint(tmpDouble2bis[0], tmpDouble2bis[1]),
new Coords(tmpDouble2bis) };
}
return new Coords[] { getPoint(tmpDouble2[0], tmpDouble2[1]),
new Coords(tmpDouble2) };
}
@Override
public boolean isInRegion(Coords coords) {
// check first if coords is in unlimited quadric
if (!super.isInRegion(coords)) {
return false;
}
// check if coords respect limits
super.getNormalProjectionParameters(coords, tmpDouble2);
if (tmpDouble2[1] < getMinParameter(1)) {
return false;
}
if (tmpDouble2[1] > getMaxParameter(1)) {
return false;
}
// all ok
return true;
}
@Override
protected Coords getPointInRegion(double u, double v) {
double v0;
if (v < getMinParameter(1)) {
v0 = getMinParameter(1);
} else if (v > getMaxParameter(1)) {
v0 = getMaxParameter(1);
} else {
v0 = v;
}
return super.getPointInRegion(u, v0);
}
// ////////////////////////
// AREA
// ////////////////////////
private double area;
/**
* Update the area
*/
public void calcArea() {
// Application.debug("geo="+getLabel()+", half="+getHalfAxis(0)+",
// min="+min+", max="+max+", type="+type);
switch (type) {
case QUADRIC_CYLINDER:
if (!Kernel.isEqual(getHalfAxis(0), getHalfAxis(1))) {
area = EllipticArcLength.getEllipseCircumference(getHalfAxis(0),
getHalfAxis(1)) * (max - min);
} else {
area = 2 * getHalfAxis(0) * Math.PI * (max - min);
}
break;
case QUADRIC_CONE:
if (!Kernel.isEqual(getHalfAxis(0), getHalfAxis(1))) {
double h = max - min;
double a = getHalfAxis(0) * h;
double b = getHalfAxis(1) * h;
area = 0.5 * a
* Math.sqrt(
b * b + h * h)
* EllipticArcLength.getEllipseCircumference(1,
Math.sqrt(1 - (1 - b / a * b / a)
/ (1 + b / h * b / h)));
return;
}
double r2 = getHalfAxis(0);
r2 *= r2;
double h2;
if (min * max < 0) { // "double-cone"
h2 = min * min + max * max;
} else { // truncated cone
h2 = Math.abs(max * max - min * min);
}
area = Math.PI * h2 * r2 * Math.sqrt(1 + 1 / r2);
break;
default:
Log.debug("todo-area");
area = Double.NaN;
}
}
/**
* @return area of lateral surface
*/
public double getArea() {
if (defined) {
return area;
}
return Double.NaN;
}
// ////////////////////////////////
// NumberValue
// ////////////////////////////////
@Override
public MyDouble getNumber() {
return new MyDouble(kernel, getDouble());
}
@Override
public double getDouble() {
return getArea();
}
@Override
public boolean isNumberValue() {
return true;
}
// //////////////////////////
// META
// //////////////////////////
private GeoElement meta = null;
@Override
public int getMetasLength() {
if (meta == null) {
return 0;
}
return 1;
}
@Override
public GeoElement[] getMetas() {
return new GeoElement[] { meta };
}
/**
* @param quadric
* cone/cylinder that created it
*/
public void setFromMeta(GeoElement quadric) {
meta = quadric;
}
// //////////////////////
// DILATE
// //////////////////////
@Override
public void dilate(NumberValue rval, Coords S) {
super.dilate(rval, S);
double r = rval.getDouble();
area *= r * r;
double rAbs = Math.abs(r);
bottom *= rAbs;
top *= rAbs;
min *= rAbs;
max *= rAbs;
}
@Override
protected void getXMLtagsMatrix(StringBuilder sb) {
// no matrix needed since it comes from an algo
}
@Override
protected void classifyQuadric() {
Log.warn("GeoQuadric3DPart should not need classification");
}
@Override
public String getTypeString() {
return "Surface";
}
/**
*
* @return GeoQuadric3D type string
*/
public String getQuadricTypeString() {
return super.getTypeString();
}
}