javajs.util.Measure Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmol Show documentation
Show all versions of jmol Show documentation
Jmol: an open-source Java viewer for chemical structures in 3D
/* $RCSfile$
* $Author: egonw $
* $Date: 2005-11-10 09:52:44 -0600 (Thu, 10 Nov 2005) $
* $Revision: 4255 $
*
* Copyright (C) 2003-2005 The Jmol Development Team
*
* Contact: [email protected]
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package javajs.util;
import javajs.api.EigenInterface;
import javajs.api.Interface;
//import org.jmol.script.T;
final public class Measure {
public final static float radiansPerDegree = (float) (2 * Math.PI / 360);
public static float computeAngle(T3 pointA, T3 pointB, T3 pointC, V3 vectorBA, V3 vectorBC, boolean asDegrees) {
vectorBA.sub2(pointA, pointB);
vectorBC.sub2(pointC, pointB);
float angle = vectorBA.angle(vectorBC);
return (asDegrees ? angle / radiansPerDegree : angle);
}
public static float computeAngleABC(T3 pointA, T3 pointB, T3 pointC, boolean asDegrees) {
V3 vectorBA = new V3();
V3 vectorBC = new V3();
return computeAngle(pointA, pointB, pointC, vectorBA, vectorBC, asDegrees);
}
public static float computeTorsion(T3 p1, T3 p2, T3 p3, T3 p4, boolean asDegrees) {
float ijx = p1.x - p2.x;
float ijy = p1.y - p2.y;
float ijz = p1.z - p2.z;
float kjx = p3.x - p2.x;
float kjy = p3.y - p2.y;
float kjz = p3.z - p2.z;
float klx = p3.x - p4.x;
float kly = p3.y - p4.y;
float klz = p3.z - p4.z;
float ax = ijy * kjz - ijz * kjy;
float ay = ijz * kjx - ijx * kjz;
float az = ijx * kjy - ijy * kjx;
float cx = kjy * klz - kjz * kly;
float cy = kjz * klx - kjx * klz;
float cz = kjx * kly - kjy * klx;
float ai2 = 1f / (ax * ax + ay * ay + az * az);
float ci2 = 1f / (cx * cx + cy * cy + cz * cz);
float ai = (float) Math.sqrt(ai2);
float ci = (float) Math.sqrt(ci2);
float denom = ai * ci;
float cross = ax * cx + ay * cy + az * cz;
float cosang = cross * denom;
if (cosang > 1) {
cosang = 1;
}
if (cosang < -1) {
cosang = -1;
}
float torsion = (float) Math.acos(cosang);
float dot = ijx * cx + ijy * cy + ijz * cz;
float absDot = Math.abs(dot);
torsion = (dot / absDot > 0) ? torsion : -torsion;
return (asDegrees ? torsion / radiansPerDegree : torsion);
}
/**
* This method calculates measures relating to two points in space
* with related quaternion frame difference. It is used in Jmol for
* calculating straightness and many other helical quantities.
*
* @param a
* @param b
* @param dq
* @return new T3[] { pt_a_prime, n, r, P3.new3(theta, pitch, residuesPerTurn), pt_b_prime };
*/
public static T3[] computeHelicalAxis(P3 a, P3 b, Quat dq) {
// b
// | /|
// | / |
// | / |
// |/ c
// b'+ / \
// | / \ Vcb = Vab . n
// n | / \d Vda = (Vcb - Vab) / 2
// |/theta \
// a'+---------a
// r
V3 vab = new V3();
vab.sub2(b, a);
/*
* testing here to see if directing the normal makes any difference -- oddly
* enough, it does not. When n = -n and theta = -theta vab.n is reversed,
* and that magnitude is multiplied by n in generating the A'-B' vector.
*
* a negative angle implies a left-handed axis (sheets)
*/
float theta = dq.getTheta();
V3 n = dq.getNormal();
float v_dot_n = vab.dot(n);
if (Math.abs(v_dot_n) < 0.0001f)
v_dot_n = 0;
V3 va_prime_d = new V3();
va_prime_d.cross(vab, n);
if (va_prime_d.dot(va_prime_d) != 0)
va_prime_d.normalize();
V3 vda = new V3();
V3 vcb = V3.newV(n);
if (v_dot_n == 0)
v_dot_n = PT.FLOAT_MIN_SAFE; // allow for perpendicular axis to vab
vcb.scale(v_dot_n);
vda.sub2(vcb, vab);
vda.scale(0.5f);
va_prime_d.scale(theta == 0 ? 0 : (float) (vda.length() / Math.tan(theta
/ 2 / 180 * Math.PI)));
V3 r = V3.newV(va_prime_d);
if (theta != 0)
r.add(vda);
P3 pt_a_prime = P3.newP(a);
pt_a_prime.sub(r);
// already done this. ??
if (v_dot_n != PT.FLOAT_MIN_SAFE)
n.scale(v_dot_n);
// must calculate directed angle:
P3 pt_b_prime = P3.newP(pt_a_prime);
pt_b_prime.add(n);
theta = computeTorsion(a, pt_a_prime, pt_b_prime, b, true);
if (Float.isNaN(theta) || r.length() < 0.0001f)
theta = dq.getThetaDirectedV(n); // allow for r = 0
// anything else is an array
float residuesPerTurn = Math.abs(theta == 0 ? 0 : 360f / theta);
float pitch = Math.abs(v_dot_n == PT.FLOAT_MIN_SAFE ? 0 : n.length()
* (theta == 0 ? 1 : 360f / theta));
return new T3[] { pt_a_prime, n, r, P3.new3(theta, pitch, residuesPerTurn), pt_b_prime };
}
public static P4 getPlaneThroughPoints(T3 pointA,
T3 pointB,
T3 pointC, V3 vNorm,
V3 vAB, P4 plane) {
float w = getNormalThroughPoints(pointA, pointB, pointC, vNorm, vAB);
plane.set4(vNorm.x, vNorm.y, vNorm.z, w);
return plane;
}
public static void getPlaneThroughPoint(T3 pt, V3 normal, P4 plane) {
plane.set4(normal.x, normal.y, normal.z, -normal.dot(pt));
}
public static float distanceToPlane(P4 plane, T3 pt) {
return (plane == null ? Float.NaN
: (plane.dot(pt) + plane.w) / (float) Math.sqrt(plane.dot(plane)));
}
public static float directedDistanceToPlane(P3 pt, P4 plane, P3 ptref) {
float f = plane.dot(pt) + plane.w;
float f1 = plane.dot(ptref) + plane.w;
return Math.signum(f1) * f / (float) Math.sqrt(plane.dot(plane));
}
public static float distanceToPlaneD(P4 plane, float d, P3 pt) {
return (plane == null ? Float.NaN : (plane.dot(pt) + plane.w) / d);
}
public static float distanceToPlaneV(V3 norm, float w, P3 pt) {
return (norm == null ? Float.NaN
: (norm.dot(pt) + w) / (float) Math.sqrt(norm.dot(norm)));
}
/**
* note that if vAB or vAC is dispensible, vNormNorm can be one of them
* @param pointA
* @param pointB
* @param pointC
* @param vNormNorm
* @param vAB
*/
public static void calcNormalizedNormal(T3 pointA, T3 pointB,
T3 pointC, T3 vNormNorm, T3 vAB) {
vAB.sub2(pointB, pointA);
vNormNorm.sub2(pointC, pointA);
vNormNorm.cross(vAB, vNormNorm);
vNormNorm.normalize();
}
public static float getDirectedNormalThroughPoints(T3 pointA,
T3 pointB, T3 pointC, T3 ptRef, V3 vNorm,
V3 vAB) {
// for x = plane({atomno=1}, {atomno=2}, {atomno=3}, {atomno=4})
float nd = getNormalThroughPoints(pointA, pointB, pointC, vNorm, vAB);
if (ptRef != null) {
P3 pt0 = P3.newP(pointA);
pt0.add(vNorm);
float d = pt0.distance(ptRef);
pt0.sub2(pointA, vNorm);
if (d > pt0.distance(ptRef)) {
vNorm.scale(-1);
nd = -nd;
}
}
return nd;
}
/**
* if vAC is dispensible vNorm can be vAC
* @param pointA
* @param pointB
* @param pointC
* @param vNorm
* @param vTemp
* @return w
*/
public static float getNormalThroughPoints(T3 pointA, T3 pointB,
T3 pointC, T3 vNorm, T3 vTemp) {
// for Polyhedra
calcNormalizedNormal(pointA, pointB, pointC, vNorm, vTemp);
// ax + by + cz + d = 0
// so if a point is in the plane, then N dot X = -d
vTemp.setT(pointA);
return -vTemp.dot(vNorm);
}
public static void getPlaneProjection(P3 pt, P4 plane, P3 ptProj, V3 vNorm) {
float dist = distanceToPlane(plane, pt);
vNorm.set(plane.x, plane.y, plane.z);
vNorm.normalize();
vNorm.scale(-dist);
ptProj.add2(pt, vNorm);
}
/**
*
* @param ptCenter
* @param ptA
* @param ptB
* @param ptC
* @param isOutward
* @param normal set to be opposite to direction of ptCenter from ABC
* @param vTemp
* @return true if winding is CCW; false if CW
*/
public static boolean getNormalFromCenter(P3 ptCenter, P3 ptA, P3 ptB, P3 ptC,
boolean isOutward, V3 normal, V3 vTemp) {
float d = getNormalThroughPoints(ptA, ptB, ptC, normal, vTemp);
boolean isReversed = (distanceToPlaneV(normal, d, ptCenter) > 0);
if (isReversed == isOutward)
normal.scale(-1f);
return !isReversed;
}
public final static V3 axisY = V3.new3(0, 1, 0);
public static void getNormalToLine(P3 pointA, P3 pointB,
V3 vNormNorm) {
// vector in xy plane perpendicular to a line between two points RMH
vNormNorm.sub2(pointA, pointB);
vNormNorm.cross(vNormNorm, axisY);
vNormNorm.normalize();
if (Float.isNaN(vNormNorm.x))
vNormNorm.set(1, 0, 0);
}
public static void getBisectingPlane(P3 pointA, V3 vAB,
T3 ptTemp, V3 vTemp, P4 plane) {
ptTemp.scaleAdd2(0.5f, vAB, pointA);
vTemp.setT(vAB);
vTemp.normalize();
getPlaneThroughPoint(ptTemp, vTemp, plane);
}
public static void projectOntoAxis(P3 point, P3 axisA,
V3 axisUnitVector,
V3 vectorProjection) {
vectorProjection.sub2(point, axisA);
float projectedLength = vectorProjection.dot(axisUnitVector);
point.scaleAdd2(projectedLength, axisUnitVector, axisA);
vectorProjection.sub2(point, axisA);
}
public static void calcBestAxisThroughPoints(P3[] points, P3 axisA,
V3 axisUnitVector,
V3 vectorProjection,
int nTriesMax) {
// just a crude starting point.
int nPoints = points.length;
axisA.setT(points[0]);
axisUnitVector.sub2(points[nPoints - 1], axisA);
axisUnitVector.normalize();
/*
* We now calculate the least-squares 3D axis
* through the helix alpha carbons starting with Vo
* as a first approximation.
*
* This uses the simple 0-centered least squares fit:
*
* Y = M cross Xi
*
* minimizing R^2 = SUM(|Y - Yi|^2)
*
* where Yi is the vector PERPENDICULAR of the point onto axis Vo
* and Xi is the vector PROJECTION of the point onto axis Vo
* and M is a vector adjustment
*
* M = SUM_(Xi cross Yi) / sum(|Xi|^2)
*
* from which we arrive at:
*
* V = Vo + (M cross Vo)
*
* Basically, this is just a 3D version of a
* standard 2D least squares fit to a line, where we would say:
*
* y = m xi + b
*
* D = n (sum xi^2) - (sum xi)^2
*
* m = [(n sum xiyi) - (sum xi)(sum yi)] / D
* b = [(sum yi) (sum xi^2) - (sum xi)(sum xiyi)] / D
*
* but here we demand that the line go through the center, so we
* require (sum xi) = (sum yi) = 0, so b = 0 and
*
* m = (sum xiyi) / (sum xi^2)
*
* In 3D we do the same but
* instead of x we have Vo,
* instead of multiplication we use cross products
*
* A bit of iteration is necessary.
*
* Bob Hanson 11/2006
*
*/
calcAveragePointN(points, nPoints, axisA);
int nTries = 0;
while (nTries++ < nTriesMax
&& findAxis(points, nPoints, axisA, axisUnitVector, vectorProjection) > 0.001) {
}
/*
* Iteration here gets the job done.
* We now find the projections of the endpoints onto the axis
*
*/
P3 tempA = P3.newP(points[0]);
projectOntoAxis(tempA, axisA, axisUnitVector, vectorProjection);
axisA.setT(tempA);
}
public static float findAxis(P3[] points, int nPoints, P3 axisA,
V3 axisUnitVector, V3 vectorProjection) {
V3 sumXiYi = new V3();
V3 vTemp = new V3();
P3 pt = new P3();
P3 ptProj = new P3();
V3 a = V3.newV(axisUnitVector);
float sum_Xi2 = 0;
for (int i = nPoints; --i >= 0;) {
pt.setT(points[i]);
ptProj.setT(pt);
projectOntoAxis(ptProj, axisA, axisUnitVector,
vectorProjection);
vTemp.sub2(pt, ptProj);
//sum_Yi2 += vTemp.lengthSquared();
vTemp.cross(vectorProjection, vTemp);
sumXiYi.add(vTemp);
sum_Xi2 += vectorProjection.lengthSquared();
}
V3 m = V3.newV(sumXiYi);
m.scale(1 / sum_Xi2);
vTemp.cross(m, axisUnitVector);
axisUnitVector.add(vTemp);
axisUnitVector.normalize();
//check for change in direction by measuring vector difference length
vTemp.sub2(axisUnitVector, a);
return vTemp.length();
}
public static void calcAveragePoint(P3 pointA, P3 pointB,
P3 pointC) {
pointC.set((pointA.x + pointB.x) / 2, (pointA.y + pointB.y) / 2,
(pointA.z + pointB.z) / 2);
}
public static void calcAveragePointN(P3[] points, int nPoints,
P3 averagePoint) {
averagePoint.setT(points[0]);
for (int i = 1; i < nPoints; i++)
averagePoint.add(points[i]);
averagePoint.scale(1f / nPoints);
}
public static Lst transformPoints(Lst vPts, M4 m4, P3 center) {
Lst v = new Lst();
for (int i = 0; i < vPts.size(); i++) {
P3 pt = P3.newP(vPts.get(i));
pt.sub(center);
m4.rotTrans(pt);
pt.add(center);
v.addLast(pt);
}
return v;
}
public static boolean isInTetrahedron(P3 pt, P3 ptA, P3 ptB,
P3 ptC, P3 ptD,
P4 plane, V3 vTemp,
V3 vTemp2, boolean fullyEnclosed) {
boolean b = (distanceToPlane(getPlaneThroughPoints(ptC, ptD, ptA, vTemp, vTemp2, plane), pt) >= 0);
if (b != (distanceToPlane(getPlaneThroughPoints(ptA, ptD, ptB, vTemp, vTemp2, plane), pt) >= 0))
return false;
if (b != (distanceToPlane(getPlaneThroughPoints(ptB, ptD, ptC, vTemp, vTemp2, plane), pt) >= 0))
return false;
float d = distanceToPlane(getPlaneThroughPoints(ptA, ptB, ptC, vTemp, vTemp2, plane), pt);
if (fullyEnclosed)
return (b == (d >= 0));
float d1 = distanceToPlane(plane, ptD);
return d1 * d <= 0 || Math.abs(d1) > Math.abs(d);
}
/**
*
* @param plane1
* @param plane2
* @return [ point, vector ] or []
*/
public static Lst