1 /**
2 dlsl.trackball
3 
4 
5 Authors: Peter Particle
6 License: MIT
7 
8 Note: All methods marked with pure are weakly pure since, they all access an instance member.
9 All static methods are strongly pure.
10 */
11 
12 module dlsl.trackball;
13 
14 import dlsl.vector;
15 import dlsl.matrix;
16 //import dlsl.quaternion;
17 
18 const float deg2rad = 0.0174532925199432957692369076849f;
19 const float rad2deg = 57.295779513082320876798154814105f;
20 
21 
22 
23 
24 private struct TrackballBase( bool ORTHOGRAPHIC ) {
25 nothrow:
26 private:
27     float   m_phi           = 0.0f;
28     float   m_theta         = 0.0f;
29     float   m_dolly         = 10.0f;
30 
31     float   m_velXform      = 1;
32     float   m_velOrbit      = 1;
33     float   m_velDolly      = 1;
34 
35     float   m_absX          = 0.0f;
36     float   m_absY          = 0.0f;
37 
38     mat4    m_matrix        = mat4.identity;
39     vec3    m_target        = vec3( 0 );
40     vec3    m_eye           = vec3( 0 );
41 
42     float   m_tanFovy       = 1.0f;
43     float   m_recipHeight   = 0.002f;
44 
45 
46     void abs2rel( ref float x, ref float y ) {
47         float ax = m_absX;
48         float ay = m_absY;
49         m_absX = x;
50         m_absY = y;
51         x = m_absX - ax;
52         y = m_absY - ay;
53     }
54 
55 
56     void update() {
57         auto rotX   = mat4.rotationX( - deg2rad * m_theta );
58         auto rotY   = mat4.rotationY( - deg2rad * m_phi );
59         m_matrix    = mat4.translation( 0, 0, m_dolly ) * rotX * rotY * mat4.translation( m_target );
60         m_eye       = - m_matrix[3].xyz * m_matrix.mat3;    // cheap invert of translate-rotate-only matrix
61     }
62 
63 
64 public:
65 
66     this( vec3 eye, vec3 target = vec3( 0 ), vec3 up = vec3( 0, 1, 0 )) {
67         lookAt( eye, target, up );
68     }
69 
70     this(
71         float ex,       float ey,       float ez,
72         float tx = 0,   float ty = 0,   float tz = 0,
73         float ux = 0,   float uy = 1,   float uz = 0
74         ) {
75         lookAt( vec3( ex, ey, ez ), vec3( tx, ty, tz ), vec3( ux, uy, uz ));
76     }
77 
78     void xformVelocity( float vel )     { m_velXform = vel; }
79     void orbitVelocity( float vel )     { m_velOrbit = vel; }
80     void dollyVelocity( float vel )     { m_velDolly = vel; }
81 
82     // TODO(pp): explain why the 3 functions bellow are required to properly compute panning (xform)
83 
84     void windowHeight( float h )        { m_recipHeight = 2 / h; }
85 
86     static if( !ORTHOGRAPHIC ) {
87         void perspectiveFovy( float f ) { import std.math : tan; m_tanFovy = tan( deg2rad * 0.5f * f ); }
88         void perspectiveFovyWindowHeight( float f, float h )    { perspectiveFovy( f ); windowHeight( h ); }
89     }
90 
91     vec3 eye()                  { return m_eye; }
92     deprecated( "Use worldTransform instead. Function viewTransform is transform of view/eye/camera (inverted worldTransform)" )
93     mat4 matrix()               { return m_matrix; }
94     mat4 worldTransform()       { return m_matrix; }
95     mat4 viewTransform()        { return m_matrix.invertTR; }
96     
97 
98     // set 2D reference point for each navigation gesture (e.g. any click)
99     void reference( float x, float y )  {
100         m_absX = x;
101         m_absY = y;
102     }
103 
104 
105     void orbit( float x, float y ) {
106         abs2rel( x, y );
107         m_phi += m_velOrbit * x;
108         m_theta += m_velOrbit * y;
109         update;
110     }
111 
112     // use x and y vectors from rotation matrix to compute camera movement parallel to screen
113     void xform( float x, float y ) {
114         abs2rel( x, y );
115         static if( !ORTHOGRAPHIC )
116             m_target += m_velXform * m_tanFovy * m_recipHeight * m_dolly * ( x * vec3( m_matrix[0].x, m_matrix[1].x, m_matrix[2].x ) - y * vec3( m_matrix[0].y, m_matrix[1].y, m_matrix[2].y ));
117         else
118             m_target += m_velXform * m_recipHeight * m_dolly * ( x * vec3( m_matrix[0].x, m_matrix[1].x, m_matrix[2].x ) - y * vec3( m_matrix[0].y, m_matrix[1].y, m_matrix[2].y ));
119         update;
120     }
121 
122 
123     void dolly( float x, float y ) {
124         abs2rel( x, y );
125         float d = m_dolly;
126         static if( !ORTHOGRAPHIC )
127             m_dolly -= m_velDolly * m_recipHeight * m_tanFovy * d * ( x + 4 * y );
128         else
129             m_dolly -= m_velDolly * m_recipHeight * d * ( x + 4 * y );  // does this function make sense for orthografik projections?
130         if ( m_dolly < 0.001f ) m_dolly = 0.001f;
131         update;
132     }
133 
134     void dolly( float d ) {
135         m_dolly = d < 0.001f ? 0.001f : d;
136         update;
137     }
138 
139     float dolly() const {
140         return m_dolly;
141     }
142 
143 
144     /// look at function with two points and an up vector, sets inner state of Trackball
145     /// we construct spherical phi, theta and r (dolly) from the passed in vectors
146     /// we have to compute those values in a reversed fashion as the internal matrix
147     /// represents the world transform matrix and not the eye transform matrix
148     void lookAt( vec3 eye, vec3 target = vec3( 0 ), vec3 up = vec3( 0, 1, 0 )) {
149         // vector from target to eye equals the camera z axis as camera look direction is neagtive z
150         vec3 vecZ   = target - eye; // as we reconstruct the inverted world matrix we use the oposite vector
151         m_dolly     = length( vecZ );
152         m_target    = - target;     // inverted target
153         vecZ /= m_dolly;
154         import std.math : asin, atan2;
155         m_phi = rad2deg * atan2( vecZ.x, vecZ.z );  // view matrix is negative
156         m_theta = - rad2deg * asin( vecZ.y );       // view matrix is positive
157 
158         // TODO(pp): compute twist from up vector
159 
160         update;
161 
162         // we can and should set the eye member from the passe in eye argument
163         // update function above is prone to precision issues
164         m_eye = eye;
165     }
166 
167     /// look at function with nine floats representing two points and an up vector
168     void lookAt( float ex, float ey, float ez, float tx = 0, float ty = 0, float tz = 0, float ux = 0, float uy = 1, float uz = 0 ) {
169         lookAt( vec3( ex, ey, ez ), vec3( tx, ty, tz ), vec3( ux, uy, uz ));
170     }
171 }
172 
173 alias Trackball = TrackballBase!false;
174 alias TrackballOrthographic = TrackballBase!true;
175 
176 
177 /// look at function with two points and an up vector, returns the view matrix (camera position and rotation matrix)
178 auto lookAtView( vec3 eye, vec3 target = vec3( 0 ), vec3 up = vec3( 0, 1, 0 )) {
179     vec3 vecZ = normalize( eye - target );  // vector from target to eye equals the camera z axis as camera look direction is negative z
180     vec3 vecX = normalize( cross( up, vecZ ));
181 
182     // TODO( pp ): fix bellow, matrix constructor
183     //return mat4( vecX, 0, cross( vecZ, vecX ), 0, vecZ, 0, eye, 1 );
184 
185     mat4 result;
186     result[0] = vec4( vecX, 0 );
187     result[1] = vec4( cross( vecZ, vecX ), 0 );
188     result[2] = vec4( vecZ, 0 );
189     result[3] = vec4( eye , 1 );
190 
191     return result;
192 }
193 
194 /// look at function with nine floats representing two points and an up vector, returns the view matrix (camera position and rotation matrix)
195 auto lookAtView( float ex, float ey, float ez, float tx = 0, float ty = 0, float tz = 0, float ux = 0, float uy = 1, float uz = 0 ) {
196     return lookAtView( vec3( ex, ey, ez ), vec3( tx, ty, tz ), vec3( ux, uy, uz ));
197 }
198 
199 /// look at function with two points and an up vector, returns the world matrix (inverted camera position and rotation matrix)
200 auto lookAtWorld( vec3 eye, vec3 target = vec3( 0 ), vec3 up = vec3( 0, 1, 0 )) {
201     return lookAtView( eye, target, up ).invertTR;
202 }
203 
204 /// look at function with nine floats representing two points and an up vector, returns the view matrix (inverted camera position and rotation matrix)
205 auto lookAtWorld( float ex, float ey, float ez, float tx = 0, float ty = 0, float tz = 0, float ux = 0, float uy = 1, float uz = 0 ) {
206     return lookAtView( vec3( ex, ey, ez ), vec3( tx, ty, tz ), vec3( ux, uy, uz )).invertTR;
207 }