2727package javajs .util ;
2828
2929import javajs .api .EigenInterface ;
30-
3130import javajs .api .Interface ;
3231
33-
34-
35-
36- //import org.jmol.script.T;
37-
3832final public class Measure {
3933
4034 public final static float radiansPerDegree = (float ) (2 * Math .PI / 360 );
@@ -171,6 +165,10 @@ public static P4 getPlaneThroughPoints(T3 pointA,
171165 T3 pointB ,
172166 T3 pointC , V3 vNorm ,
173167 V3 vAB , P4 plane ) {
168+ if (vNorm == null )
169+ vNorm = new V3 ();
170+ if (vAB == null )
171+ vAB = new V3 ();
174172 float w = getNormalThroughPoints (pointA , pointB , pointC , vNorm , vAB );
175173 plane .set4 (vNorm .x , vNorm .y , vNorm .z , w );
176174 return plane ;
@@ -252,12 +250,20 @@ public static float getNormalThroughPoints(T3 pointA, T3 pointB,
252250 return -vTemp .dot (vNorm );
253251 }
254252
255- public static void getPlaneProjection (P3 pt , P4 plane , P3 ptProj , V3 vNorm ) {
253+ /**
254+ * Project a point onto a plane, also returning the normal vector.
255+ *
256+ * @param pt
257+ * @param plane
258+ * @param retPtProj returned pt (can be pt)
259+ * @param retNorm returned normal vector
260+ */
261+ public static void getPlaneProjection (T3 pt , P4 plane , T3 retPtProj , V3 retNorm ) {
256262 float dist = distanceToPlane (plane , pt );
257- vNorm .set (plane .x , plane .y , plane .z );
258- vNorm .normalize ();
259- vNorm .scale (-dist );
260- ptProj .add2 (pt , vNorm );
263+ retNorm .set (plane .x , plane .y , plane .z );
264+ retNorm .normalize ();
265+ retNorm .scale (-dist );
266+ retPtProj .add2 (pt , retNorm );
261267 }
262268
263269 /**
@@ -267,7 +273,7 @@ public static void getPlaneProjection(P3 pt, P4 plane, P3 ptProj, V3 vNorm) {
267273 * @param ptB
268274 * @param ptC
269275 * @param isOutward
270- * @param normal set to be opposite to direction of ptCenter from ABC
276+ * @param normal set to be opposite to direction of ptCenter from abd
271277 * @param vTemp
272278 * @return true if winding is CCW; false if CW
273279 */
@@ -299,23 +305,39 @@ public static void getBisectingPlane(P3 pointA, V3 vAB,
299305 vTemp .normalize ();
300306 getPlaneThroughPoint (ptTemp , vTemp , plane );
301307 }
302-
303- public static void projectOntoAxis (P3 point , P3 axisA ,
308+
309+ /**
310+ * project point onto a line containing ptA and having axis unitVector axisUnitVector,
311+ * returning the result in point and a vector from ptA to pt in vectorProjection
312+ * @param pt input pt to be projected, returns projected
313+ * @param ptA input point on line
314+ * @param axisUnitVector input unit vector
315+ * @param vectorProjection return for pt.sub(ptA)
316+ */
317+ public static void projectOntoAxis (P3 pt , P3 ptA ,
304318 V3 axisUnitVector ,
305319 V3 vectorProjection ) {
306- vectorProjection .sub2 (point , axisA );
320+ vectorProjection .sub2 (pt , ptA );
307321 float projectedLength = vectorProjection .dot (axisUnitVector );
308- point .scaleAdd2 (projectedLength , axisUnitVector , axisA );
309- vectorProjection .sub2 (point , axisA );
322+ pt .scaleAdd2 (projectedLength , axisUnitVector , ptA );
323+ vectorProjection .sub2 (pt , ptA );
310324 }
311-
312- public static void calcBestAxisThroughPoints (P3 [] points , P3 axisA ,
325+
326+ /**
327+ *
328+ * @param points
329+ * @param nPoints
330+ * @param axisA
331+ * @param axisUnitVector
332+ * @param vectorProjection
333+ * @param nTriesMax
334+ */
335+ public static void calcBestAxisThroughPoints (P3 [] points , int nPoints , P3 axisA ,
313336 V3 axisUnitVector ,
314337 V3 vectorProjection ,
315338 int nTriesMax ) {
316339 // just a crude starting point.
317340
318- int nPoints = points .length ;
319341 axisA .setT (points [0 ]);
320342 axisUnitVector .sub2 (points [nPoints - 1 ], axisA );
321343 axisUnitVector .normalize ();
@@ -459,6 +481,7 @@ public static boolean isInTetrahedron(P3 pt, P3 ptA, P3 ptB,
459481
460482
461483 /**
484+ * Calculate the line that is the intersection of two planes.
462485 *
463486 * @param plane1
464487 * @param plane2
@@ -513,6 +536,8 @@ public static Lst<Object> getIntersectionPP(P4 plane1, P4 plane2) {
513536 }
514537
515538 /**
539+ *
540+ * Calculate the intersection of a line with a plane.
516541 *
517542 * @param pt1 point on line
518543 * @param v unit vector of line
@@ -725,4 +750,251 @@ public static float getRmsd(P3[][] centerAndPoints, Quat q) {
725750 return (float ) Math .sqrt (sum2 / n );
726751 }
727752
753+ /**
754+ * Get the endpoints of the best line through N points, where N >= 2
755+ *
756+ * @param points
757+ * @param nPoints
758+ * @return end points
759+ */
760+ public static P3 [] getBestLineThroughPoints (P3 [] points , int nPoints ) {
761+ if (nPoints <= 0 )
762+ nPoints = points .length ;
763+ if (nPoints <= 2 ) {
764+ return points ;
765+ }
766+ P3 ptA = new P3 ();
767+ V3 unitVector = new V3 ();
768+ V3 vTemp = new V3 ();
769+ calcBestAxisThroughPoints (points , nPoints , ptA , unitVector ,
770+ vTemp , 8 );
771+ return getProjectedLineSegment (points , nPoints , ptA , unitVector , vTemp );
772+ }
773+
774+ public static P3 [] getProjectedLineSegment (P3 [] points , int nPoints ,
775+ P3 ptA , V3 unitVector ,
776+ V3 vTemp ) {
777+ if (nPoints < 0 )
778+ nPoints = points .length ;
779+ if (vTemp == null )
780+ vTemp = new V3 ();
781+ P3 pmin = null , pmax = null , p ;
782+ float dmin = Float .MAX_VALUE , dmax = -Float .MAX_VALUE ;
783+ for (int i = 0 ; i < points .length ; i ++) {
784+ projectOntoAxis (p = P3 .newP (points [i ]), ptA , unitVector , vTemp );
785+ float d = unitVector .dot (vTemp );
786+ if (d < dmin ) {
787+ dmin = d ;
788+ pmin = p ;
789+ }
790+ if (d > dmax ) {
791+ dmax = d ;
792+ pmax = p ;
793+ }
794+ }
795+ return new P3 [] { pmin , pmax };
796+ }
797+
798+ public static boolean isInTriangle (P3 p , P3 a , P3 b , P3 c , V3 v0 , V3 v1 , V3 v2 ) {
799+ // from http: //www.blackpawn.com/texts/pointinpoly/default.html
800+ // Compute barycentric coordinates
801+ v0 .sub2 (c , a );
802+ v1 .sub2 (b , a );
803+ v2 .sub2 (p , a );
804+ float dot00 = v0 .dot (v0 );
805+ float dot01 = v0 .dot (v1 );
806+ float dot02 = v0 .dot (v2 );
807+ float dot11 = v1 .dot (v1 );
808+ float dot12 = v1 .dot (v2 );
809+ float invDenom = 1 / (dot00 * dot11 - dot01 * dot01 );
810+ float u = (dot11 * dot02 - dot01 * dot12 ) * invDenom ;
811+ float v = (dot00 * dot12 - dot01 * dot02 ) * invDenom ;
812+ return (u >= 0 && v >= 0 && u + v <= 1 );
813+ }
814+
815+ /**
816+ * Calculate the best ax + by + cz + d = 0 plane through a number of points
817+ * using a three-step check for the best plane based on normal distance. this
818+ * simple calculation isn't perfect, but it does the job quickly and with no
819+ * need for a full singular value decomposition.
820+ *
821+ * @param points
822+ * @param nPoints
823+ * @param plane
824+ * @return RMSD for the best plane along with filling the plane itself
825+ *
826+ * @author hansonr 2022.01.26
827+ */
828+ public static float calcBestPlaneThroughPoints (P3 [] points , int nPoints ,
829+ P4 plane ) {
830+ if (nPoints <= 0 ) {
831+ nPoints = points .length ;
832+ }
833+ if (nPoints == 3 ) {
834+ getPlaneThroughPoints (points [0 ], points [1 ], points [2 ], null , null , plane );
835+ return 0 ;
836+ }
837+ P4 pmin = plane ;
838+ P4 plane2 = new P4 ();
839+ P4 plane3 ;
840+ float rmsd = calcPlaneForMode (points , nPoints , plane , 'z' );
841+ if (rmsd < 1e-6 )
842+ return rmsd ;
843+ float f2 = calcPlaneForMode (points , nPoints , plane2 , 'y' );
844+ if (f2 < rmsd ) {
845+ rmsd = f2 ;
846+ pmin = plane2 ;
847+ plane3 = plane ;
848+ } else {
849+ plane3 = plane2 ;
850+ }
851+ if (rmsd >= 1e-6 ) {
852+ f2 = calcPlaneForMode (points , nPoints , plane3 , 'x' );
853+ if (f2 < rmsd ) {
854+ rmsd = f2 ;
855+ pmin = plane3 ;
856+ }
857+ }
858+ if (pmin != plane ) {
859+ plane .setT (pmin );
860+ plane .w = pmin .w ;
861+ }
862+ return rmsd ;
863+ }
864+
865+ /**
866+ * Compact calculation of the best pane using a simple method discussed at
867+ * https://stackoverflow.com/questions/12299540/plane-fitting-to-4-or-more-xyz-points
868+ *
869+ * (A^T A)^-1 A^T B
870+ *
871+ * run three times to ensure that at least one is not perpendicular.
872+
873+ * @param points
874+ * @param nPoints
875+ * @param plane filled with best plane
876+ * @param mode
877+ * @return rmsd
878+ */
879+ public static float calcPlaneForMode (P3 [] points , int nPoints , P4 plane , char mode ) {
880+
881+ double [][] A = new double [nPoints ][3 ];
882+ double [][] AT = new double [3 ][nPoints ];
883+ double [][] ATAT = new double [3 ][nPoints ];
884+ double [][] ATA1 = new double [3 ][3 ];
885+ double [] B = new double [nPoints ];
886+
887+ for (int i = nPoints ; --i >= 0 ;) {
888+ P3 p = points [i ];
889+ A [i ][0 ] = AT [0 ][i ] = (mode == 'x' ? p .z : p .x );
890+ A [i ][1 ] = AT [1 ][i ] = (mode == 'y' ? p .z : p .y );
891+ A [i ][2 ] = AT [2 ][i ] = 1 ;
892+ B [i ] = -(mode == 'y' ? p .y : mode == 'x' ? p .x : p .z );
893+ }
894+ M3 m = new M3 ();
895+ for (int i = 3 ; --i >= 0 ;) {
896+ for (int j = 3 ; --j >= 0 ;) {
897+ double d = 0 ;
898+ for (int k = nPoints ; --k >= 0 ;) {
899+ d += AT [i ][k ] * A [k ][j ];
900+ }
901+ m .set33 (i , j , (float ) d );
902+ }
903+ }
904+ m .invert ();
905+ for (int i = 3 ; --i >= 0 ;) {
906+ for (int j = 3 ; --j >= 0 ;) {
907+ ATA1 [i ][j ] = m .get33 (i , j );
908+ }
909+ }
910+ for (int i = 3 ; --i >= 0 ;) {
911+ for (int k = nPoints ; --k >= 0 ;) {
912+ double d = 0 ;
913+ for (int j = 3 ; --j >= 0 ;) {
914+ d += ATA1 [i ][j ] * AT [j ][k ];
915+ }
916+ ATAT [i ][k ] = d ;
917+ }
918+ }
919+ switch (mode ) {
920+ case 'x' :
921+ plane .x = 1 ;
922+ break ;
923+ case 'y' :
924+ plane .y = 1 ;
925+ break ;
926+ case 'z' :
927+ plane .z = 1 ;
928+ break ;
929+ }
930+ float len2 = 1 ;
931+ for (int j = 3 ; --j >= 0 ;) {
932+ double v = 0 ;
933+ for (int k = nPoints ; --k >= 0 ;) {
934+ v += ATAT [j ][k ]*B [k ];
935+ }
936+ switch (j ) {
937+ case 0 :
938+ len2 += v * v ;
939+ if (mode == 'x' )
940+ plane .z = (float ) v ;
941+ else
942+ plane .x = (float ) v ;
943+ break ;
944+ case 1 :
945+ len2 += v * v ;
946+ if (mode == 'y' )
947+ plane .z = (float ) v ;
948+ else
949+ plane .y = (float ) v ;
950+ break ;
951+ case 2 :
952+ plane .w = (float ) v ;
953+ }
954+ }
955+ float f = (float ) Math .sqrt (len2 );
956+ plane .scale4 ((1 /plane .w > 0 ? 1 : -1 )/f );
957+ float sum2 = 0 ;
958+ for (int i = 0 ; i < nPoints ; i ++) {
959+ float d = distanceToPlane (plane , points [i ]);
960+ sum2 += d *d ;
961+ }
962+ float ret = (float ) Math .sqrt (sum2 / nPoints );
963+ return ret ;
964+ }
965+
966+ static P3 rndPt () {
967+ return P3 .new3 ((float ) Math .random ()*20 ,(float ) (Math .random ()*20 ),(float ) (Math .random ()*20 ) );
968+ }
969+ static void testRnd () {
970+ P4 plane = P4 .new4 ((float ) Math .random ()*20 , (float ) Math .random ()*20 , (float ) Math .random ()*20 , (float ) Math .random ()*20 );
971+ plane .scale4 (1 /plane .length ());
972+ System .out .println ("\n ==========\n " );
973+ System .out .println ("plane is " + plane );
974+ P3 ptProj = new P3 ();
975+ V3 vNorm = new V3 ();
976+ P3 [] pts = new P3 [4 ];
977+ for (int i = 0 ; i < pts .length ; i ++) {
978+ pts [i ] = new P3 ();
979+ P3 p = rndPt ();
980+ getPlaneProjection (p , plane , ptProj , vNorm );
981+ vNorm .scale (1 /vNorm .length ());
982+ pts [i ].setT (ptProj );
983+ float d = (float )Math .random ()*0.1f ;
984+ pts [i ].scaleAdd2 (d , vNorm , ptProj );
985+ System .out .println (pts [i ] + " d=" + d );
986+ }
987+ P4 p2 = new P4 ();
988+ float f = calcBestPlaneThroughPoints (pts , -1 , p2 );
989+ System .out .println ("found " + p2 + " rmsd = " + f );
990+ }
991+
992+
993+
994+ static public void test () {
995+ for (int i = 0 ; i < 10 ; i ++)
996+ testRnd ();
997+ System .exit (0 );
998+ }
999+
7281000}
0 commit comments