1 package org.lcsim.util.swim;
2
3 import hep.physics.vec.BasicHep3Vector;
4 import hep.physics.vec.Hep3Vector;
5 import hep.physics.vec.VecOp;
6
7 import org.lcsim.spacegeom.CartesianPoint;
8 import org.lcsim.spacegeom.CartesianVector;
9 import org.lcsim.spacegeom.SpacePoint;
10 import org.lcsim.spacegeom.SpaceVector;
11
12 /**
13 * A straight line
14 * @author tonyj
15 * @version $Id: Line.java,v 1.12 2007/08/16 21:46:45 jstrube Exp $
16 */
17 public class Line implements Trajectory
18 {
19 private double sinPhi;
20 private double cosPhi;
21 private double sinLambda;
22 private double cosLambda;
23 private Hep3Vector origin;
24
25 /**
26 * Defines a line in space. The direction of the line is defined
27 * by the angles phi and lambda, such that the Cartesian unit vector is
28 * (cos(lambda)*cos(phi), cos(lambda)*sin(phi), sin(lambda)).
29 * @param origin A point on the line
30 */
31 public Line(Hep3Vector origin, double phi, double lambda)
32 {
33 this.origin = origin;
34 sinPhi = Math.sin(phi);
35 cosPhi = Math.cos(phi);
36 sinLambda = Math.sin(lambda);
37 cosLambda = Math.cos(lambda);
38 }
39
40 /**
41 * Gets a point after traveling distance alpha from the trajectory's
42 * origin point along the trajectory
43 */
44 public SpacePoint getPointAtDistance(double alpha)
45 {
46 double z = origin.z() + alpha*sinLambda;
47 double dr = alpha*cosLambda;
48 double x = origin.x() + dr*cosPhi;
49 double y = origin.y() + dr*sinPhi;
50 return new CartesianPoint(x,y,z);
51 }
52
53 /**
54 * Calculates the distance at which the trajectory first reaches radius R.
55 * Returns Double.NaN if the trajectory does not intercept the cylinder.
56 * In principle there could be multiple solutions, so this method should
57 * always return the minimum <b>positive</b> solution.
58 */
59 public double getDistanceToInfiniteCylinder(double r)
60 {
61 if (cosLambda == 0.0) {
62 // This is a special case where the line is parallel to the z-axis.
63 // There can be no well-defined answer in this case
64 return Double.NaN;
65 }
66 // Find the intercepts on the cylinder (Cartesian):
67 double[] distances = this.findInterceptsOnCylinder(r);
68 // Which is the best?
69 double bestDistance = -1.0;
70 if (distances != null)
71 {
72 for (int i=0; i<distances.length; i++) {
73 if (distances[i] >= 0.0) {
74 // Potentially valid -- is it the best so far?
75 if (bestDistance<0.0 || distances[i]<bestDistance) {
76 // Yes!
77 bestDistance = distances[i];
78 }
79 }
80 }
81 }
82 if (bestDistance < 0.0) {
83 // There was no valid (positive) solution
84 return Double.NaN;
85 }
86 return bestDistance;
87 }
88
89 /**
90 * Calculate the distance along the trajectory to reach a given Z plane.
91 * Note distance may be negative.
92 */
93 public double getDistanceToZPlane(double z)
94 {
95 return (z-origin.z())/sinLambda;
96 }
97
98 /**
99 * Obtain the point of closest approach (POCA)
100 * of the line to a point. The return value is the distance
101 * parameter s, such that getPointAtDistance(s) is the POCA.
102 * @param point Point in space to swim to
103 * @return The length parameter s
104 */
105 public double getDistanceToPoint(Hep3Vector point) {
106 return findPOCAToPoint(point);
107 }
108
109 /**
110 * An internal utility routine. This finds all intercepts of the
111 * line on a infinite cylinder of radius r. The cylinder's axis
112 * is the same as the z-axis. The return value is an array of
113 * signed distances from the origin point to the intercept point.
114 * If there are no solutions, the return value is null.
115 */
116 protected double[] findInterceptsOnCylinder(double r)
117 {
118 if (cosLambda == 0.0) {
119 throw new AssertionError("Don't call this private routine with a line that's parallel to the z-axis!");
120 }
121
122 // Treat this first as a problem in the XY plane, then think about Z later.
123 // Track is described as
124 // position(t) = (x0, y0) + t(vx, vy)
125 // i.e.
126 // x = x0 + t(vx)
127 // y = y0 + t(vy) = y0 + ((x-x0)/vx)(vy)
128 // = [y0 - x0.vy/vx] + [x.vy/vx]
129 // = c + m.x
130 // Circle is described as
131 // x*x + y*y = r*r
132 // Solve for x and y:
133 // x*x + (c+mx)*(c+mx) = r*r
134 // x*x + c*c + 2*c*m*x + m*m*x*x = r*r
135 // (m*m + 1).x^2 + (2*c*m).x + (c*c - r*r) = 0
136 // Thus, solving the quadratic,
137 // x = [ -(2*c*m) +- sqrt( (2*c*m)^2 - 4*(m*m+1)*(c*c-r*r) ) ] / [2*(m*m+1)]
138
139 double x0 = origin.x();
140 double y0 = origin.y();
141 double vx = cosLambda*cosPhi;
142 double vy = cosLambda*sinPhi;
143
144 double c = y0 - (x0*vy)/vx;
145 double m = vy/vx;
146
147 if (vx == 0.0) {
148 // This is a special case where the line is perpendicular to the x-axis.
149 // Handle this by flipping the x and y coordinates.
150 x0 = origin.y();
151 y0 = origin.x();
152 vx = cosLambda*sinPhi;
153 vy = cosLambda*cosPhi;
154 c = y0 - (x0*vy)/vx;
155 m = vy/vx;
156 }
157
158 // Solve the quadratic...
159 double termA = (m*m + 1.0);
160 double termB = (2.0*c*m);
161 double termC = (c*c - r*r);
162 double sqrtTerm = termB*termB - 4.0*termA*termC;
163
164 double[] xSolutions = null; // This will hold the co-ordinates of the solutions
165
166 if (sqrtTerm>0.0) {
167 // Two solutions.
168 xSolutions = new double[2];
169 xSolutions[0] = ( -termB + Math.sqrt(sqrtTerm) ) / (2.0 * termA);
170 xSolutions[1] = ( -termB - Math.sqrt(sqrtTerm) ) / (2.0 * termA);
171 } else if (sqrtTerm==0.0) {
172 // One solution
173 xSolutions = new double[1];
174 xSolutions[0] = ( -termB ) / (2.0 * termA);
175 } else if (sqrtTerm < 0.0) {
176 // No solutions
177 }
178
179 double[] distances = null; // The array of signed distances we'll return
180 if (xSolutions == null) {
181 // No solutions -- distances stays null
182 } else {
183 // Unit vector is (cosLambda*cosPhi, cosLambda*sinPhi, sinLambda), so
184 // we can calculate the distances easily...
185 distances = new double[xSolutions.length];
186 for (int i=0; i<distances.length; i++) {
187 // Calculate in a way that remains correct even if we flipped x and y
188 // co-ordinates:
189 distances[i] = (xSolutions[i] - x0) / vx;
190 }
191 }
192
193 return distances;
194 }
195
196 /**
197 * An internal utility routine to find the distance of closest
198 * approach (DOCA) of the line to a point. This is a simple 2D
199 * vector calculation:
200 * * Let the displacement vector from the origin to point be d.
201 * * Decompose d into a component parallel to the line (d_parallel)
202 * and a component perpendicular to the line (d_perp):
203 * d = d_parallel + d_perp
204 * * Take the dot product with the unit vector v along the line to
205 * obtain and use the parallel/perpendicular properties to obtain:
206 * |d.v| = |d_parallel|
207 * * Hence the DOCA, |d_perp|, is given by
208 * |d_perp|^2 = |d|^2 - |d_parallel|^2
209 * = |d|^2 - |d.v|^2
210 */
211 protected double findDOCAToPoint(Hep3Vector point)
212 {
213 // The first line is kind of ugly.
214 Hep3Vector displacement = new BasicHep3Vector(point.x() - origin.x(), point.y() - origin.y(), point.z() - origin.z());
215 Hep3Vector lineDirection = this.getUnitVector();
216 double dotProduct = VecOp.dot(displacement, lineDirection);
217 double doca = Math.sqrt(displacement.magnitudeSquared() - dotProduct*dotProduct);
218 return doca;
219 }
220
221 /**
222 * An internal utility routine to obtain the point of closest approach
223 * (POCA) of the line to a point. The return value is the distance
224 * parameter s, such that getPointAtDistance(s) is the POCA.
225 * The vector calculation is as follows:
226 * Let the origin point be x and the unit vector along the line be v.
227 * Let the point we're trying to find the POCA to be p.
228 * Suppose the POCA is at
229 * x' = x + sv
230 * such that the scalar distance parameter we want is s.
231 * Then the displacement vector from the POCA to p is
232 * d = x' - p
233 * But x' is the POCA, so d is perpendicular to v:
234 * 0 = d.v
235 * = (x' - p).v
236 * = (x + sv - p).v
237 * So
238 * (x-p).v + sv.v = 0
239 * But v is a unit vector, so
240 * s = (p-x).v
241 */
242 protected double findPOCAToPoint(Hep3Vector point)
243 {
244 // Find (p-x)
245 Hep3Vector originToPoint = new BasicHep3Vector(point.x()-origin.x(), point.y() - origin.y(), point.z() - origin.z());
246 Hep3Vector lineDirection = this.getUnitVector();
247 double dotProduct = VecOp.dot(originToPoint, lineDirection);
248 return dotProduct;
249 }
250
251 /**
252 * Internal utility routine to return the unit vector of the
253 * line's direction.
254 */
255 protected Hep3Vector getUnitVector()
256 {
257 Hep3Vector lineDirection = new BasicHep3Vector(cosLambda*cosPhi, cosLambda*sinPhi, sinLambda);
258 return lineDirection;
259 }
260
261 /**
262 * Return the direction of the line. Marked Deprecated since this should
263 * really be replaced by a general method for the Trajectory interface,
264 * perhaps getDirection(double alpha) for distance parameter alpha.
265 */
266 @Deprecated public Hep3Vector getDirection()
267 {
268 return this.getUnitVector();
269 }
270
271 /**
272 * Routine to find where two lines meet.
273 * This will probably be replaced by a more general vertexing solution
274 * at some point, but for now it works.
275 */
276 @Deprecated static public double[] getPOCAOfLines(Line line1, Line line2)
277 {
278 double[] output = line1.findTrackToTrackPOCA(line2);
279 return output;
280 }
281
282 /**
283 * Internal utility routine to find where two lines meet.
284 * This will probably be replaced by a more general vertexing solution
285 * at some point, but for now it works.
286 */
287 @Deprecated private double[] findTrackToTrackPOCA(Line otherLine)
288 {
289 Line line1 = this;
290 Line line2 = otherLine;
291
292 // Unit vectors of the two lines:
293 Hep3Vector v1 = line1.getUnitVector();
294 Hep3Vector v2 = line2.getUnitVector();
295 // Origin points on the two lines:
296 Hep3Vector x1 = line1.origin;
297 Hep3Vector x2 = line2.origin;
298
299 // Find the common perpendicular:
300 Hep3Vector perp = VecOp.cross(v1, v2);
301 if (perp.magnitude() == 0.0) {
302 // Lines are parallel!
303 return null;
304 } else {
305 // Let the origin point along the lines be x1 and x2.
306 // Let the unit vectors along the lines be v1 and v2.
307 // Suppose that the points of closest approach are at:
308 // x1' = x1 + a(v1)
309 // x2' = x2 + b(v2)
310 // Construct a vector p1 which is perpendicular to perp and to v1:
311 // p1 = v1 x perp = v1 x (v1 x v2) = v1(v1.v2) - v2(v1.v1)
312 // p2 = v2 x perp = v2 x (v1 x v2) = v1(v2.v2) - v2(v1.v2)
313 // Then v1.p1 = (v1.v1)(v1.v2) - (v1.v2)(v1.v1) = 0
314 // v1.p2 = (v1.v1)(v2.v2) - (v1.v2)(v1.v2) = L
315 // v2.p1 = (v2.v1)(v1.v2) - (v2.v2)(v1.v1) = -L
316 // v2.p2 = (v2.v1)(v2.v2) - (v2.v2)(v1.v2) = 0
317 // Thus,
318 // (x2'-x1').p1 = (x2 + b(v2) - x1 -a(v1)).p1
319 // = x2.p1 + b(v2.p1) - x1.p1 - a(v1.p1)
320 // = x2.p1 - bL - x1.p1
321 // and similarly,
322 // (x2'-x1').p2 = x2.p2 -x1.p2 - aL
323 // But p1 and p2 are perpendicular to perp, and (x2'-x1') is parallel
324 // to perp for x1' and x2' the closest points. So
325 // (x2'-x1').p1 = x2.p1 - bL - x1.p1 = 0
326 // (x2'-x1').p2 = x2.p2 - x1.p2 - aL = 0
327 // Hence,
328 // -bL = -x2.p1 + x1.p1 => b = (x2-x1).p1 / L
329 // -aL = -x2.p2 + x1.p2 => a = (x2-x1).p2 / L
330 // and so the POCA is at
331 // (x1' + x2')/2 = (x1 + a(v1) + x2 + b(v2)) / 2
332 // = (x1 + [(x2-x1).p2/L]v1 + x2 + [(x2-x1).p1/L]v2) / 2
333
334 // p1 = v1 x perp (and similarly p2)
335 Hep3Vector p1 = VecOp.cross(v1, perp);
336 Hep3Vector p2 = VecOp.cross(v2, perp);
337
338 // L = (v1.v1)(v2.v2) - (v1.v2)(v1.v2) = 1 - (v1.v2)^2
339 double L = 1.0 - (VecOp.dot(v1,v2) * VecOp.dot(v1,v2));
340
341 // a = (x2-x1).p2 / L
342 // b = (x2-x1).p1 / L
343 double a = VecOp.dot(VecOp.sub(x2,x1), p2) / L;
344 double b = VecOp.dot(VecOp.sub(x2,x1), p1) / L;
345
346 double[] output = new double[2];
347 output[0] = a;
348 output[1] = b;
349 return output;
350 }
351 }
352
353 /**
354 * Defines a line in space. The line is defined by a point and a
355 * direction vector. The magnitude of the direction vector is ignored.
356 * @param origin A point on the line
357 * @param dir The signed direction of the line
358 */
359 public Line(Hep3Vector origin, Hep3Vector dir)
360 {
361 // The easy bit:
362 this.origin = origin;
363
364 // The hard bit: find the direction as (cosPhi, sinPhi, cosLambda, sinLambda)
365 double normalization = dir.magnitude();
366 Hep3Vector dirNormalized = hep.physics.vec.VecOp.unit(dir);
367
368 // cosPhi, sinPhi, sinLambda are fairly easy.
369 // It helps that the signed unit vector is (cos(lambda)*cos(phi), cos(lambda)*sin(phi), sin(lambda)).
370 double phi = hep.physics.vec.VecOp.phi(dirNormalized);
371 cosPhi = Math.cos(phi);
372 sinPhi = Math.sin(phi);
373 sinLambda = dirNormalized.z();
374
375 // cosLambda is harder. First find the absolute value:
376 double cosLambdaSquared = (dirNormalized.x()*dirNormalized.x() + dirNormalized.y()*dirNormalized.y());
377 if (cosLambdaSquared == 0.0) {
378 // Easy case
379 cosLambda = 0.0;
380 } else {
381 // Need to tease it apart using either X or Y. Choose the one with the bigger lever arm...
382 boolean useX = (Math.abs(dir.x()) > Math.abs(dir.y()));
383
384 // We will use one of the relations:
385 // vx = cosLambda * cosPhi
386 // vy = cosLambda * sinPhi
387 // where the unit direction vector is (vx, vy, vz)
388 if (useX) {
389 cosLambda = dirNormalized.x() / cosPhi;
390 } else {
391 cosLambda = dirNormalized.y() / sinPhi;
392 }
393 }
394 }
395
396
397 /**
398 * Calculates the <em>unit vector</em> of the momentum at a certain distance from the origin.
399 * Since the momentum is constant for a straight line,
400 * @param alpha is ignored
401 * @return The unit direction of the line, since there is now geometric way to obtain the momentum along a straight line.
402 */
403 public SpaceVector getUnitTangentAtLength(double alpha) {
404 SpaceVector lineDirection = new CartesianVector(cosLambda * cosPhi, cosLambda * sinPhi, sinLambda);
405 return lineDirection;
406 }
407 }