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 }