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 }