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