Skip to content

Commit 8b85df6

Browse files
committed
adds plane/line/triangle measure methods
1 parent 339fab4 commit 8b85df6

File tree

3 files changed

+294
-22
lines changed

3 files changed

+294
-22
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20220123183926
1+
20220131135154
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20220123183926
1+
20220131135154

sources/net.sf.j2s.java.core/src/javajs/util/Measure.java

Lines changed: 292 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,8 @@
2727
package javajs.util;
2828

2929
import javajs.api.EigenInterface;
30-
3130
import javajs.api.Interface;
3231

33-
34-
35-
36-
//import org.jmol.script.T;
37-
3832
final 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

Comments
 (0)