1 module des.math.linear.ray;
2 
3 import std.math;
4 import std.traits;
5 import des.util.testsuite;
6 import des.math.linear.vector;
7 import des.math.linear.matrix;
8 import des.math.basic;
9 
10 version(unittest)
11 {
12     bool eq_seg(A,B,E=float)( in Ray!A a, in Ray!B b, in E eps=E.epsilon )
13     { return eq_approx( a.pos.data ~ a.dir.data, b.pos.data ~ b.dir.data, eps ); }
14 }
15 
16 ///
17 struct Ray(T) if( isFloatingPoint!T )
18 {
19     ///
20     alias Vector3!T vectype;
21 
22     ///
23     vectype pos, dir;
24 
25     mixin( BasicMathOp!"pos dir" );
26 
27 pure:
28     ///
29     static auto fromPoints(A,B)( in Vector!(3,A) start, in Vector!(3,B) end )
30         if( is(typeof(vectype(start))) && is(typeof(vectype(start))) )
31     { return Ray!T( start, end - start ); }
32 
33     ///
34     this(A,B)( in Vector!(3,A) a, in Vector!(3,B) b )
35         if( is(typeof(vectype(a))) && is(typeof(vectype(b))) )
36     {
37         pos = a;
38         dir = b;
39     }
40 
41     @property
42     {
43         ///
44         ref vectype start() { return pos; }
45         ///
46         ref const(vectype) start() const { return pos; }
47         ///
48         vectype end() const { return pos + dir; }
49         ///
50         vectype end( in vectype p )
51         {
52             dir = p - pos;
53             return p;
54         }
55 
56         ///
57         auto revert() const
58         { return Ray!(T).fromPoints( end, start ); }
59 
60         ///
61         T len2() const { return dir.len2; }
62         ///
63         T len() const { return dir.len; }
64     }
65 
66     /// affine transform
67     auto tr(X)( in Matrix!(4,4,X) mtr ) const
68     {
69         return Ray!T( (mtr * Vector4!T( pos, 1 )).xyz,
70                       (mtr * Vector4!T( dir, 0 )).xyz );
71     }
72 
73     /+ высота проведённая из точки это отрезок, 
74        соединяющий проекцию точки на прямую и 
75        саму точку (Ray) +/
76     ///
77     auto altitude( in vectype pp ) const
78     {
79         auto n = dir.e;
80         auto dd = pos + n * dot(n,(pp-pos));
81         return Ray!T( dd, pp - dd );
82     }
83 
84     /+ общий перпендикуляр +/
85     ///
86     auto altitude(F)( in Ray!F seg ) const
87     {
88         /+ находим нормаль для паралельных 
89         плоскостей в которых лежат s1 и s2 +/
90         auto norm = cross(dir,seg.dir).e;
91 
92         /+ расстояние между началами точками на прямых +/
93         auto mv = pos - seg.pos;
94 
95         /+ нормальный вектор, длиной в расстояние между плоскостями +/
96         auto dist = norm * dot(norm,mv);
97 
98         /+ переносим отрезок на плоскость первой прямой
99            и сразу находим пересечение +/
100         auto pp = calcIntersection( Ray!T( seg.pos + dist, seg.dir ) );
101 
102         return Ray!T( pp, -dist );
103     }
104 
105     /+ пересечение с другой прямой 
106        если она в той же плоскости +/
107     ///
108     auto intersect(F)( in Ray!F seg ) const
109     {
110         auto a = altitude( seg );
111         return a.pos + a.dir * 0.5;
112     }
113 
114     ///
115     auto calcIntersection(F)( in Ray!F seg ) const
116     {
117         auto a = pos;
118         auto v = dir;
119 
120         auto b = seg.pos;
121         auto w = seg.dir;
122 
123         static T resolve( T a0, T r, T a1, T q, T b0, T p, T b1, T s )
124         {
125             T pq = p * q, rs = r * s;
126             return ( a0 * pq + ( b1 - b0 ) * r * q - a1 * rs ) / ( pq - rs );
127         }
128 
129         auto x = resolve( a.x, v.x, b.x, w.x,  a.y, v.y, b.y, w.y );
130         auto y = resolve( a.y, v.y, b.y, w.y,  a.x, v.x, b.x, w.x );
131         auto z = resolve( a.z, v.z, b.z, w.z,  a.y, v.y, b.y, w.y );
132 
133         x = isFinite(x) ? x : resolve( a.x, v.x, b.x, w.x,  a.z, v.z, b.z, w.z );
134         y = isFinite(y) ? y : resolve( a.y, v.y, b.y, w.y,  a.z, v.z, b.z, w.z );
135         z = isFinite(z) ? z : resolve( a.z, v.z, b.z, w.z,  a.x, v.x, b.x, w.x );
136 
137         if( !isFinite(x) ) x = pos.x;
138         if( !isFinite(y) ) y = pos.y;
139         if( !isFinite(z) ) z = pos.z;
140 
141         return vectype( x, y, z );
142     }
143 }
144 
145 ///
146 alias Ray!float  fRay;
147 ///
148 alias Ray!double dRay;
149 ///
150 alias Ray!real   rRay;
151 
152 ///
153 unittest
154 {
155     auto r1 = fRay( vec3(1,2,3), vec3(2,3,4) );
156     auto r2 = fRay( vec3(4,5,6), vec3(5,2,3) );
157     auto rs = fRay( vec3(5,7,9), vec3(7,5,7) );
158     assert( r1 + r2 == rs );
159 }
160 
161 unittest
162 {
163     auto a = vec3(1,2,3);
164     auto b = vec3(2,3,4);
165     auto r1 = fRay( a, b );
166     assert( r1.start == a );
167     assert( r1.end == a + b );
168     r1.start = b;
169     assert( r1.start == b );
170     r1.start = a;
171     assert( r1.len == b.len );
172     r1.end = a;
173     assert( r1.len == 0 );
174 }
175 
176 unittest
177 {
178     import des.math.linear.matrix;
179     auto mtr = mat4( 2, 0.1, 0.04, 2,
180                      0.3, 5, 0.01, 5,
181                      0.1, 0.02, 3, 1,
182                      0, 0, 0, 1 );
183 
184     auto s = fRay( vec3(1,2,3), vec3(2,3,4) );
185     
186     auto ta = (mtr * vec4(s.start,1)).xyz;
187     auto tb = (mtr * vec4(s.end,1)).xyz;
188     auto ts = s.tr( mtr );
189 
190     assert( eq( ts.start, ta ) );
191     assert( eq_approx( ts.end, tb, 1e-5 ) );
192 }
193 
194 ///
195 unittest
196 {
197     auto s = fRay( vec3(2,0,0), vec3(-4,4,0) );
198     auto p = vec3( 0,0,0 );
199     auto r = s.altitude(p);
200     assert( eq( p, r.end ) );
201     assert( eq( r.pos, vec3(1,1,0) ) );
202 }
203 
204 unittest
205 {
206     auto s = fRay( vec3(2,0,0), vec3(0,4,0) );
207     auto p = vec3( 0,0,0 );
208     auto r = s.altitude(p).len;
209     assert( r == 2.0f );
210 }
211 
212 unittest
213 {
214     auto s1 = fRay( vec3(0,0,1), vec3(2,2,0) );
215     auto s2 = fRay( vec3(2,0,-1), vec3(-4,4,0) );
216     auto a1 = s1.altitude(s2);
217     auto a2 = s2.altitude(s1);
218     assert( eq_seg( a1, a2.revert ) );
219     assert( a1.len == 2 );
220     assert( eq( a1.pos, vec3(1,1,1) ) );
221     assert( eq( a1.dir, vec3(0,0,-2) ) );
222 }
223 
224 ///
225 unittest
226 {
227     auto s1 = fRay( vec3(-2,0,0), vec3(1,0,0) );
228     auto s2 = fRay( vec3(0,0,2), vec3(0,1,-1) );
229 
230     auto a1 = s1.altitude(s2);
231     assert( eq_seg( a1, fRay(vec3(0,0,0), vec3(0,1,1)) ) );
232 }
233 
234 ///
235 unittest
236 {
237     auto s1 = fRay( vec3(0,0,0), vec3(2,2,0) );
238     auto s2 = fRay( vec3(2,0,0), vec3(-4,4,0) );
239     assert( eq( s1.intersect(s2), s2.intersect(s1) ) );
240     assert( eq( s1.intersect(s2), vec3(1,1,0) ) );
241 }